diff --git a/demo/2-declaritive-styles.ts b/demo/2-declaritive-styles.ts index a514e7eef6..04502264a3 100644 --- a/demo/2-declaritive-styles.ts +++ b/demo/2-declaritive-styles.ts @@ -1,48 +1,89 @@ // Example on how to customise the look at feel using Styles // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HeadingLevel, Packer, Paragraph, TextRun } from "../build"; +import { Document, HeadingLevel, Packer, Paragraph, Styles, TextRun, UnderlineType } from "../build"; const doc = new Document({ creator: "Clippy", title: "Sample Document", description: "A brief example of using docx", + styles: new Styles({ + paragraphStyles: [ + { + id: "Heading1", + name: "Heading 1", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + size: 28, + bold: true, + italics: true, + }, + paragraph: { + spacing: { + after: 120, + }, + }, + }, + { + id: "Heading2", + name: "Heading 2", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + size: 26, + bold: true, + underline: { + type: UnderlineType.DOUBLE, + color: "FF0000", + }, + }, + paragraph: { + spacing: { + before: 240, + after: 120, + }, + }, + }, + { + id: "aside", + name: "Aside", + basedOn: "Normal", + next: "Normal", + run: { + color: "999999", + italics: true, + }, + paragraph: { + indent: { + left: 720, + }, + spacing: { + line: 276, + }, + }, + }, + { + id: "wellSpaced", + name: "Well Spaced", + basedOn: "Normal", + quickFormat: true, + paragraph: { + spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }, + }, + }, + { + id: "ListParagraph", + name: "List Paragraph", + basedOn: "Normal", + quickFormat: true, + }, + ], + }), }); -doc.Styles.createParagraphStyle("Heading1", "Heading 1") - .basedOn("Normal") - .next("Normal") - .quickFormat() - .size(28) - .bold() - .italics() - .spacing({ after: 120 }); - -doc.Styles.createParagraphStyle("Heading2", "Heading 2") - .basedOn("Normal") - .next("Normal") - .quickFormat() - .size(26) - .bold() - .underline("double", "FF0000") - .spacing({ before: 240, after: 120 }); - -doc.Styles.createParagraphStyle("aside", "Aside") - .basedOn("Normal") - .next("Normal") - .color("999999") - .italics() - .indent({ left: 720 }) - .spacing({ line: 276 }); - -doc.Styles.createParagraphStyle("wellSpaced", "Well Spaced") - .basedOn("Normal") - .spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }); - -doc.Styles.createParagraphStyle("ListParagraph", "List Paragraph") - .quickFormat() - .basedOn("Normal"); - const numberedAbstract = doc.Numbering.createAbstractNumbering(); numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left"); diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index d271299d9c..7d3e447523 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,5 +1,6 @@ import { XmlComponent } from "file/xml-components"; import { DocumentAttributes } from "../document/document-attributes"; +import { Styles } from "../styles"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; export interface IPropertiesOptions { @@ -11,6 +12,7 @@ export interface IPropertiesOptions { readonly lastModifiedBy?: string; readonly revision?: string; readonly externalStyles?: string; + readonly styles?: Styles; } export class CoreProperties extends XmlComponent { diff --git a/src/file/file.ts b/src/file/file.ts index c0adf21e30..f68760328d 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -46,8 +46,6 @@ export interface ISectionOptions { export class File { // tslint:disable-next-line:readonly-keyword private currentRelationshipId: number = 1; - // tslint:disable-next-line:readonly-keyword - private styles: Styles; private readonly document: Document; private readonly headers: IDocumentHeader[] = []; @@ -61,6 +59,7 @@ export class File { private readonly settings: Settings; private readonly contentTypes: ContentTypes; private readonly appProperties: AppProperties; + private readonly styles: Styles; constructor( options: IPropertiesOptions = { @@ -97,6 +96,8 @@ export class File { } else if (options.externalStyles) { const stylesFactory = new ExternalStylesFactory(); this.styles = stylesFactory.newInstance(options.externalStyles); + } else if (options.styles) { + this.styles = options.styles; } else { const stylesFactory = new DefaultStylesFactory(); this.styles = stylesFactory.newInstance(); @@ -277,10 +278,6 @@ export class File { return this.styles; } - public set Styles(styles: Styles) { - this.styles = styles; - } - public get CoreProperties(): CoreProperties { return this.coreProperties; } diff --git a/src/file/numbering/level.ts b/src/file/numbering/level.ts index 26d0849fc4..b0fa183f53 100644 --- a/src/file/numbering/level.ts +++ b/src/file/numbering/level.ts @@ -10,12 +10,12 @@ import { LeftTabStop, RightTabStop, Spacing, - TabStopPosition, ThematicBreak, } from "../paragraph/formatting"; import { ParagraphProperties } from "../paragraph/properties"; import * as formatting from "../paragraph/run/formatting"; import { RunProperties } from "../paragraph/run/properties"; +import { UnderlineType } from "../paragraph/run/underline"; interface ILevelAttributesProperties { readonly ilvl?: number; @@ -185,7 +185,7 @@ export class LevelBase extends XmlComponent { return this; } - public underline(underlineType?: string, color?: string): Level { + public underline(underlineType?: UnderlineType, color?: string): Level { this.addRunProperty(new formatting.Underline(underlineType, color)); return this; } diff --git a/src/file/numbering/numbering.spec.ts b/src/file/numbering/numbering.spec.ts index cbf7203a06..d5f2f9e168 100644 --- a/src/file/numbering/numbering.spec.ts +++ b/src/file/numbering/numbering.spec.ts @@ -9,6 +9,7 @@ import { Numbering } from "./numbering"; import { EMPTY_OBJECT } from "file/xml-components"; import { TabStopPosition } from "../paragraph"; +import { UnderlineType } from "../paragraph/run/underline"; describe("Numbering", () => { let numbering: Numbering; @@ -356,7 +357,7 @@ describe("AbstractNumbering", () => { it("should set the style if given", () => { const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline("double"); + const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE); const tree = new Formatter().format(level); expect(tree["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }], @@ -365,7 +366,7 @@ describe("AbstractNumbering", () => { it("should set the style and color if given", () => { const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline("double", "005599"); + const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline(UnderlineType.DOUBLE, "005599"); const tree = new Formatter().format(level); expect(tree["w:lvl"]).to.include({ "w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }], diff --git a/src/file/paragraph/run/index.ts b/src/file/paragraph/run/index.ts index 81914b7b15..353e794c06 100644 --- a/src/file/paragraph/run/index.ts +++ b/src/file/paragraph/run/index.ts @@ -4,3 +4,4 @@ export * from "./symbol-run"; export * from "./picture-run"; export * from "./run-fonts"; export * from "./sequential-identifier"; +export * from "./underline"; diff --git a/src/file/paragraph/run/underline.spec.ts b/src/file/paragraph/run/underline.spec.ts index b593b816dd..77f7819e7b 100644 --- a/src/file/paragraph/run/underline.spec.ts +++ b/src/file/paragraph/run/underline.spec.ts @@ -27,7 +27,7 @@ describe("Underline", () => { }); it("should use the given style type and color", () => { - const underline = new u.Underline("double", "FF00CC"); + const underline = new u.Underline(u.UnderlineType.DOUBLE, "FF00CC"); const tree = new Formatter().format(underline); expect(tree).to.deep.equal({ "w:u": { _attr: { "w:val": "double", "w:color": "FF00CC" } }, diff --git a/src/file/paragraph/run/underline.ts b/src/file/paragraph/run/underline.ts index 7d4a1c98d1..9cdb7b25a1 100644 --- a/src/file/paragraph/run/underline.ts +++ b/src/file/paragraph/run/underline.ts @@ -33,7 +33,7 @@ export abstract class BaseUnderline extends XmlComponent { } export class Underline extends BaseUnderline { - constructor(underlineType: string = "single", color?: string) { + constructor(underlineType: UnderlineType = UnderlineType.SINGLE, color?: string) { super(underlineType, color); } } diff --git a/src/file/styles/external-styles-factory.ts b/src/file/styles/external-styles-factory.ts index cbfa4a3c90..839041d690 100644 --- a/src/file/styles/external-styles-factory.ts +++ b/src/file/styles/external-styles-factory.ts @@ -38,11 +38,13 @@ export class ExternalStylesFactory { throw new Error("can not find styles element"); } - const importedStyle = new Styles(new ImportedRootElementAttributes(stylesXmlElement.attributes)); const stylesElements = stylesXmlElement.elements || []; - for (const childElm of stylesElements) { - importedStyle.push(convertToXmlComponent(childElm) as ImportedXmlComponent); - } + + const importedStyle = new Styles({ + initialStyles: new ImportedRootElementAttributes(stylesXmlElement.attributes), + importedStyles: stylesElements.map((childElm) => convertToXmlComponent(childElm) as ImportedXmlComponent), + }); + return importedStyle; } } diff --git a/src/file/styles/factory.ts b/src/file/styles/factory.ts index 800d2ca9ce..d43f46b732 100644 --- a/src/file/styles/factory.ts +++ b/src/file/styles/factory.ts @@ -1,7 +1,7 @@ import { DocumentAttributes } from "../document/document-attributes"; -import { Color, Italics, Size } from "../paragraph/run/formatting"; -import { Styles } from "./"; +import { Styles } from "./styles"; +import { DocumentDefaults } from "./defaults"; import { FootnoteReferenceStyle, FootnoteText, @@ -27,57 +27,58 @@ export class DefaultStylesFactory { w15: "http://schemas.microsoft.com/office/word/2012/wordml", Ignorable: "w14 w15", }); - const styles = new Styles(documentAttributes); + const styles = new Styles({ + initialStyles: documentAttributes, + importedStyles: [ + new DocumentDefaults(), + new TitleStyle({ + run: { + size: 56, + }, + }), + new Heading1Style({ + run: { + color: "2E74B5", + size: 32, + }, + }), + new Heading2Style({ + run: { + color: "2E74B5", + size: 26, + }, + }), + new Heading3Style({ + run: { + color: "1F4D78", + size: 24, + }, + }), + new Heading4Style({ + run: { + color: "2E74B5", + italics: true, + }, + }), + new Heading5Style({ + run: { + color: "2E74B5", + }, + }), + new Heading6Style({ + run: { + color: "1F4D78", + }, + }), + new ListParagraph({}), + new HyperlinkStyle({}), + new FootnoteReferenceStyle({}), + new FootnoteText({}), + new FootnoteTextChar({}), + ], + }); styles.createDocumentDefaults(); - const titleStyle = new TitleStyle(); - titleStyle.addRunProperty(new Size(56)); - styles.push(titleStyle); - - const heading1Style = new Heading1Style(); - heading1Style.addRunProperty(new Color("2E74B5")); - heading1Style.addRunProperty(new Size(32)); - styles.push(heading1Style); - - const heading2Style = new Heading2Style(); - heading2Style.addRunProperty(new Color("2E74B5")); - heading2Style.addRunProperty(new Size(26)); - styles.push(heading2Style); - - const heading3Style = new Heading3Style(); - heading3Style.addRunProperty(new Color("1F4D78")); - heading3Style.addRunProperty(new Size(24)); - styles.push(heading3Style); - - const heading4Style = new Heading4Style(); - heading4Style.addRunProperty(new Color("2E74B5")); - heading4Style.addRunProperty(new Italics()); - styles.push(heading4Style); - - const heading5Style = new Heading5Style(); - heading5Style.addRunProperty(new Color("2E74B5")); - styles.push(heading5Style); - - const heading6Style = new Heading6Style(); - heading6Style.addRunProperty(new Color("1F4D78")); - styles.push(heading6Style); - - const listParagraph = new ListParagraph(); - // listParagraph.addParagraphProperty(); - styles.push(listParagraph); - - const hyperLinkStyle = new HyperlinkStyle(); - styles.push(hyperLinkStyle); - - const footnoteReferenceStyle = new FootnoteReferenceStyle(); - styles.push(footnoteReferenceStyle); - - const footnoteTextStyle = new FootnoteText(); - styles.push(footnoteTextStyle); - - const footnoteTextCharStyle = new FootnoteTextChar(); - styles.push(footnoteTextCharStyle); - return styles; } } diff --git a/src/file/styles/style/character-style.spec.ts b/src/file/styles/style/character-style.spec.ts index c7594f3829..fa12e5e512 100644 --- a/src/file/styles/style/character-style.spec.ts +++ b/src/file/styles/style/character-style.spec.ts @@ -1,15 +1,16 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { UnderlineType } from "file/paragraph/run/underline"; +import { ShadingType } from "file/table"; +import { EMPTY_OBJECT } from "file/xml-components"; import { CharacterStyle } from "./character-style"; -import { EMPTY_OBJECT } from "file/xml-components"; - describe("CharacterStyle", () => { describe("#constructor", () => { it("should set the style type to character and use the given style id", () => { - const style = new CharacterStyle("myStyleId"); + const style = new CharacterStyle({ id: "myStyleId" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -17,7 +18,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -29,7 +30,10 @@ describe("CharacterStyle", () => { }); it("should set the name of the style, if given", () => { - const style = new CharacterStyle("myStyleId", "Style Name"); + const style = new CharacterStyle({ + id: "myStyleId", + name: "Style Name", + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -38,7 +42,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -52,7 +56,7 @@ describe("CharacterStyle", () => { describe("formatting methods: style attributes", () => { it("#basedOn", () => { - const style = new CharacterStyle("myStyleId").basedOn("otherId"); + const style = new CharacterStyle({ id: "myStyleId", basedOn: "otherId" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -60,7 +64,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -75,7 +79,12 @@ describe("CharacterStyle", () => { describe("formatting methods: run properties", () => { it("#size", () => { - const style = new CharacterStyle("myStyleId").size(24); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + size: 24, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -86,7 +95,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -99,7 +108,12 @@ describe("CharacterStyle", () => { describe("#underline", () => { it("should set underline to 'single' if no arguments are given", () => { - const style = new CharacterStyle("myStyleId").underline(); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + underline: {}, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -110,7 +124,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -122,7 +136,14 @@ describe("CharacterStyle", () => { }); it("should set the style if given", () => { - const style = new CharacterStyle("myStyleId").underline("double"); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + underline: { + type: UnderlineType.DOUBLE, + }, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -133,7 +154,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -145,7 +166,15 @@ describe("CharacterStyle", () => { }); it("should set the style and color if given", () => { - const style = new CharacterStyle("myStyleId").underline("double", "005599"); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + underline: { + type: UnderlineType.DOUBLE, + color: "005599", + }, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -156,7 +185,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -169,7 +198,12 @@ describe("CharacterStyle", () => { }); it("#superScript", () => { - const style = new CharacterStyle("myStyleId").superScript(); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + superScript: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -188,7 +222,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -200,7 +234,12 @@ describe("CharacterStyle", () => { }); it("#color", () => { - const style = new CharacterStyle("myStyleId").color("123456"); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + color: "123456", + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -211,7 +250,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -223,7 +262,12 @@ describe("CharacterStyle", () => { }); it("#bold", () => { - const style = new CharacterStyle("myStyleId").bold(); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + bold: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -234,7 +278,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -246,7 +290,12 @@ describe("CharacterStyle", () => { }); it("#italics", () => { - const style = new CharacterStyle("myStyleId").italics(); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + italics: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -257,7 +306,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -269,7 +318,7 @@ describe("CharacterStyle", () => { }); it("#link", () => { - const style = new CharacterStyle("myStyleId").link("MyLink"); + const style = new CharacterStyle({ id: "myStyleId", link: "MyLink" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -277,7 +326,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -290,7 +339,7 @@ describe("CharacterStyle", () => { }); it("#semiHidden", () => { - const style = new CharacterStyle("myStyleId").semiHidden(); + const style = new CharacterStyle({ id: "myStyleId", semiHidden: true }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -298,7 +347,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -309,7 +358,12 @@ describe("CharacterStyle", () => { }); it("#highlight", () => { - const style = new CharacterStyle("myStyleId").highlight("005599"); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + highlight: "005599", + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -320,7 +374,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -332,7 +386,16 @@ describe("CharacterStyle", () => { }); it("#shadow", () => { - const style = new CharacterStyle("myStyleId").shadow("pct10", "00FFFF", "FF0000"); + const style = new CharacterStyle({ + id: "myStyleId", + run: { + shadow: { + type: ShadingType.PERCENT_10, + fill: "00FFFF", + color: "FF0000", + }, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -343,7 +406,7 @@ describe("CharacterStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, diff --git a/src/file/styles/style/character-style.ts b/src/file/styles/style/character-style.ts index c60cca9394..32ff4b6b69 100644 --- a/src/file/styles/style/character-style.ts +++ b/src/file/styles/style/character-style.ts @@ -1,69 +1,128 @@ import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; -import { XmlComponent } from "file/xml-components"; +import { UnderlineType } from "file/paragraph/run/underline"; + import { BasedOn, Link, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; import { Style } from "./style"; +export interface IBaseCharacterStyleOptions { + readonly basedOn?: string; + readonly link?: string; + readonly semiHidden?: boolean; + readonly run?: { + readonly size?: number; + readonly bold?: boolean; + readonly italics?: boolean; + readonly smallCaps?: boolean; + readonly allCaps?: boolean; + readonly strike?: boolean; + readonly doubleStrike?: boolean; + readonly subScript?: boolean; + readonly superScript?: boolean; + readonly underline?: { + readonly type?: UnderlineType; + readonly color?: string; + }; + readonly color?: string; + readonly font?: string; + readonly characterSpacing?: number; + readonly highlight?: string; + readonly shadow?: { + readonly type: string; + readonly fill: string; + readonly color: string; + }; + }; +} + +export interface ICharacterStyleOptions extends IBaseCharacterStyleOptions { + readonly id: string; + readonly name?: string; +} + export class CharacterStyle extends Style { private readonly runProperties: RunProperties; - constructor(styleId: string, name?: string) { - super({ type: "character", styleId: styleId }, name); + constructor(options: ICharacterStyleOptions) { + super({ type: "character", styleId: options.id }, options.name); this.runProperties = new RunProperties(); this.root.push(this.runProperties); - this.root.push(new UiPriority("99")); + this.root.push(new UiPriority(99)); this.root.push(new UnhideWhenUsed()); - } - public basedOn(parentId: string): CharacterStyle { - this.root.push(new BasedOn(parentId)); - return this; - } + if (options.basedOn) { + this.root.push(new BasedOn(options.basedOn)); + } - public addRunProperty(property: XmlComponent): CharacterStyle { - this.runProperties.push(property); - return this; - } + if (options.link) { + this.root.push(new Link(options.link)); + } - public color(color: string): CharacterStyle { - return this.addRunProperty(new formatting.Color(color)); - } + if (options.semiHidden) { + this.root.push(new SemiHidden()); + } - public bold(): CharacterStyle { - return this.addRunProperty(new formatting.Bold()); - } + if (options.run) { + if (options.run.size) { + this.runProperties.push(new formatting.Size(options.run.size)); + this.runProperties.push(new formatting.SizeComplexScript(options.run.size)); + } - public italics(): CharacterStyle { - return this.addRunProperty(new formatting.Italics()); - } + if (options.run.bold) { + this.runProperties.push(new formatting.Bold()); + } - public underline(underlineType?: string, color?: string): CharacterStyle { - return this.addRunProperty(new formatting.Underline(underlineType, color)); - } + if (options.run.italics) { + this.runProperties.push(new formatting.Italics()); + } - public superScript(): CharacterStyle { - return this.addRunProperty(new formatting.SuperScript()); - } + if (options.run.smallCaps) { + this.runProperties.push(new formatting.SmallCaps()); + } - public size(twips: number): CharacterStyle { - return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); - } + if (options.run.allCaps) { + this.runProperties.push(new formatting.Caps()); + } - public link(link: string): CharacterStyle { - this.root.push(new Link(link)); - return this; - } + if (options.run.strike) { + this.runProperties.push(new formatting.Strike()); + } - public semiHidden(): CharacterStyle { - this.root.push(new SemiHidden()); - return this; - } + if (options.run.doubleStrike) { + this.runProperties.push(new formatting.DoubleStrike()); + } - public highlight(color: string): CharacterStyle { - return this.addRunProperty(new formatting.Highlight(color)); - } + if (options.run.subScript) { + this.runProperties.push(new formatting.SubScript()); + } - public shadow(value: string, fill: string, color: string): CharacterStyle { - return this.addRunProperty(new formatting.Shading(value, fill, color)); + if (options.run.superScript) { + this.runProperties.push(new formatting.SuperScript()); + } + + if (options.run.underline) { + this.runProperties.push(new formatting.Underline(options.run.underline.type, options.run.underline.color)); + } + + if (options.run.color) { + this.runProperties.push(new formatting.Color(options.run.color)); + } + + if (options.run.font) { + this.runProperties.push(new formatting.RunFonts(options.run.font)); + } + + if (options.run.characterSpacing) { + this.runProperties.push(new formatting.CharacterSpacing(options.run.characterSpacing)); + } + + if (options.run.highlight) { + this.runProperties.push(new formatting.Highlight(options.run.highlight)); + } + + if (options.run.shadow) { + this.runProperties.push(new formatting.Shading(options.run.shadow.type, options.run.shadow.fill, options.run.shadow.color)); + } + } } } diff --git a/src/file/styles/style/components.spec.ts b/src/file/styles/style/components.spec.ts index 7542cf009f..b1720ef4b4 100644 --- a/src/file/styles/style/components.spec.ts +++ b/src/file/styles/style/components.spec.ts @@ -30,9 +30,9 @@ describe("Style components", () => { }); it("UiPriority#constructor", () => { - const style = new components.UiPriority("123"); + const style = new components.UiPriority(123); const tree = new Formatter().format(style); - expect(tree).to.deep.equal({ "w:uiPriority": { _attr: { "w:val": "123" } } }); + expect(tree).to.deep.equal({ "w:uiPriority": { _attr: { "w:val": 123 } } }); }); it("UnhideWhenUsed#constructor", () => { diff --git a/src/file/styles/style/components.ts b/src/file/styles/style/components.ts index 019b50624b..a051a75005 100644 --- a/src/file/styles/style/components.ts +++ b/src/file/styles/style/components.ts @@ -1,7 +1,8 @@ +// http://officeopenxml.com/WPstyleGenProps.php import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; interface IComponentAttributes { - readonly val: string; + readonly val: string | number; } class ComponentAttributes extends XmlAttributeComponent { @@ -37,7 +38,7 @@ export class Link extends XmlComponent { } export class UiPriority extends XmlComponent { - constructor(value: string) { + constructor(value: number) { super("w:uiPriority"); // TODO: this value should be a ST_DecimalNumber this.root.push(new ComponentAttributes({ val: value })); diff --git a/src/file/styles/style/default-styles.spec.ts b/src/file/styles/style/default-styles.spec.ts index ac6022583b..1cf26ba482 100644 --- a/src/file/styles/style/default-styles.spec.ts +++ b/src/file/styles/style/default-styles.spec.ts @@ -1,12 +1,15 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import * as defaultStyels from "./default-styles"; +import * as defaultStyles from "./default-styles"; import { EMPTY_OBJECT } from "file/xml-components"; describe("Default Styles", () => { it("HeadingStyle#constructor", () => { - const style = new defaultStyels.HeadingStyle("Heading1", "Heading 1"); + const style = new defaultStyles.HeadingStyle({ + id: "Heading1", + name: "Heading 1", + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -20,7 +23,7 @@ describe("Default Styles", () => { }); it("TitleStyle#constructor", () => { - const style = new defaultStyels.TitleStyle(); + const style = new defaultStyles.TitleStyle({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -34,7 +37,7 @@ describe("Default Styles", () => { }); it("Heading1Style#constructor", () => { - const style = new defaultStyels.Heading1Style(); + const style = new defaultStyles.Heading1Style({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -48,7 +51,7 @@ describe("Default Styles", () => { }); it("Heading2Style#constructor", () => { - const style = new defaultStyels.Heading2Style(); + const style = new defaultStyles.Heading2Style({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -62,7 +65,7 @@ describe("Default Styles", () => { }); it("Heading3Style#constructor", () => { - const style = new defaultStyels.Heading3Style(); + const style = new defaultStyles.Heading3Style({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -76,7 +79,7 @@ describe("Default Styles", () => { }); it("Heading4Style#constructor", () => { - const style = new defaultStyels.Heading4Style(); + const style = new defaultStyles.Heading4Style({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -90,7 +93,7 @@ describe("Default Styles", () => { }); it("Heading5Style#constructor", () => { - const style = new defaultStyels.Heading5Style(); + const style = new defaultStyles.Heading5Style({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -104,7 +107,7 @@ describe("Default Styles", () => { }); it("Heading6Style#constructor", () => { - const style = new defaultStyels.Heading6Style(); + const style = new defaultStyles.Heading6Style({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -118,7 +121,7 @@ describe("Default Styles", () => { }); it("ListParagraph#constructor", () => { - const style = new defaultStyels.ListParagraph(); + const style = new defaultStyles.ListParagraph({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -131,7 +134,7 @@ describe("Default Styles", () => { }); it("FootnoteText#constructor", () => { - const style = new defaultStyels.FootnoteText(); + const style = new defaultStyles.FootnoteText({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -171,14 +174,14 @@ describe("Default Styles", () => { { "w:basedOn": { _attr: { "w:val": "Normal" } } }, { "w:link": { _attr: { "w:val": "FootnoteTextChar" } } }, { - "w:uiPriority": { - _attr: { - "w:val": "99", - }, - }, + "w:semiHidden": EMPTY_OBJECT, }, { - "w:semiHidden": EMPTY_OBJECT, + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, }, { "w:unhideWhenUsed": EMPTY_OBJECT, @@ -188,7 +191,7 @@ describe("Default Styles", () => { }); it("FootnoteReferenceStyle#constructor", () => { - const style = new defaultStyels.FootnoteReferenceStyle(); + const style = new defaultStyles.FootnoteReferenceStyle({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -208,7 +211,7 @@ describe("Default Styles", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -225,7 +228,7 @@ describe("Default Styles", () => { }); it("FootnoteTextChar#constructor", () => { - const style = new defaultStyels.FootnoteTextChar(); + const style = new defaultStyles.FootnoteTextChar({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -252,7 +255,7 @@ describe("Default Styles", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -269,19 +272,28 @@ describe("Default Styles", () => { }); it("HyperlinkStyle#constructor", () => { - const style = new defaultStyels.HyperlinkStyle(); + const style = new defaultStyles.HyperlinkStyle({}); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ { _attr: { "w:type": "character", "w:styleId": "Hyperlink" } }, { "w:name": { _attr: { "w:val": "Hyperlink" } } }, { - "w:rPr": [{ "w:color": { _attr: { "w:val": "0563C1" } } }, { "w:u": { _attr: { "w:val": "single" } } }], + "w:rPr": [ + { "w:u": { _attr: { "w:val": "single" } } }, + { + "w:color": { + _attr: { + "w:val": "0563C1", + }, + }, + }, + ], }, { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, diff --git a/src/file/styles/style/default-styles.ts b/src/file/styles/style/default-styles.ts index d9572499c2..8974435a95 100644 --- a/src/file/styles/style/default-styles.ts +++ b/src/file/styles/style/default-styles.ts @@ -1,106 +1,170 @@ -import { CharacterStyle } from "./character-style"; -import { ParagraphStyle } from "./paragraph-style"; +import { UnderlineType } from "file/paragraph/run/underline"; + +import { CharacterStyle, IBaseCharacterStyleOptions } from "./character-style"; +import { IBaseParagraphStyleOptions, IParagraphStyleOptions, ParagraphStyle } from "./paragraph-style"; export class HeadingStyle extends ParagraphStyle { - constructor(styleId: string, name: string) { - super(styleId, name); - this.basedOn("Normal"); - this.next("Normal"); - this.quickFormat(); + constructor(options: IParagraphStyleOptions) { + super({ + ...options, + basedOn: "Normal", + next: "Normal", + quickFormat: true, + }); } } export class TitleStyle extends HeadingStyle { - constructor() { - super("Title", "Title"); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "Title", + name: "Title", + }); } } export class Heading1Style extends HeadingStyle { - constructor() { - super("Heading1", "Heading 1"); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "Heading1", + name: "Heading 1", + }); } } export class Heading2Style extends HeadingStyle { - constructor() { - super("Heading2", "Heading 2"); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "Heading2", + name: "Heading 2", + }); } } export class Heading3Style extends HeadingStyle { - constructor() { - super("Heading3", "Heading 3"); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "Heading3", + name: "Heading 3", + }); } } export class Heading4Style extends HeadingStyle { - constructor() { - super("Heading4", "Heading 4"); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "Heading4", + name: "Heading 4", + }); } } export class Heading5Style extends HeadingStyle { - constructor() { - super("Heading5", "Heading 5"); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "Heading5", + name: "Heading 5", + }); } } export class Heading6Style extends HeadingStyle { - constructor() { - super("Heading6", "Heading 6"); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "Heading6", + name: "Heading 6", + }); } } export class ListParagraph extends ParagraphStyle { - constructor() { - super("ListParagraph", "List Paragraph"); - this.basedOn("Normal"); - this.quickFormat(); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "ListParagraph", + name: "List Paragraph", + basedOn: "Normal", + quickFormat: true, + }); } } export class FootnoteText extends ParagraphStyle { - constructor() { - super("FootnoteText", "footnote text"); - this.basedOn("Normal") - .link("FootnoteTextChar") - .uiPriority("99") - .semiHidden() - .unhideWhenUsed() - .spacing({ - after: 0, - line: 240, - lineRule: "auto", - }) - .size(20); + constructor(options: IBaseParagraphStyleOptions) { + super({ + ...options, + id: "FootnoteText", + name: "footnote text", + link: "FootnoteTextChar", + basedOn: "Normal", + uiPriority: 99, + semiHidden: true, + unhideWhenUsed: true, + paragraph: { + spacing: { + after: 0, + line: 240, + lineRule: "auto", + }, + }, + run: { + size: 20, + }, + }); } } export class FootnoteReferenceStyle extends CharacterStyle { - constructor() { - super("FootnoteReference", "footnote reference"); - this.basedOn("DefaultParagraphFont") - .semiHidden() - .superScript(); + constructor(options: IBaseCharacterStyleOptions) { + super({ + ...options, + id: "FootnoteReference", + name: "footnote reference", + basedOn: "DefaultParagraphFont", + semiHidden: true, + run: { + superScript: true, + }, + }); } } export class FootnoteTextChar extends CharacterStyle { - constructor() { - super("FootnoteTextChar", "Footnote Text Char"); - this.basedOn("DefaultParagraphFont") - .link("FootnoteText") - .semiHidden() - .size(20); + constructor(options: IBaseCharacterStyleOptions) { + super({ + ...options, + id: "FootnoteTextChar", + name: "Footnote Text Char", + basedOn: "DefaultParagraphFont", + link: "FootnoteText", + semiHidden: true, + run: { + size: 20, + }, + }); } } export class HyperlinkStyle extends CharacterStyle { - constructor() { - super("Hyperlink", "Hyperlink"); - this.basedOn("DefaultParagraphFont") - .color("0563C1") - .underline("single"); + constructor(options: IBaseCharacterStyleOptions) { + super({ + ...options, + id: "Hyperlink", + name: "Hyperlink", + basedOn: "DefaultParagraphFont", + run: { + color: "0563C1", + underline: { + type: UnderlineType.SINGLE, + }, + }, + }); } } diff --git a/src/file/styles/style/paragraph-style.spec.ts b/src/file/styles/style/paragraph-style.spec.ts index d3a77b9a15..93b0c6777c 100644 --- a/src/file/styles/style/paragraph-style.spec.ts +++ b/src/file/styles/style/paragraph-style.spec.ts @@ -1,7 +1,9 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { TabStopPosition } from "file/paragraph"; +import { AlignmentType, TabStopPosition } from "file/paragraph"; +import { UnderlineType } from "file/paragraph/run/underline"; +import { ShadingType } from "file/table"; import { EMPTY_OBJECT } from "file/xml-components"; import { ParagraphStyle } from "./paragraph-style"; @@ -9,7 +11,7 @@ import { ParagraphStyle } from "./paragraph-style"; describe("ParagraphStyle", () => { describe("#constructor", () => { it("should set the style type to paragraph and use the given style id", () => { - const style = new ParagraphStyle("myStyleId"); + const style = new ParagraphStyle({ id: "myStyleId" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, @@ -17,7 +19,10 @@ describe("ParagraphStyle", () => { }); it("should set the name of the style, if given", () => { - const style = new ParagraphStyle("myStyleId", "Style Name"); + const style = new ParagraphStyle({ + id: "myStyleId", + name: "Style Name", + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -30,7 +35,7 @@ describe("ParagraphStyle", () => { describe("formatting methods: style attributes", () => { it("#basedOn", () => { - const style = new ParagraphStyle("myStyleId").basedOn("otherId"); + const style = new ParagraphStyle({ id: "myStyleId", basedOn: "otherId" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -41,7 +46,7 @@ describe("ParagraphStyle", () => { }); it("#quickFormat", () => { - const style = new ParagraphStyle("myStyleId").quickFormat(); + const style = new ParagraphStyle({ id: "myStyleId", quickFormat: true }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:qFormat": EMPTY_OBJECT }], @@ -49,7 +54,7 @@ describe("ParagraphStyle", () => { }); it("#next", () => { - const style = new ParagraphStyle("myStyleId").next("otherId"); + const style = new ParagraphStyle({ id: "myStyleId", next: "otherId" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -62,7 +67,12 @@ describe("ParagraphStyle", () => { describe("formatting methods: paragraph properties", () => { it("#indent", () => { - const style = new ParagraphStyle("myStyleId").indent({ left: 720 }); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + indent: { left: 720 }, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -75,7 +85,7 @@ describe("ParagraphStyle", () => { }); it("#spacing", () => { - const style = new ParagraphStyle("myStyleId").spacing({ before: 50, after: 150 }); + const style = new ParagraphStyle({ id: "myStyleId", paragraph: { spacing: { before: 50, after: 150 } } }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -88,7 +98,12 @@ describe("ParagraphStyle", () => { }); it("#center", () => { - const style = new ParagraphStyle("myStyleId").center(); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + alignment: AlignmentType.CENTER, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -101,7 +116,12 @@ describe("ParagraphStyle", () => { }); it("#character spacing", () => { - const style = new ParagraphStyle("myStyleId").characterSpacing(24); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + characterSpacing: 24, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -114,7 +134,12 @@ describe("ParagraphStyle", () => { }); it("#left", () => { - const style = new ParagraphStyle("myStyleId").left(); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + alignment: AlignmentType.LEFT, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -127,7 +152,12 @@ describe("ParagraphStyle", () => { }); it("#right", () => { - const style = new ParagraphStyle("myStyleId").right(); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + alignment: AlignmentType.RIGHT, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -140,7 +170,12 @@ describe("ParagraphStyle", () => { }); it("#justified", () => { - const style = new ParagraphStyle("myStyleId").justified(); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + alignment: AlignmentType.JUSTIFIED, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -153,7 +188,12 @@ describe("ParagraphStyle", () => { }); it("#thematicBreak", () => { - const style = new ParagraphStyle("myStyleId").thematicBreak(); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + thematicBreak: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -181,7 +221,12 @@ describe("ParagraphStyle", () => { }); it("#leftTabStop", () => { - const style = new ParagraphStyle("myStyleId").leftTabStop(1200); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + leftTabStop: 1200, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -198,7 +243,12 @@ describe("ParagraphStyle", () => { }); it("#maxRightTabStop", () => { - const style = new ParagraphStyle("myStyleId").rightTabStop(TabStopPosition.MAX); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + rightTabStop: TabStopPosition.MAX, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -215,7 +265,12 @@ describe("ParagraphStyle", () => { }); it("#keepLines", () => { - const style = new ParagraphStyle("myStyleId").keepLines(); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + keepLines: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }] }], @@ -223,7 +278,12 @@ describe("ParagraphStyle", () => { }); it("#keepNext", () => { - const style = new ParagraphStyle("myStyleId").keepNext(); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + keepNext: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }] }], @@ -231,7 +291,12 @@ describe("ParagraphStyle", () => { }); it("#outlineLevel", () => { - const style = new ParagraphStyle("myStyleId").outlineLevel(1); + const style = new ParagraphStyle({ + id: "myStyleId", + paragraph: { + outlineLevel: 1, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -244,7 +309,12 @@ describe("ParagraphStyle", () => { describe("formatting methods: run properties", () => { it("#size", () => { - const style = new ParagraphStyle("myStyleId").size(24); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + size: 24, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -257,7 +327,12 @@ describe("ParagraphStyle", () => { }); it("#smallCaps", () => { - const style = new ParagraphStyle("myStyleId").smallCaps(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + smallCaps: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -270,7 +345,12 @@ describe("ParagraphStyle", () => { }); it("#allCaps", () => { - const style = new ParagraphStyle("myStyleId").allCaps(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + allCaps: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -283,7 +363,12 @@ describe("ParagraphStyle", () => { }); it("#strike", () => { - const style = new ParagraphStyle("myStyleId").strike(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + strike: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -296,7 +381,12 @@ describe("ParagraphStyle", () => { }); it("#doubleStrike", () => { - const style = new ParagraphStyle("myStyleId").doubleStrike(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + doubleStrike: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -309,7 +399,12 @@ describe("ParagraphStyle", () => { }); it("#subScript", () => { - const style = new ParagraphStyle("myStyleId").subScript(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + subScript: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -322,7 +417,12 @@ describe("ParagraphStyle", () => { }); it("#superScript", () => { - const style = new ParagraphStyle("myStyleId").superScript(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + superScript: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -335,7 +435,12 @@ describe("ParagraphStyle", () => { }); it("#font", () => { - const style = new ParagraphStyle("myStyleId").font("Times"); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + font: "Times", + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -350,7 +455,12 @@ describe("ParagraphStyle", () => { }); it("#bold", () => { - const style = new ParagraphStyle("myStyleId").bold(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + bold: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -363,7 +473,12 @@ describe("ParagraphStyle", () => { }); it("#italics", () => { - const style = new ParagraphStyle("myStyleId").italics(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + italics: true, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -376,7 +491,12 @@ describe("ParagraphStyle", () => { }); it("#highlight", () => { - const style = new ParagraphStyle("myStyleId").highlight("005599"); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + highlight: "005599", + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -389,7 +509,16 @@ describe("ParagraphStyle", () => { }); it("#shadow", () => { - const style = new ParagraphStyle("myStyleId").shadow("pct10", "00FFFF", "FF0000"); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + shadow: { + type: ShadingType.PERCENT_10, + fill: "00FFFF", + color: "FF0000", + }, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -403,7 +532,12 @@ describe("ParagraphStyle", () => { describe("#underline", () => { it("should set underline to 'single' if no arguments are given", () => { - const style = new ParagraphStyle("myStyleId").underline(); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + underline: {}, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -416,7 +550,14 @@ describe("ParagraphStyle", () => { }); it("should set the style if given", () => { - const style = new ParagraphStyle("myStyleId").underline("double"); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + underline: { + type: UnderlineType.DOUBLE, + }, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -429,7 +570,15 @@ describe("ParagraphStyle", () => { }); it("should set the style and color if given", () => { - const style = new ParagraphStyle("myStyleId").underline("double", "005599"); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + underline: { + type: UnderlineType.DOUBLE, + color: "005599", + }, + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -443,7 +592,12 @@ describe("ParagraphStyle", () => { }); it("#color", () => { - const style = new ParagraphStyle("myStyleId").color("123456"); + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + color: "123456", + }, + }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -456,7 +610,7 @@ describe("ParagraphStyle", () => { }); it("#link", () => { - const style = new ParagraphStyle("myStyleId").link("MyLink"); + const style = new ParagraphStyle({ id: "myStyleId", link: "MyLink" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:link": { _attr: { "w:val": "MyLink" } } }], @@ -464,7 +618,7 @@ describe("ParagraphStyle", () => { }); it("#semiHidden", () => { - const style = new ParagraphStyle("myStyleId").semiHidden(); + const style = new ParagraphStyle({ id: "myStyleId", semiHidden: true }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:semiHidden": EMPTY_OBJECT }], @@ -472,7 +626,7 @@ describe("ParagraphStyle", () => { }); it("#uiPriority", () => { - const style = new ParagraphStyle("myStyleId").uiPriority("99"); + const style = new ParagraphStyle({ id: "myStyleId", uiPriority: 99 }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ @@ -480,7 +634,7 @@ describe("ParagraphStyle", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -489,7 +643,7 @@ describe("ParagraphStyle", () => { }); it("#unhideWhenUsed", () => { - const style = new ParagraphStyle("myStyleId").unhideWhenUsed(); + const style = new ParagraphStyle({ id: "myStyleId", unhideWhenUsed: true }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:unhideWhenUsed": EMPTY_OBJECT }], diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index 360e2924de..ab9f75ccec 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -14,179 +14,199 @@ import { import { RightTabStop } from "file/paragraph/formatting"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; -import { XmlComponent } from "file/xml-components"; +import { UnderlineType } from "file/paragraph/run/underline"; +import { ShadingType } from "file/table"; + import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; import { Style } from "./style"; +export interface IBaseParagraphStyleOptions { + readonly basedOn?: string; + readonly next?: string; + readonly quickFormat?: boolean; + readonly link?: string; + readonly semiHidden?: boolean; + readonly uiPriority?: number; + readonly unhideWhenUsed?: boolean; + readonly run?: { + readonly size?: number; + readonly bold?: boolean; + readonly italics?: boolean; + readonly smallCaps?: boolean; + readonly allCaps?: boolean; + readonly strike?: boolean; + readonly doubleStrike?: boolean; + readonly subScript?: boolean; + readonly superScript?: boolean; + readonly underline?: { + readonly type?: UnderlineType; + readonly color?: string; + }; + readonly color?: string; + readonly font?: string; + readonly characterSpacing?: number; + readonly highlight?: string; + readonly shadow?: { + readonly type: ShadingType; + readonly fill: string; + readonly color: string; + }; + }; + readonly paragraph?: { + readonly alignment?: AlignmentType; + readonly thematicBreak?: boolean; + readonly rightTabStop?: number; + readonly leftTabStop?: number; + readonly indent?: object; + readonly spacing?: ISpacingProperties; + readonly keepNext?: boolean; + readonly keepLines?: boolean; + readonly outlineLevel?: number; + }; +} + +export interface IParagraphStyleOptions extends IBaseParagraphStyleOptions { + readonly id: string; + readonly name?: string; +} export class ParagraphStyle extends Style { private readonly paragraphProperties: ParagraphProperties; private readonly runProperties: RunProperties; - constructor(styleId: string, name?: string) { - super({ type: "paragraph", styleId: styleId }, name); + constructor(options: IParagraphStyleOptions) { + super({ type: "paragraph", styleId: options.id }, options.name); this.paragraphProperties = new ParagraphProperties({}); this.runProperties = new RunProperties(); this.root.push(this.paragraphProperties); this.root.push(this.runProperties); - } - public addParagraphProperty(property: XmlComponent): ParagraphStyle { - this.paragraphProperties.push(property); - return this; - } + if (options.basedOn) { + this.root.push(new BasedOn(options.basedOn)); + } - public outlineLevel(level: number): ParagraphStyle { - this.paragraphProperties.push(new OutlineLevel(level)); - return this; - } + if (options.next) { + this.root.push(new Next(options.next)); + } - public addRunProperty(property: XmlComponent): ParagraphStyle { - this.runProperties.push(property); - return this; - } + if (options.quickFormat) { + this.root.push(new QuickFormat()); + } - public basedOn(parentId: string): ParagraphStyle { - this.root.push(new BasedOn(parentId)); - return this; - } + if (options.link) { + this.root.push(new Link(options.link)); + } - public quickFormat(): ParagraphStyle { - this.root.push(new QuickFormat()); - return this; - } + if (options.semiHidden) { + this.root.push(new SemiHidden()); + } - public next(nextId: string): ParagraphStyle { - this.root.push(new Next(nextId)); - return this; - } + if (options.uiPriority) { + this.root.push(new UiPriority(options.uiPriority)); + } - // ---------- Run formatting ---------------------- // + if (options.unhideWhenUsed) { + this.root.push(new UnhideWhenUsed()); + } - public size(twips: number): ParagraphStyle { - return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); - } + if (options.run) { + if (options.run.size) { + this.runProperties.push(new formatting.Size(options.run.size)); + this.runProperties.push(new formatting.SizeComplexScript(options.run.size)); + } - public bold(): ParagraphStyle { - return this.addRunProperty(new formatting.Bold()); - } + if (options.run.bold) { + this.runProperties.push(new formatting.Bold()); + } - public italics(): ParagraphStyle { - return this.addRunProperty(new formatting.Italics()); - } + if (options.run.italics) { + this.runProperties.push(new formatting.Italics()); + } - public smallCaps(): ParagraphStyle { - return this.addRunProperty(new formatting.SmallCaps()); - } + if (options.run.smallCaps) { + this.runProperties.push(new formatting.SmallCaps()); + } - public allCaps(): ParagraphStyle { - return this.addRunProperty(new formatting.Caps()); - } + if (options.run.allCaps) { + this.runProperties.push(new formatting.Caps()); + } - public strike(): ParagraphStyle { - return this.addRunProperty(new formatting.Strike()); - } + if (options.run.strike) { + this.runProperties.push(new formatting.Strike()); + } - public doubleStrike(): ParagraphStyle { - return this.addRunProperty(new formatting.DoubleStrike()); - } + if (options.run.doubleStrike) { + this.runProperties.push(new formatting.DoubleStrike()); + } - public subScript(): ParagraphStyle { - return this.addRunProperty(new formatting.SubScript()); - } + if (options.run.subScript) { + this.runProperties.push(new formatting.SubScript()); + } - public superScript(): ParagraphStyle { - return this.addRunProperty(new formatting.SuperScript()); - } + if (options.run.superScript) { + this.runProperties.push(new formatting.SuperScript()); + } - public underline(underlineType?: string, color?: string): ParagraphStyle { - return this.addRunProperty(new formatting.Underline(underlineType, color)); - } + if (options.run.underline) { + this.runProperties.push(new formatting.Underline(options.run.underline.type, options.run.underline.color)); + } - public color(color: string): ParagraphStyle { - return this.addRunProperty(new formatting.Color(color)); - } + if (options.run.color) { + this.runProperties.push(new formatting.Color(options.run.color)); + } - public font(fontName: string): ParagraphStyle { - return this.addRunProperty(new formatting.RunFonts(fontName)); - } + if (options.run.font) { + this.runProperties.push(new formatting.RunFonts(options.run.font)); + } - public characterSpacing(value: number): ParagraphStyle { - return this.addRunProperty(new formatting.CharacterSpacing(value)); - } + if (options.run.characterSpacing) { + this.runProperties.push(new formatting.CharacterSpacing(options.run.characterSpacing)); + } - public highlight(color: string): ParagraphStyle { - return this.addRunProperty(new formatting.Highlight(color)); - } + if (options.run.highlight) { + this.runProperties.push(new formatting.Highlight(options.run.highlight)); + } - public shadow(value: string, fill: string, color: string): ParagraphStyle { - return this.addRunProperty(new formatting.Shading(value, fill, color)); - } + if (options.run.shadow) { + this.runProperties.push(new formatting.Shading(options.run.shadow.type, options.run.shadow.fill, options.run.shadow.color)); + } + } - // --------------------- Paragraph formatting ------------------------ // + if (options.paragraph) { + if (options.paragraph.alignment) { + this.paragraphProperties.push(new Alignment(options.paragraph.alignment)); + } - public center(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentType.CENTER)); - } + if (options.paragraph.thematicBreak) { + this.paragraphProperties.push(new ThematicBreak()); + } - public left(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentType.LEFT)); - } + if (options.paragraph.rightTabStop) { + this.paragraphProperties.push(new RightTabStop(options.paragraph.rightTabStop)); + } - public right(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentType.RIGHT)); - } + if (options.paragraph.leftTabStop) { + this.paragraphProperties.push(new LeftTabStop(options.paragraph.leftTabStop)); + } - public justified(): ParagraphStyle { - return this.addParagraphProperty(new Alignment(AlignmentType.BOTH)); - } + if (options.paragraph.indent) { + this.paragraphProperties.push(new Indent(options.paragraph.indent)); + } - public thematicBreak(): ParagraphStyle { - return this.addParagraphProperty(new ThematicBreak()); - } + if (options.paragraph.spacing) { + this.paragraphProperties.push(new Spacing(options.paragraph.spacing)); + } - public rightTabStop(position: number): ParagraphStyle { - return this.addParagraphProperty(new RightTabStop(position)); - } + if (options.paragraph.keepNext) { + this.paragraphProperties.push(new KeepNext()); + } - public leftTabStop(position: number): ParagraphStyle { - return this.addParagraphProperty(new LeftTabStop(position)); - } + if (options.paragraph.keepLines) { + this.paragraphProperties.push(new KeepLines()); + } - public indent(attrs: object): ParagraphStyle { - return this.addParagraphProperty(new Indent(attrs)); - } - - public spacing(params: ISpacingProperties): ParagraphStyle { - return this.addParagraphProperty(new Spacing(params)); - } - - public keepNext(): ParagraphStyle { - return this.addParagraphProperty(new KeepNext()); - } - - public keepLines(): ParagraphStyle { - return this.addParagraphProperty(new KeepLines()); - } - - /*-------------- Style Properties -----------------*/ - - public link(link: string): ParagraphStyle { - this.root.push(new Link(link)); - return this; - } - - public semiHidden(): ParagraphStyle { - this.root.push(new SemiHidden()); - return this; - } - - public uiPriority(priority: string): ParagraphStyle { - this.root.push(new UiPriority(priority)); - return this; - } - - public unhideWhenUsed(): ParagraphStyle { - this.root.push(new UnhideWhenUsed()); - return this; + if (options.paragraph.outlineLevel) { + this.paragraphProperties.push(new OutlineLevel(options.paragraph.outlineLevel)); + } + } } } diff --git a/src/file/styles/styles.spec.ts b/src/file/styles/styles.spec.ts index 69c0bd931e..acc782c743 100644 --- a/src/file/styles/styles.spec.ts +++ b/src/file/styles/styles.spec.ts @@ -1,31 +1,20 @@ -import { assert, expect } from "chai"; +import { expect } from "chai"; import { Formatter } from "export/formatter"; - -import { CharacterStyle, ParagraphStyle } from "./style"; +import { EMPTY_OBJECT } from "file/xml-components"; import { Styles } from "./styles"; -import { EMPTY_OBJECT } from "file/xml-components"; - describe("Styles", () => { - let styles: Styles; - - beforeEach(() => { - styles = new Styles(); - }); - - describe("#constructor()", () => { - it("should create styles with correct rootKey", () => { - const newJson = JSON.parse(JSON.stringify(styles)); - assert.equal(newJson.rootKey, "w:styles"); - }); - }); - describe("#createParagraphStyle", () => { it("should create a new paragraph style and push it onto this collection", () => { - const pStyle = styles.createParagraphStyle("pStyleId"); - expect(pStyle).to.instanceOf(ParagraphStyle); + const styles = new Styles({ + paragraphStyles: [ + { + id: "pStyleId", + }, + ], + }); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); expect(tree).to.deep.equal([ { @@ -35,8 +24,14 @@ describe("Styles", () => { }); it("should set the paragraph name if given", () => { - const pStyle = styles.createParagraphStyle("pStyleId", "Paragraph Style"); - expect(pStyle).to.instanceOf(ParagraphStyle); + const styles = new Styles({ + paragraphStyles: [ + { + id: "pStyleId", + name: "Paragraph Style", + }, + ], + }); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); expect(tree).to.deep.equal([ { @@ -51,8 +46,13 @@ describe("Styles", () => { describe("#createCharacterStyle", () => { it("should create a new character style and push it onto this collection", () => { - const cStyle = styles.createCharacterStyle("pStyleId"); - expect(cStyle).to.instanceOf(CharacterStyle); + const styles = new Styles({ + characterStyles: [ + { + id: "pStyleId", + }, + ], + }); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); expect(tree).to.deep.equal([ { @@ -61,7 +61,7 @@ describe("Styles", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, @@ -74,8 +74,14 @@ describe("Styles", () => { }); it("should set the character name if given", () => { - const cStyle = styles.createCharacterStyle("pStyleId", "Character Style"); - expect(cStyle).to.instanceOf(CharacterStyle); + const styles = new Styles({ + characterStyles: [ + { + id: "pStyleId", + name: "Character Style", + }, + ], + }); const tree = new Formatter().format(styles)["w:styles"].filter((x) => !x._attr); expect(tree).to.deep.equal([ { @@ -85,7 +91,7 @@ describe("Styles", () => { { "w:uiPriority": { _attr: { - "w:val": "99", + "w:val": 99, }, }, }, diff --git a/src/file/styles/styles.ts b/src/file/styles/styles.ts index ff38f5ec6e..6e5baa8f2a 100644 --- a/src/file/styles/styles.ts +++ b/src/file/styles/styles.ts @@ -1,36 +1,48 @@ -import { BaseXmlComponent, XmlComponent } from "file/xml-components"; +import { BaseXmlComponent, ImportedXmlComponent, XmlComponent } from "file/xml-components"; + import { DocumentDefaults } from "./defaults"; import { CharacterStyle, ParagraphStyle } from "./style"; +import { ICharacterStyleOptions } from "./style/character-style"; +import { IParagraphStyleOptions } from "./style/paragraph-style"; export * from "./border"; -export class Styles extends XmlComponent { - constructor(initialStyles?: BaseXmlComponent) { - super("w:styles"); - if (initialStyles) { - this.root.push(initialStyles); - } - } +interface IStylesOptions { + readonly initialStyles?: BaseXmlComponent; + readonly paragraphStyles?: IParagraphStyleOptions[]; + readonly characterStyles?: ICharacterStyleOptions[]; + readonly importedStyles?: Array; +} - public push(style: XmlComponent): Styles { - this.root.push(style); - return this; +export class Styles extends XmlComponent { + constructor(options: IStylesOptions) { + super("w:styles"); + + if (options.initialStyles) { + this.root.push(options.initialStyles); + } + + if (options.paragraphStyles) { + for (const style of options.paragraphStyles) { + this.root.push(new ParagraphStyle(style)); + } + } + + if (options.characterStyles) { + for (const style of options.characterStyles) { + this.root.push(new CharacterStyle(style)); + } + } + + if (options.importedStyles) { + for (const style of options.importedStyles) { + this.root.push(style); + } + } } public createDocumentDefaults(): DocumentDefaults { const defaults = new DocumentDefaults(); - this.push(defaults); + this.root.push(defaults); return defaults; } - - public createParagraphStyle(styleId: string, name?: string): ParagraphStyle { - const paragraphStyle = new ParagraphStyle(styleId, name); - this.push(paragraphStyle); - return paragraphStyle; - } - - public createCharacterStyle(styleId: string, name?: string): CharacterStyle { - const characterStyle = new CharacterStyle(styleId, name); - this.push(characterStyle); - return characterStyle; - } }