diff --git a/CHANGELOG.md b/CHANGELOG.md index 071ac41e..d548eed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] (2019-??-??) +### Added +- **font:** extend font declaration ## [0.7.0] (2019-02-19) ### Added diff --git a/README.md b/README.md index b199049d..aa52a554 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ style1.setPageBreakBefore(); style1.setKeepTogether(); p1.setStyle(style1); // font usage -document.declareFont('Open Sans', 'Open Sans', simpleOdf.FontPitch.Variable); +document.getFontFaceDeclarations().create('Open Sans', 'Open Sans', simpleOdf.FontPitch.Variable); const p2 = body.addParagraph('It always seems impossible until it\'s done.'); const style2 = new simpleOdf.ParagraphStyle(); style1.setFontName('Open Sans'); diff --git a/package.json b/package.json index ccbb5710..92659c15 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,16 @@ "main": "./lib/index.js", "types": "./lib/index.d.ts", "scripts": { + "build": "tsc -p .", "preversion": "npm test", "postversion": "git push && git push --tags", - "prepublishOnly": "tsc", + "prepublishOnly": "npm run build", + "pretest": "npm run build", "posttest": "npm run lint", "test": "jest", "watch-test": "jest --watch", "coverage": "jest --coverage", - "lint": "tslint --project tslint.json src/**/*.ts", + "lint": "tslint -c tslint.json --project .", "docs": "rm -r ./lib && tsc && jsdoc2md --name-format --param-list-format list --separators --partial ./jsdoc2md/body.hbs ./jsdoc2md/params-list.hbs ./jsdoc2md/returns.hbs ./jsdoc2md/scope.hbs --files ./lib/api/**/*.js ./lib/style/**/*.js > ./docs/API.md" }, "dependencies": { diff --git a/src/api/meta/Meta.ts b/src/api/meta/Meta.ts index 2b2ae50e..536d1a08 100644 --- a/src/api/meta/Meta.ts +++ b/src/api/meta/Meta.ts @@ -73,6 +73,7 @@ export class Meta { * @since 0.6.0 */ public setCreator (creator: string | undefined): Meta { + /* tslint:disable-next-line:strict-type-predicates */ if (creator === undefined || typeof creator === 'string') { this.creator = creator; } @@ -162,6 +163,7 @@ export class Meta { * @since 0.6.0 */ public setDescription (description: string | undefined): Meta { + /* tslint:disable-next-line:strict-type-predicates */ if (description === undefined || typeof description === 'string') { this.description = description; } @@ -233,6 +235,7 @@ export class Meta { * @since 0.6.0 */ public setInitialCreator (initialCreator: string | undefined): Meta { + /* tslint:disable-next-line:strict-type-predicates */ if (initialCreator === undefined || typeof initialCreator === 'string') { this.initialCreator = initialCreator; } @@ -271,6 +274,7 @@ export class Meta { * @since 0.6.0 */ public addKeyword (keyword: string): Meta { + /* tslint:disable-next-line:strict-type-predicates */ if (typeof keyword === 'string') { this.keywords.push(...keyword.split(',')); } @@ -420,6 +424,7 @@ export class Meta { * @since 0.6.0 */ public setPrintedBy (printedBy: string | undefined): Meta { + /* tslint:disable-next-line:strict-type-predicates */ if (printedBy === undefined || typeof printedBy === 'string') { this.printedBy = printedBy; } @@ -457,6 +462,7 @@ export class Meta { * @since 0.6.0 */ public setSubject (subject: string | undefined): Meta { + /* tslint:disable-next-line:strict-type-predicates */ if (subject === undefined || typeof subject === 'string') { this.subject = subject; } @@ -493,6 +499,7 @@ export class Meta { * @since 0.6.0 */ public setTitle (title: string | undefined): Meta { + /* tslint:disable-next-line:strict-type-predicates */ if (title === undefined || typeof title === 'string') { this.title = title; } diff --git a/src/api/office/FontFaceDeclarations.spec.ts b/src/api/office/FontFaceDeclarations.spec.ts new file mode 100644 index 00000000..2a1e9f67 --- /dev/null +++ b/src/api/office/FontFaceDeclarations.spec.ts @@ -0,0 +1,64 @@ +import { FontFace, FontPitch } from '../style'; +import { FontFaceDeclarations } from './FontFaceDeclarations'; + +describe(FontFaceDeclarations.name, () => { + const testFontFamily = 'someFontFamily'; + const testFontName = 'someFontName'; + const testFontPitch = FontPitch.Variable; + + let fontFaceDeclarations: FontFaceDeclarations; + + beforeEach(() => { + fontFaceDeclarations = new FontFaceDeclarations(); + }); + + describe('font face', () => { + it('return an empty list by default', () => { + const fonts = fontFaceDeclarations.getAll(); + + expect(fonts).toEqual([]); + }); + + it('create and return new font', () => { + const font = fontFaceDeclarations.create(testFontName, testFontFamily, testFontPitch); + + expect(font).toBeInstanceOf(FontFace); + expect(font.getFontFamily()).toBe(testFontFamily); + expect(font.getFontPitch()).toBe(testFontPitch); + expect(font.getName()).toBe(testFontName); + }); + + it('create a font only once', () => { + const font1 = fontFaceDeclarations.create(testFontName); + const font2 = fontFaceDeclarations.create(testFontName); + const fonts = fontFaceDeclarations.getAll(); + + expect(font1).toBe(font2); + expect(fonts.length).toBe(1); + expect(fonts[0]).toBe(font1); + }); + + it('get previously created font', () => { + const font1 = fontFaceDeclarations.create(testFontName); + const font2 = fontFaceDeclarations.get(testFontName); + + expect(font1).toBe(font2); + }); + + it('return undefined if unknown font is requested', () => { + fontFaceDeclarations.create(testFontName); + + const font = fontFaceDeclarations.get('unknownFontName'); + + expect(font).toBeUndefined(); + }); + + it('delete font', () => { + fontFaceDeclarations.create(testFontName); + expect(fontFaceDeclarations.getAll().length).toBe(1); + + fontFaceDeclarations.delete(testFontName); + expect(fontFaceDeclarations.getAll().length).toBe(0); + }); + }); +}); diff --git a/src/api/office/FontFaceDeclarations.ts b/src/api/office/FontFaceDeclarations.ts new file mode 100644 index 00000000..ceb0df7f --- /dev/null +++ b/src/api/office/FontFaceDeclarations.ts @@ -0,0 +1,99 @@ +import { FontFace, FontPitch } from '../style'; + +/** + * This class contains all font face declarations of a document. + * + * It is used to manage the fonts that are used in the document. + * + * @example + * document.getFontFaceDeclarations() + * .create('FreeSans', 'FreeSans', FontPitch.Variable); + * + * @since 0.8.0 + */ +export class FontFaceDeclarations { + private readonly fontFaces: Map = new Map(); + + /** + * Creates a {@link FontFace} object with the given name. + * If a font with this name already exists, the existing font will be returned. + * + * @example + * const fontFaceDeclarations = new FontFaceDeclarations(); + * fontFaceDeclarations.create('FreeSans', 'FreeSans', FontPitch.Variable); + * + * @param {string} name The unique name for the font + * @param {string} [fontFamily] The name of the font family + * @param {FontPitch} [fontPitch] Indicator whether the font has a fixed or variable width + * @returns {FontFace} A new `FontFace` object with the specified properties + * or an existing font face, if one with the specified name exists + * @since 0.8.0 + */ + public create (name: string, fontFamily?: string, fontPitch?: FontPitch): FontFace { + let fontFace = this.fontFaces.get(name); + + if (fontFace !== undefined) { + return fontFace; + } + + fontFace = new FontFace(name, fontFamily, fontPitch); + this.fontFaces.set(name, fontFace); + + return fontFace; + } + + /** + * The `get()` method returns a specified element from a Map object. + * + * @example + * const fontFaceDeclarations = new FontFaceDeclarations(); + * fontFaceDeclarations.create('FreeSans'); + * fontFaceDeclarations.get('UnknownFont'); // undefined + * fontFaceDeclarations.get('FreeSans'); // FreeSans font + * + * @param {string} name The name of the requested font + * @returns {FontFace | undefined} The `FontFace` object associated with the specified name + * or `undefined` if there is no font with this name + * @since 0.8.0 + */ + public get (name: string): FontFace | undefined { + return this.fontFaces.get(name); + } + + /** + * The `getAll()` method returns a new `Array` object that contains the fonts of the document. + * + * @example + * const fontFaceDeclarations = new FontFaceDeclarations(); + * fontFaceDeclarations.create('FreeSans'); + * fontFaceDeclarations.create('Symbol'); + * fontFaceDeclarations.getAll(); // [FreeSans, Symbol] + * + * @returns {FontFace[]} A new `Array` object that contains the fonts of the document + * @since 0.8.0 + */ + public getAll (): FontFace[] { + return [...this.fontFaces.values()]; + } + + /** + * The `delete()` method removes the specified font from the font face declarations. + * + * @example + * var myMap = new Map(); + * const fontFaceDeclarations = new FontFaceDeclarations(); + * fontFaceDeclarations.create('FreeSans'); + * fontFaceDeclarations.create('Symbol'); + * fontFaceDeclarations.delete('FreeSans'); + * fontFaceDeclarations.get('FreeSans'); // undefined + * + * @param {string} name The name of the font to remove from the font face declarations + * @returns {Meta} The `Meta` object + * @since 0.8.0 + */ + public delete (name: string): FontFaceDeclarations { + this.fontFaces.delete(name); + + return this; + } +} diff --git a/src/api/office/TextDocument.spec.ts b/src/api/office/TextDocument.spec.ts index a8609d00..600304ef 100644 --- a/src/api/office/TextDocument.spec.ts +++ b/src/api/office/TextDocument.spec.ts @@ -1,8 +1,7 @@ import { readFile, unlink } from 'fs'; import { promisify } from 'util'; import { Meta } from '../meta/Meta'; -import { FontFace } from '../style'; -import { FontPitch } from '../style/FontPitch'; +import { FontFaceDeclarations } from './FontFaceDeclarations'; import { TextBody } from './TextBody'; import { TextDocument, XML_DECLARATION } from './TextDocument'; @@ -26,24 +25,10 @@ describe(TextDocument.name, () => { }); describe('font', () => { - it('return an empty list of fonts by default', () => { - const fonts = document.getFonts(); + it('return a font face declarations object', () => { + const fontFaceDeclarations = document.getFontFaceDeclarations(); - expect(fonts).toEqual([]); - }); - - it('return a font face object', () => { - const font = document.declareFont('Springfield', 'Springfield', FontPitch.Variable); - - expect(font).toBeInstanceOf(FontFace); - }); - - it('add font face to list of fonts', () => { - document.declareFont('Springfield', 'Springfield', FontPitch.Variable); - - const fonts = document.getFonts(); - - expect(fonts).toEqual([new FontFace('Springfield', 'Springfield', FontPitch.Variable)]); + expect(fontFaceDeclarations).toBeInstanceOf(FontFaceDeclarations); }); }); diff --git a/src/api/office/TextDocument.ts b/src/api/office/TextDocument.ts index 95c8f556..00d8a1f8 100644 --- a/src/api/office/TextDocument.ts +++ b/src/api/office/TextDocument.ts @@ -3,7 +3,7 @@ import { promisify } from 'util'; import { XMLSerializer } from 'xmldom'; import { TextDocumentWriter } from '../../xml/TextDocumentWriter'; import { Meta } from '../meta'; -import { FontFace, FontPitch } from '../style'; +import { FontFaceDeclarations } from './FontFaceDeclarations'; import { TextBody } from './TextBody'; export const XML_DECLARATION = '\n'; @@ -14,7 +14,7 @@ export const XML_DECLARATION = '\n'; * @example * const document = new TextDocument(); * document.getMeta().setCreator('Homer Simpson'); - * document.declareFont('FreeSans', 'FreeSans', FontPitch.Variable); + * document.getFontFaceDeclarations().create('FreeSans', 'FreeSans', FontPitch.Variable); * document.getBody().addHeading('My first document'); * document.saveFlat('/home/homer/document.fodt'); * @@ -22,12 +22,20 @@ export const XML_DECLARATION = '\n'; */ export class TextDocument { private meta: Meta; - private fonts: FontFace[]; + private fontFaceDeclarations: FontFaceDeclarations; private body: TextBody; + /** + * Creates a `TextDocument` instance that represents a OpenDocument text document. + * + * @example + * const document = new TextDocument(); + * + * @since 0.1.0 + */ public constructor () { this.meta = new Meta(); - this.fonts = []; + this.fontFaceDeclarations = new FontFaceDeclarations(); this.body = new TextBody(); } @@ -47,52 +55,29 @@ export class TextDocument { } /** - * The `declareFont` method creates a font face to be used in the document. - * - * **Note: There is no check whether the font exists. - * In order to be displayed properly, the font must be present on the target system.** + * The `getFonts()` method returns the font face declarations of the document. * * @example * new TextDocument() - * .declareFont('FreeSans', 'FreeSans', FontPitch.Variable); + * .getFontFaceDeclarations() + * .create('FreeSans', 'FreeSans', FontPitch.Variable); * - * @param {string} name The name of the font; this name must be set to a {@link ParagraphStyle} - * @param {string} fontFamily The name of the font family - * @param {FontPitch} fontPitch The pitch of the font - * @returns {FontFace} The declared `FontFace` object - * @since 0.4.0 + * @returns {FontFaceDeclarations} An object holding the font faces of the document + * @since 0.8.0 */ - public declareFont (name: string, fontFamily: string, fontPitch: FontPitch): FontFace { - const fontFace = new FontFace(name, fontFamily, fontPitch); - this.fonts.push(fontFace); - - return fontFace; - } - - /** - * The `getFonts()` method returns all font face declarations for the document. - * - * @example - * const document = new TextDocument(); - * document.declareFont('FreeSans', 'FreeSans', FontPitch.Variable); - * document.getFonts(); - * - * @returns {FontFace[]} A copy of the list of font face declarations for the document - * @since 0.7.0 - */ - public getFonts (): FontFace[] { - return Array.from(this.fonts); + public getFontFaceDeclarations (): FontFaceDeclarations { + return this.fontFaceDeclarations; } /** * The `getMeta()` method returns the metadata of the document. * * @example - * new TextDocument.getMeta() + * new TextDocument() + * .getMeta() * .setCreator('Homer Simpson'); * * @returns {Meta} An object holding the metadata of the document - * @see {@link Meta} * @since 0.6.0 */ public getMeta (): Meta { diff --git a/src/api/office/index.ts b/src/api/office/index.ts index ef057350..b191d112 100644 --- a/src/api/office/index.ts +++ b/src/api/office/index.ts @@ -1,2 +1,3 @@ +export { FontFaceDeclarations } from './FontFaceDeclarations'; export { TextBody } from './TextBody'; export { TextDocument } from './TextDocument'; diff --git a/src/api/style/FontFace.spec.ts b/src/api/style/FontFace.spec.ts index ed3cc055..4b599dde 100644 --- a/src/api/style/FontFace.spec.ts +++ b/src/api/style/FontFace.spec.ts @@ -1,4 +1,5 @@ import { FontFace } from './FontFace'; +import { FontFamilyGeneric } from './FontFamilyGeneric'; import { FontPitch } from './FontPitch'; describe(FontFace.name, () => { @@ -12,21 +13,123 @@ describe(FontFace.name, () => { fontFace = new FontFace(testName, testFamily, testFontPitch); }); + describe('name', () => { + it('return initial name', () => { + expect(fontFace.getName()).toBe(testName); + }); + }); + + describe('font charset', () => { + const testCharset = 'x-symbol'; + + it('return undefined by default', () => { + expect(fontFace.getCharset()).toBeUndefined(); + }); + + it('return previous set charset', () => { + fontFace.setCharset(testCharset); + + expect(fontFace.getCharset()).toBe(testCharset); + }); + + it('return undefined if undefined is set', () => { + fontFace.setCharset(testCharset); + fontFace.setCharset(undefined); + + expect(fontFace.getCharset()).toBeUndefined(); + }); + + it('ignore invalid input', () => { + fontFace.setCharset(testCharset); + fontFace.setCharset('2342'); + + expect(fontFace.getCharset()).toBe(testCharset); + }); + }); + describe('font family', () => { it('return initial font family', () => { expect(fontFace.getFontFamily()).toBe(testFamily); }); + + it('return undefined if initial family is not set', () => { + fontFace = new FontFace(testName); + + expect(fontFace.getFontFamily()).toBeUndefined(); + }); + + it('return previous set charset', () => { + const testFamily2 = 'someOtherFontFamily'; + + fontFace.setFontFamily(testFamily2); + + expect(fontFace.getFontFamily()).toBe(testFamily2); + }); + + it('return undefined if undefined is set', () => { + fontFace.setFontFamily(undefined); + + expect(fontFace.getFontFamily()).toBeUndefined(); + }); + + it('ignore invalid input', () => { + fontFace.setFontFamily(null as any); + + expect(fontFace.getFontFamily()).toBe(testFamily); + }); + }); + + describe('font family generic', () => { + const testFamilyGeneric = FontFamilyGeneric.System; + + it('return undefined by default', () => { + expect(fontFace.getFontFamilyGeneric()).toBeUndefined(); + }); + + it('return previous set font family generic', () => { + fontFace.setFontFamilyGeneric(testFamilyGeneric); + + expect(fontFace.getFontFamilyGeneric()).toBe(testFamilyGeneric); + }); + + it('return undefined if undefined is set', () => { + fontFace.setFontFamilyGeneric(testFamilyGeneric); + fontFace.setFontFamilyGeneric(undefined); + + expect(fontFace.getFontFamilyGeneric()).toBeUndefined(); + }); + + it('ignore invalid input', () => { + fontFace.setFontFamilyGeneric(testFamilyGeneric); + fontFace.setFontFamilyGeneric(null as any); + + expect(fontFace.getFontFamilyGeneric()).toBe(testFamilyGeneric); + }); }); describe('font pitch', () => { it('return initial font pitch', () => { expect(fontFace.getFontPitch()).toBe(testFontPitch); }); - }); - describe('name', () => { - it('return initial name', () => { - expect(fontFace.getName()).toBe(testName); + it('return undefined if initial family is not set', () => { + fontFace = new FontFace(testName); + + expect(fontFace.getFontPitch()).toBeUndefined(); + }); + + it('return previous set charset', () => { + const testFontPitch2 = FontPitch.Fixed; + + fontFace.setFontPitch(testFontPitch2); + + expect(fontFace.getFontPitch()).toBe(testFontPitch2); + }); + + it('return undefined if undefined is set', () => { + fontFace.setFontPitch(undefined); + + expect(fontFace.getFontPitch()).toBeUndefined(); }); }); }); diff --git a/src/api/style/FontFace.ts b/src/api/style/FontFace.ts index 6233e828..0e287a12 100644 --- a/src/api/style/FontFace.ts +++ b/src/api/style/FontFace.ts @@ -1,24 +1,205 @@ +import { FontFamilyGeneric } from './FontFamilyGeneric'; import { FontPitch } from './FontPitch'; +/** + * This class represents a font face declaration. + * + * It is used to describe the characteristics of a font which is used in the document. + * The unique name of a font can be used inside styles to select a font face declaration. + * + * @example + * const font = document.getFontFaceDeclarations().create('FreeSans', 'FreeSans', FontPitch.Variable); + * font.setFontFamilyGeneric(FontFamilyGeneric.Swiss); + * + * @since 0.8.0 + */ export class FontFace { private name: string; - private fontFamily: string; - private fontPitch: FontPitch; + private fontCharset: string | undefined; + private fontFamily: string | undefined; + private fontFamilyGeneric: FontFamilyGeneric | undefined; + private fontPitch: FontPitch | undefined; - public constructor (name: string, fontFamily: string, fontPitch: FontPitch) { + /** + * Creates a `FontFace` instance that represents the characteristics of a font. + * + * @example + * const font = new FontFace('FreeSans', 'FreeSans', FontPitch.Variable); + * const font = new FontFace('FreeSans', 'FreeSans'); + * const font = new FontFace('FreeSans'); + * + * @param {string} name The unique name for the font + * @param {string} [fontFamily] The name of the font family + * @param {FontPitch} [fontPitch] Indicator whether the font has a fixed or variable width + * + * @since 0.8.0 + */ + public constructor (name: string, fontFamily?: string, fontPitch?: FontPitch) { this.name = name; this.fontFamily = fontFamily; this.fontPitch = fontPitch; } - public getFontFamily (): string { + /** + * The `setCharset()` method sets whether the font defines glyphs according to the semantics of Unicode or not. + * + * The value can be `x-symbol` or a character encoding. + * + * If an illegal value is provided, the value will be ignored. + * + * @example + * const font = new FontFace('OpenSymbol', 'OpenSymbol', FontPitch.Variable); + * font.setCharset('x-symbol'); // 'x-symbol' + * font.setCharset('23'); // 'x-symbol' + * font.setCharset(undefined); // undefined + * + * @param {string | undefined} fontCharset The charset of the font or `undefined` to unset the charset + * @returns {FontFace} The `FontFace` object + * @since 0.8.0 + */ + public setCharset (fontCharset: string | undefined): FontFace { + if (fontCharset === undefined || /^[A-Za-z][A-Za-z0-9._\-]*$/.test(fontCharset) === true) { + this.fontCharset = fontCharset; + } + + return this; + } + + /** + * The `getCharset()` method returns whether the font defines glyphs according to the semantics of Unicode or not. + * + * @example + * const font = new FontFace('OpenSymbol', 'OpenSymbol', FontPitch.Variable); + * font.getCharset(); // undefined + * font.setCharset('x-symbol'); + * font.getCharset(); // 'x-symbol' + * + * @returns {string | undefined} The charset of the font or `undefined` if the charset is not set + * @since 0.8.0 + */ + public getCharset (): string | undefined { + return this.fontCharset; + } + + /** + * The `setFontFamily()` method sets the font family which is to be used to render the text. + * + * @example + * const font = new FontFace('OpenSymbol'); + * font.setFontFamily('OpenSymbol'); // 'OpenSymbol' + * font.setFontFamily(undefined); // undefined + * + * @param {string | undefined} fontFamily The font family of the font or `undefined` to unset the font family + * @returns {FontFace} The `FontFace` object + * @since 0.8.0 + */ + public setFontFamily (fontFamily: string | undefined): FontFace { + /* tslint:disable-next-line:strict-type-predicates */ + if (fontFamily === undefined || typeof fontFamily === 'string') { + this.fontFamily = fontFamily; + } + + return this; + } + + /** + * The `getFontFamily()` method returns the font family which is to be used to render the text. + * + * @example + * const font = new FontFace('OpenSymbol'); + * font.setFontFamily('OpenSymbol'); // 'OpenSymbol' + * font.setFontFamily(undefined); // undefined + * + * @returns {string | undefined} The font family of the font or `undefined` if the font family is not set + * @since 0.8.0 + */ + public getFontFamily (): string | undefined { return this.fontFamily; } - public getFontPitch (): FontPitch { + /** + * The `setFontFamilyGeneric()` method sets the generic font family name of the font. + * + * @example + * const font = new FontFace('OpenSymbol'); + * font.setFontFamilyGeneric(FontFamilyGeneric.System); // 'system' + * font.setFontFamilyGeneric(undefined); // undefined + * + * @param {FontFamilyGeneric | undefined} fontFamilyGeneric The generic font family name + * or `undefined` to unset the generic font family name + * @returns {FontFace} The `FontFace` object + * @since 0.8.0 + */ + public setFontFamilyGeneric (fontFamilyGeneric: FontFamilyGeneric | undefined): FontFace { + /* tslint:disable-next-line:strict-type-predicates */ + if (fontFamilyGeneric === undefined || typeof fontFamilyGeneric === 'string') { + this.fontFamilyGeneric = fontFamilyGeneric; + } + + return this; + } + + /** + * The `getFontFamilyGeneric()` method returns the generic font family name of the font. + * + * @example + * const font = new FontFace('OpenSymbol'); + * font.getFontFamilyGeneric(); // undefined + * font.setFontFamilyGeneric(FontFamilyGeneric.System); + * font.getFontFamilyGeneric(); // 'system' + * + * @returns {string | undefined} The generic font family name of the font + * or `undefined` if the generic font family name is not set + * @since 0.8.0 + */ + public getFontFamilyGeneric (): FontFamilyGeneric | undefined { + return this.fontFamilyGeneric; + } + + /** + * The `setFontPitch()` method sets whether the font has a fixed or variable width. + * + * @example + * const font = new FontFace('OpenSymbol'); + * font.setFontPitch(FontPitch.Variable); // variable + * font.setFontPitch(undefined); // undefined + * + * @param {FontPitch | undefined} fontPitch The pitch of the font or `undefined` to unset the font pitch + * @returns {FontFace} The `FontFace` object + * @since 0.8.0 + */ + public setFontPitch (fontPitch: FontPitch | undefined): FontFace { + this.fontPitch = fontPitch; + + return this; + } + + /** + * The `getFontPitch()` method returns whether the font has a fixed or variable width. + * + * @example + * const font = new FontFace('OpenSymbol'); + * font.getFontPitch(); // undefined + * font.setFontPitch(FontPitch.Variable); + * font.getFontPitch(); // variable + * + * @returns {string | undefined} The pitch of the font or `undefined` if the font pitch is not set + * @since 0.8.0 + */ + public getFontPitch (): FontPitch | undefined { return this.fontPitch; } + /** + * The `getName()` method returns the unique name of the font. + * + * @example + * const font = new FontFace('FreeSans'); + * font.getName(); // 'FreeSans' + * + * @returns {string} A string that identifies the font in this document + * @since 0.8.0 + */ public getName (): string { return this.name; } diff --git a/src/api/style/FontFamilyGeneric.ts b/src/api/style/FontFamilyGeneric.ts new file mode 100644 index 00000000..b7ee76b6 --- /dev/null +++ b/src/api/style/FontFamilyGeneric.ts @@ -0,0 +1,14 @@ +export enum FontFamilyGeneric { + /** the family of decorative fonts */ + Decorative = 'decorative', + /** the family of modern fonts */ + Modern = 'modern', + /** the family roman fonts (with serifs) */ + Roman = 'roman', + /** the family of script fonts */ + Script = 'script', + /** the family roman fonts (without serifs) */ + Swiss = 'swiss', + /** the family system fonts */ + System = 'system' +} diff --git a/src/api/style/index.ts b/src/api/style/index.ts index 112fd5d5..50d08695 100644 --- a/src/api/style/index.ts +++ b/src/api/style/index.ts @@ -1,2 +1,3 @@ export { FontFace } from './FontFace'; +export { FontFamilyGeneric } from './FontFamilyGeneric'; export { FontPitch } from './FontPitch'; diff --git a/src/api/text/Hyperlink.ts b/src/api/text/Hyperlink.ts index 4ba5c6e8..f92160e4 100644 --- a/src/api/text/Hyperlink.ts +++ b/src/api/text/Hyperlink.ts @@ -39,6 +39,7 @@ export class Hyperlink extends OdfTextElement { * @since 0.3.0 */ public setURI (uri: string): Hyperlink { + /* tslint:disable-next-line:strict-type-predicates */ if (typeof uri === 'string' && uri.trim().length > 0) { this.uri = uri; } diff --git a/src/index.ts b/src/index.ts index b1648752..0bf44924 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,11 +5,13 @@ export { Image } from './api/draw/Image'; export { Meta } from './api/meta/Meta'; // office +export { FontFaceDeclarations } from './api/office/FontFaceDeclarations'; export { TextBody } from './api/office/TextBody'; export { TextDocument } from './api/office/TextDocument'; // style export { FontFace } from './api/style/FontFace'; +export { FontFamilyGeneric } from './api/style/FontFamilyGeneric'; export { FontPitch } from './api/style/FontPitch'; // style (legacy) diff --git a/src/xml/OdfTextElementWriter.ts b/src/xml/OdfTextElementWriter.ts index 9a7f0e0f..1b342a5b 100644 --- a/src/xml/OdfTextElementWriter.ts +++ b/src/xml/OdfTextElementWriter.ts @@ -10,6 +10,7 @@ export class OdfTextElementWriter { */ public write (odfText: OdfTextElement, document: Document, parent: Element): void { const text = odfText.getText(); + /* tslint:disable-next-line:strict-type-predicates */ if (text === undefined || text === '') { return; } diff --git a/src/xml/TextDocumentWriter.spec.ts b/src/xml/TextDocumentWriter.spec.ts index eeab5c23..8a638a3d 100644 --- a/src/xml/TextDocumentWriter.spec.ts +++ b/src/xml/TextDocumentWriter.spec.ts @@ -1,9 +1,9 @@ import { XMLSerializer } from 'xmldom'; import { TextDocument } from '../api/office'; -import { FontPitch } from '../api/style'; import { TextDocumentWriter } from './TextDocumentWriter'; jest.mock('./meta/MetaWriter'); +jest.mock('./office/FontFaceDeclarationsWriter'); jest.mock('./DomVisitor'); describe(TextDocumentWriter.name, () => { @@ -56,26 +56,4 @@ describe(TextDocumentWriter.name, () => { expect(documentAsString).toMatch(/xmlns:xlink="http:\/\/www.w3.org\/1999\/xlink"/); }); }); - - describe('font face declarations', () => { - it('add font declaration to document', () => { - textDocument.declareFont('Springfield', 'Springfield', FontPitch.Variable); - - const document = documentWriter.write(textDocument); - const documentAsString = new XMLSerializer().serializeToString(document); - - /* tslint:disable-next-line:max-line-length */ - expect(documentAsString).toMatch(/<\/office:font-face-decls>/); - }); - - it('add font declaration to document and wrap font family if it contains spaces', () => { - textDocument.declareFont('Homer Simpson', 'Homer Simpson', FontPitch.Fixed); - - const document = documentWriter.write(textDocument); - const documentAsString = new XMLSerializer().serializeToString(document); - - /* tslint:disable-next-line:max-line-length */ - expect(documentAsString).toMatch(/<\/office:font-face-decls>/); - }); - }); }); diff --git a/src/xml/TextDocumentWriter.ts b/src/xml/TextDocumentWriter.ts index 6f5875ac..f850a528 100644 --- a/src/xml/TextDocumentWriter.ts +++ b/src/xml/TextDocumentWriter.ts @@ -1,10 +1,10 @@ import { DOMImplementation } from 'xmldom'; import { TextDocument } from '../api/office/TextDocument'; -import { FontFace } from '../api/style'; import { DomVisitor } from './DomVisitor'; import { MetaWriter } from './meta/MetaWriter'; import { OdfAttributeName } from './OdfAttributeName'; import { OdfElementName } from './OdfElementName'; +import { FontFaceDeclarationsWriter } from './office/FontFaceDeclarationsWriter'; const OFFICE_VERSION = '1.2'; @@ -35,7 +35,7 @@ export class TextDocumentWriter { new MetaWriter().write(document, root, textDocument.getMeta()); - this.setFontFaceElements(textDocument.getFonts(), document, root); + new FontFaceDeclarationsWriter().write(textDocument.getFontFaceDeclarations(), document, root); new DomVisitor().visit(textDocument.getBody(), document, root); @@ -58,32 +58,4 @@ export class TextDocumentWriter { root.setAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'); root.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); } - - /** - * Adds the `font-face-decls` element and the font faces if any font needs to be declared. - * - * @param {Document} document The XML document - * @param {Element} root The element which will be used as parent - * @private - */ - private setFontFaceElements (fonts: FontFace[], document: Document, root: Element): void { - if (fonts.length === 0) { - return; - } - - const fontFaceDeclsElement = document.createElement(OdfElementName.OfficeFontFaceDeclarations); - root.appendChild(fontFaceDeclsElement); - - fonts.forEach((font: FontFace) => { - const fontFaceElement = document.createElement(OdfElementName.StyleFontFace); - fontFaceDeclsElement.appendChild(fontFaceElement); - fontFaceElement.setAttribute('style:name', font.getName()); - - const fontFamily = font.getFontFamily(); - const encodedFontFamily = fontFamily.includes(' ') === true ? `'${fontFamily}'` : fontFamily; - fontFaceElement.setAttribute('svg:font-family', encodedFontFamily); - - fontFaceElement.setAttribute('style:font-pitch', font.getFontPitch()); - }); - } } diff --git a/src/xml/office/FontFaceDeclarationsWriter.spec.ts b/src/xml/office/FontFaceDeclarationsWriter.spec.ts new file mode 100644 index 00000000..203c9e83 --- /dev/null +++ b/src/xml/office/FontFaceDeclarationsWriter.spec.ts @@ -0,0 +1,50 @@ +import { DOMImplementation, XMLSerializer } from 'xmldom'; +import { FontFaceDeclarations } from '../../api/office'; +import { FontPitch } from '../../api/style'; +import { OdfElementName } from '../OdfElementName'; +import { FontFaceDeclarationsWriter } from './FontFaceDeclarationsWriter'; + +describe(FontFaceDeclarationsWriter.name, () => { + describe('#write', () => { + let fontFaceDeclarationsWriter: FontFaceDeclarationsWriter; + let testDocument: Document; + let testRoot: Element; + let fontFaceDeclarations: FontFaceDeclarations; + + beforeEach(() => { + testDocument = new DOMImplementation().createDocument('someNameSpace', OdfElementName.OfficeDocument, null); + testRoot = testDocument.firstChild as Element; + fontFaceDeclarations = new FontFaceDeclarations(); + + fontFaceDeclarationsWriter = new FontFaceDeclarationsWriter(); + }); + + it('do nothing if no font face is defined', () => { + fontFaceDeclarationsWriter.write(fontFaceDeclarations, testDocument, testRoot); + const documentAsString = new XMLSerializer().serializeToString(testDocument); + + /* tslint:disable-next-line:max-line-length */ + expect(documentAsString).not.toMatch(/office:font-face-decls/); + }); + + it('add font declaration to document', () => { + fontFaceDeclarations.create('Springfield', 'Springfield', FontPitch.Variable); + + fontFaceDeclarationsWriter.write(fontFaceDeclarations, testDocument, testRoot); + const documentAsString = new XMLSerializer().serializeToString(testDocument); + + /* tslint:disable-next-line:max-line-length */ + expect(documentAsString).toMatch(/<\/office:font-face-decls>/); + }); + + it('add font declaration to document and wrap font family if it contains spaces', () => { + fontFaceDeclarations.create('Homer Simpson', 'Homer Simpson', FontPitch.Fixed); + + fontFaceDeclarationsWriter.write(fontFaceDeclarations, testDocument, testRoot); + const documentAsString = new XMLSerializer().serializeToString(testDocument); + + /* tslint:disable-next-line:max-line-length */ + expect(documentAsString).toMatch(/<\/office:font-face-decls>/); + }); + }); +}); diff --git a/src/xml/office/FontFaceDeclarationsWriter.ts b/src/xml/office/FontFaceDeclarationsWriter.ts new file mode 100644 index 00000000..176f5b06 --- /dev/null +++ b/src/xml/office/FontFaceDeclarationsWriter.ts @@ -0,0 +1,53 @@ +import { FontFaceDeclarations } from '../../api/office'; +import { FontFace } from '../../api/style'; +import { OdfElementName } from '../OdfElementName'; + +/** + * Transforms a {@link FontFaceDeclarations} object into ODF conform XML + * + * @since 0.8.0 + */ +export class FontFaceDeclarationsWriter { + /** + * Transforms the given {@link FontFaceDeclarations} into Open Document Format. + * + * @param {Document} document The XML document + * @param {Element} parent The parent node in the DOM + * @param {FontFaceDeclarations} fontFaceDeclarations The font face declarations to serialize + * @since 0.7.0 + */ + public write (fontFaceDeclarations: FontFaceDeclarations, document: Document, root: Element): void { + const fonts = fontFaceDeclarations.getAll(); + + if (fonts.length === 0) { + return; + } + + const fontFaceDeclsElement = document.createElement(OdfElementName.OfficeFontFaceDeclarations); + root.appendChild(fontFaceDeclsElement); + + fonts.forEach((font: FontFace) => { + this.visitFontFace(font, document, fontFaceDeclsElement); + }); + } + + private visitFontFace (font: FontFace, document: Document, parent: Element): Element { + const fontFaceElement = document.createElement(OdfElementName.StyleFontFace); + parent.appendChild(fontFaceElement); + + fontFaceElement.setAttribute('style:name', font.getName()); + + const fontFamily = font.getFontFamily(); + if (fontFamily !== undefined) { + const encodedFontFamily = fontFamily.includes(' ') === true ? `'${fontFamily}'` : fontFamily; + fontFaceElement.setAttribute('svg:font-family', encodedFontFamily); + } + + const fontPitch = font.getFontPitch(); + if (fontPitch !== undefined) { + fontFaceElement.setAttribute('style:font-pitch', fontPitch); + } + + return fontFaceElement; + } +} diff --git a/test/integration.spec.ts b/test/integration.spec.ts index 791ee0d0..b2dbbbde 100644 --- a/test/integration.spec.ts +++ b/test/integration.spec.ts @@ -99,7 +99,7 @@ xdescribe('integration', () => { }); it('font name', () => { - document.declareFont('Open Sans', 'Open Sans', FontPitch.Variable); + document.getFontFaceDeclarations().create('Open Sans', 'Open Sans', FontPitch.Variable); const paragraph = body.addParagraph('Open Sans'); paragraph.setStyle(new ParagraphStyle()); diff --git a/tslint.json b/tslint.json index 4d371633..a15e8d4b 100644 --- a/tslint.json +++ b/tslint.json @@ -2,6 +2,7 @@ "defaultSeverity": "error", "extends": "tslint-config-standard", "rules": { - "semicolon": [true, "always"] + "semicolon": [true, "always"], + "max-line-length": { "options": 120 } } } \ No newline at end of file