From 04b6d8e54ab50aee935be19884a9bf99ef3124e6 Mon Sep 17 00:00:00 2001 From: Dolan Date: Mon, 30 Sep 2019 22:56:21 +0100 Subject: [PATCH 01/12] Declarative hyperlinks, bookmarks, tab stops and page breaks --- demo/10-my-cv.ts | 6 +- demo/14-page-numbers.ts | 6 +- demo/2-declaritive-styles.ts | 18 +++-- demo/21-bookmarks.ts | 12 ++- demo/35-hyperlinks.ts | 8 +- docs/usage/paragraph.md | 9 ++- docs/usage/tab-stops.md | 78 ++++++++++++++----- docs/usage/table-of-contents.md | 24 ------ src/export/formatter.spec.ts | 55 ++++++++++--- src/file/footnotes/footnotes.ts | 6 +- src/file/numbering/level.ts | 8 +- src/file/numbering/numbering.spec.ts | 3 +- .../paragraph/formatting/tab-stop.spec.ts | 27 +------ src/file/paragraph/formatting/tab-stop.ts | 10 +-- src/file/paragraph/paragraph.spec.ts | 13 ++-- src/file/paragraph/paragraph.ts | 43 +++------- src/file/paragraph/run/run.ts | 8 ++ src/file/styles/style/paragraph-style.spec.ts | 6 +- src/file/styles/style/paragraph-style.ts | 6 +- .../table-of-contents/table-of-contents.ts | 26 ++++--- 20 files changed, 207 insertions(+), 165 deletions(-) diff --git a/demo/10-my-cv.ts b/demo/10-my-cv.ts index 6c60be4f48..75c5d9e8bd 100644 --- a/demo/10-my-cv.ts +++ b/demo/10-my-cv.ts @@ -1,7 +1,7 @@ // Generate a CV // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun } from "../build"; +import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TabStopPosition, TextRun } from "../build"; // tslint:disable:no-shadowed-variable @@ -227,7 +227,9 @@ class DocumentCreator { public createInstitutionHeader(institutionName: string, dateText: string): Paragraph { return new Paragraph({ tabStop: { - maxRight: {}, + right: { + position: TabStopPosition.MAX, + }, }, children: [ new TextRun({ diff --git a/demo/14-page-numbers.ts b/demo/14-page-numbers.ts index fc9a20169d..ae34300b5c 100644 --- a/demo/14-page-numbers.ts +++ b/demo/14-page-numbers.ts @@ -1,7 +1,7 @@ // Page numbers // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { AlignmentType, Document, Header, Packer, Paragraph, TextRun } from "../build"; +import { AlignmentType, Document, Header, Packer, PageBreak, Paragraph, TextRun } from "../build"; const doc = new Document(); @@ -26,8 +26,8 @@ doc.addSection({ }, children: [ new Paragraph({ - text: "First Page", - }).pageBreak(), + children: [new TextRun("First Page"), new PageBreak()], + }), new Paragraph("Second Page"), ], }); diff --git a/demo/2-declaritive-styles.ts b/demo/2-declaritive-styles.ts index a854b04546..a514e7eef6 100644 --- a/demo/2-declaritive-styles.ts +++ b/demo/2-declaritive-styles.ts @@ -82,14 +82,16 @@ doc.addSection({ level: 0, }, }), - new Paragraph({}).addRun( - new TextRun({ - text: "Some monospaced content", - font: { - name: "Monospace", - }, - }), - ), + new Paragraph({ + children: [ + new TextRun({ + text: "Some monospaced content", + font: { + name: "Monospace", + }, + }), + ], + }), new Paragraph({ text: "An aside, in light gray italics and indented", style: "aside", diff --git a/demo/21-bookmarks.ts b/demo/21-bookmarks.ts index 7a8e7b523b..ae7427aaf4 100644 --- a/demo/21-bookmarks.ts +++ b/demo/21-bookmarks.ts @@ -2,6 +2,7 @@ // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; import { Document, HeadingLevel, Packer, Paragraph } from "../build"; +import { PageBreak } from "../build/file/paragraph"; const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante."; @@ -22,11 +23,16 @@ doc.addSection({ children: [ new Paragraph({ heading: HeadingLevel.HEADING_1, - }).addBookmark(bookmark), + children: [bookmark], + }), new Paragraph("\n"), new Paragraph(LOREM_IPSUM), - new Paragraph({}).pageBreak(), - new Paragraph({}).addHyperLink(hyperlink), + new Paragraph({ + children: [new PageBreak()], + }), + new Paragraph({ + children: [hyperlink], + }), ], }); diff --git a/demo/35-hyperlinks.ts b/demo/35-hyperlinks.ts index c547d93b36..6392299b84 100644 --- a/demo/35-hyperlinks.ts +++ b/demo/35-hyperlinks.ts @@ -4,12 +4,14 @@ import * as fs from "fs"; import { Document, Packer, Paragraph } from "../build"; const doc = new Document(); -const paragraph = new Paragraph({}); const link = doc.createHyperlink("http://www.example.com", "Hyperlink"); -paragraph.addHyperLink(link); doc.addSection({ - children: [paragraph], + children: [ + new Paragraph({ + children: [link], + }), + ], }); Packer.toBuffer(doc).then((buffer) => { diff --git a/docs/usage/paragraph.md b/docs/usage/paragraph.md index c770e8c00f..f428bd7374 100644 --- a/docs/usage/paragraph.md +++ b/docs/usage/paragraph.md @@ -244,10 +244,15 @@ The above example will create a heading with a page break directly under it. ## Page Break -To move to a new page (insert a page break), simply add `.pageBreak()` on a paragraph: +To move to a new page (insert a page break): ```ts -const paragraph = new docx.Paragraph("Amazing Heading").pageBreak(); +const paragraph = new docx.Paragraph({ + children: [ + new TextRun("Amazing Heading"), + new PageBreak(), + ] +}); ``` The above example will create a heading and start a new page immediately afterwards. diff --git a/docs/usage/tab-stops.md b/docs/usage/tab-stops.md index e172c7f9b0..e266c0b310 100644 --- a/docs/usage/tab-stops.md +++ b/docs/usage/tab-stops.md @@ -2,7 +2,7 @@ > Tab stops are useful, if you are unclear of what they are, [here is a link explaining](https://en.wikipedia.org/wiki/Tab_stop). It enables side by side text which is nicely laid out without the need for tables, or constantly pressing space bar. -!> **Note**: At the moment, the unit of measurement for a tab stop is counter intuitive for a human. It is using OpenXMLs own measuring system. For example, 2268 roughly translates to 3cm. Therefore in the future, I may consider changing it to percentages or even cm. +!> **Note**: The unit of measurement for a tab stop is in [DXA](https://stackoverflow.com/questions/14360183/default-wordml-unit-measurement-pixel-or-point-or-inches) ![Word 2013 Tabs](http://www.teachucomp.com/wp-content/uploads/blog-4-22-2015-UsingTabStopsInWord-1024x577.png "Word 2013 Tab Stops") @@ -11,44 +11,86 @@ Simply call the relevant methods on the paragraph listed below. Then just add a ## Example ```ts -const paragraph = new docx.Paragraph().maxRightTabStop(); -const leftText = new docx.TextRun("Hey everyone").bold(); -const rightText = new docx.TextRun("11th November 2015").tab(); -paragraph.addRun(leftText); -paragraph.addRun(rightText); +const paragraph = new Paragraph({ + children: [new TextRun("Hey everyone").bold(), new TextRun("11th November 1999").tab()], + tabStop: { + right: { + position: TabStopPosition.MAX, + }, + }, +}); ``` -The example above will create a left aligned text, and a right aligned text on the same line. The laymans approach to this problem would be to either use text boxes or tables. YUK! + +The example above will create a left aligned text, and a right aligned text on the same line. The laymans approach to this problem would be to either use text boxes or tables. Not ideal! ```ts -const paragraph = new docx.Paragraph(); -paragraph.maxRightTabStop(); -paragraph.leftTabStop(1000); -const text = new docx.TextRun("Second tab stop here I come!").tab().tab(); -paragraph.addRun(text); +const paragraph = new Paragraph({ + children: [new TextRun("Second tab stop here I come!").tab().tab()], + tabStop: { + right: { + position: TabStopPosition.MAX, + }, + left: { + position: 1000, + }, + }, +}); ``` The above shows the use of two tab stops, and how to select/use it. ## Left Tab Stop + ```ts -paragraph.leftTabStop(2268); +const paragraph = new Paragraph({ + tabStop: { + left: { + position: 2268, + }, + }, +}); ``` + 2268 is the distance from the left side. ## Center Tab Stop + ```ts -paragraph.centerTabStop(2268); +const paragraph = new Paragraph({ + tabStop: { + center: { + position: 2268, + }, + }, +}); ``` -2268 is the distance from the left side. + +2268 is the distance from the center. ## Right Tab Stop + ```ts -paragraph.rightTabStop(2268); +const paragraph = new Paragraph({ + tabStop: { + right: { + position: 2268, + }, + }, +}); ``` -2268 is the distance from the left side. + +2268 is the distance fro0oum the left side. ## Max Right Tab Stop + ```ts -paragraph.maxRightTabStop(); +const paragraph = new Paragraph({ + tabStop: { + right: { + position: TabStopPosition.MAX, + }, + }, +}); ``` + This will create a tab stop on the very edge of the right hand side. Handy for right aligning and left aligning text on the same line. diff --git a/docs/usage/table-of-contents.md b/docs/usage/table-of-contents.md index 00dc329ad9..ea5ff2afd5 100644 --- a/docs/usage/table-of-contents.md +++ b/docs/usage/table-of-contents.md @@ -47,30 +47,6 @@ Here is the list of all options that you can use to generate your tables of cont ## Examples -```ts -// Let's define the options for generate a TOC for heading 1-5 and MySpectacularStyle, -// making the entries be hyperlinks for the paragraph -const toc = new TableOfContents("Summary", { - hyperlink: true, - headingStyleRange: "1-5", - stylesWithLevels: [new StyleLevel("MySpectacularStyle", 1)], -}); - -doc.addTableOfContents(toc); - -doc.add(new Paragraph("Header #1").heading1().pageBreakBefore()); -doc.add(new Paragraph("I'm a little text, very nicely written.'")); - -doc.add(new Paragraph("Header #2").heading1().pageBreakBefore()); -doc.add(new Paragraph("I'm another text very nicely written.'")); -doc.add(new Paragraph("Header #2.1").heading2()); -doc.add(new Paragraph("I'm another text very nicely written.'")); - -doc.add(new Paragraph("My Spectacular Style #1").style("MySpectacularStyle").pageBreakBefore()); -``` - -### Complete example - [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/28-table-of-contents.ts ':include') _Source: https://github.com/dolanmiu/docx/blob/master/demo/28-table-of-contents.ts_ diff --git a/src/export/formatter.spec.ts b/src/export/formatter.spec.ts index 7ee05fa5d1..da772db2b1 100644 --- a/src/export/formatter.spec.ts +++ b/src/export/formatter.spec.ts @@ -28,15 +28,52 @@ describe("Formatter", () => { }); it("should format simple paragraph with bold text", () => { - const paragraph = new Paragraph(""); - paragraph.addRun( - new TextRun({ - text: "test", - bold: true, - }), - ); - const newJson = formatter.format(paragraph); - assert.isDefined(newJson["w:p"][1]["w:r"][0]["w:rPr"][0]["w:b"]._attr["w:val"]); + const paragraph = new Paragraph({ + children: [ + new TextRun({ + text: "test", + bold: true, + }), + ], + }); + + const tree = formatter.format(paragraph); + expect(tree).to.deep.equal({ + "w:p": [ + { + "w:r": [ + { + "w:rPr": [ + { + "w:b": { + _attr: { + "w:val": true, + }, + }, + }, + { + "w:bCs": { + _attr: { + "w:val": true, + }, + }, + }, + ], + }, + { + "w:t": [ + { + _attr: { + "xml:space": "preserve", + }, + }, + "test", + ], + }, + ], + }, + ], + }); }); it("should format attributes (rsidSect)", () => { diff --git a/src/file/footnotes/footnotes.ts b/src/file/footnotes/footnotes.ts index 2fea5fe380..91ba1d199d 100644 --- a/src/file/footnotes/footnotes.ts +++ b/src/file/footnotes/footnotes.ts @@ -44,7 +44,8 @@ export class FootNotes extends XmlComponent { line: 240, lineRule: "auto", }, - }).addRun(new SeperatorRun()), + children: [new SeperatorRun()], + }), ); this.root.push(begin); @@ -56,7 +57,8 @@ export class FootNotes extends XmlComponent { line: 240, lineRule: "auto", }, - }).addRun(new ContinuationSeperatorRun()), + children: [new ContinuationSeperatorRun()], + }), ); this.root.push(spacing); } diff --git a/src/file/numbering/level.ts b/src/file/numbering/level.ts index 8a266fa47f..26d0849fc4 100644 --- a/src/file/numbering/level.ts +++ b/src/file/numbering/level.ts @@ -8,8 +8,9 @@ import { KeepLines, KeepNext, LeftTabStop, - MaxRightTabStop, + RightTabStop, Spacing, + TabStopPosition, ThematicBreak, } from "../paragraph/formatting"; import { ParagraphProperties } from "../paragraph/properties"; @@ -235,9 +236,8 @@ export class LevelBase extends XmlComponent { return this; } - public maxRightTabStop(): Level { - this.addParagraphProperty(new MaxRightTabStop()); - return this; + public rightTabStop(position: number): Level { + return this.addParagraphProperty(new RightTabStop(position)); } public leftTabStop(position: number): Level { diff --git a/src/file/numbering/numbering.spec.ts b/src/file/numbering/numbering.spec.ts index f38261b071..cbf7203a06 100644 --- a/src/file/numbering/numbering.spec.ts +++ b/src/file/numbering/numbering.spec.ts @@ -8,6 +8,7 @@ import { Num } from "./num"; import { Numbering } from "./numbering"; import { EMPTY_OBJECT } from "file/xml-components"; +import { TabStopPosition } from "../paragraph"; describe("Numbering", () => { let numbering: Numbering; @@ -202,7 +203,7 @@ describe("AbstractNumbering", () => { it("#maxRightTabStop", () => { const abstractNumbering = new AbstractNumbering(1); - const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").maxRightTabStop(); + const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").rightTabStop(TabStopPosition.MAX); const tree = new Formatter().format(level); expect(tree["w:lvl"]).to.include({ "w:pPr": [ diff --git a/src/file/paragraph/formatting/tab-stop.spec.ts b/src/file/paragraph/formatting/tab-stop.spec.ts index ac5e563fa5..558289ba8e 100644 --- a/src/file/paragraph/formatting/tab-stop.spec.ts +++ b/src/file/paragraph/formatting/tab-stop.spec.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import { Utility } from "tests/utility"; -import { LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./tab-stop"; +import { LeaderType, LeftTabStop, RightTabStop } from "./tab-stop"; describe("LeftTabStop", () => { let tabStop: LeftTabStop; @@ -52,28 +52,3 @@ describe("RightTabStop", () => { }); }); }); - -describe("MaxRightTabStop", () => { - let tabStop: MaxRightTabStop; - - beforeEach(() => { - tabStop = new MaxRightTabStop(); - }); - - describe("#constructor()", () => { - it("should create a Tab Stop with correct attributes", () => { - const newJson = Utility.jsonify(tabStop); - - const attributes = { - val: "right", - pos: 9026, - }; - assert.equal(JSON.stringify(newJson.root[0].root[0].root), JSON.stringify(attributes)); - }); - - it("should create a Tab Stop with w:tab", () => { - const newJson = Utility.jsonify(tabStop); - assert.equal(newJson.root[0].rootKey, "w:tab"); - }); - }); -}); diff --git a/src/file/paragraph/formatting/tab-stop.ts b/src/file/paragraph/formatting/tab-stop.ts index 2706c6f43d..e934b80c06 100644 --- a/src/file/paragraph/formatting/tab-stop.ts +++ b/src/file/paragraph/formatting/tab-stop.ts @@ -28,6 +28,10 @@ export enum LeaderType { UNDERSCORE = "underscore", } +export enum TabStopPosition { + MAX = 9026, +} + export class TabAttributes extends XmlAttributeComponent<{ readonly val: TabValue; readonly pos: string | number; @@ -49,12 +53,6 @@ export class TabStopItem extends XmlComponent { } } -export class MaxRightTabStop extends TabStop { - constructor(leader?: LeaderType) { - super(new TabStopItem(TabValue.RIGHT, 9026, leader)); - } -} - export class LeftTabStop extends TabStop { constructor(position: number, leader?: LeaderType) { super(new TabStopItem(TabValue.LEFT, position, leader)); diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 91ee616a4e..0536e44505 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -4,7 +4,7 @@ import { Formatter } from "export/formatter"; import { EMPTY_OBJECT } from "file/xml-components"; import { Numbering } from "../numbering"; -import { AlignmentType, HeadingLevel, LeaderType } from "./formatting"; +import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition } from "./formatting"; import { Paragraph } from "./paragraph"; describe("Paragraph", () => { @@ -254,10 +254,12 @@ describe("Paragraph", () => { }); describe("#maxRightTabStop()", () => { - it("should add maxRightTabStop to JSON", () => { + it("should add right tab stop to JSON", () => { const paragraph = new Paragraph({ tabStop: { - maxRight: {}, + right: { + position: TabStopPosition.MAX, + }, }, }); const tree = new Formatter().format(paragraph); @@ -492,8 +494,9 @@ describe("Paragraph", () => { describe("#pageBreak()", () => { it("should add page break to JSON", () => { - const paragraph = new Paragraph({}); - paragraph.pageBreak(); + const paragraph = new Paragraph({ + children: [new PageBreak()], + }); const tree = new Formatter().format(paragraph); expect(tree).to.deep.equal({ "w:p": [ diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 24c3e1717d..494a4fc831 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -11,14 +11,14 @@ import { KeepLines, KeepNext } from "./formatting/keep"; import { PageBreak, PageBreakBefore } from "./formatting/page-break"; import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spacing"; import { HeadingLevel, Style } from "./formatting/style"; -import { CenterTabStop, LeaderType, LeftTabStop, MaxRightTabStop, RightTabStop } from "./formatting/tab-stop"; +import { CenterTabStop, LeaderType, LeftTabStop, RightTabStop, TabStopPosition } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; import { Bookmark, Hyperlink, OutlineLevel } from "./links"; import { ParagraphProperties } from "./properties"; import { PictureRun, Run, SequentialIdentifier, TextRun } from "./run"; interface ITabStopOptions { - readonly position: number; + readonly position: number | TabStopPosition; readonly leader?: LeaderType; } @@ -39,9 +39,6 @@ export interface IParagraphOptions { readonly tabStop?: { readonly left?: ITabStopOptions; readonly right?: ITabStopOptions; - readonly maxRight?: { - readonly leader?: LeaderType; - }; readonly center?: ITabStopOptions; }; readonly style?: string; @@ -53,7 +50,7 @@ export interface IParagraphOptions { readonly level: number; readonly custom?: boolean; }; - readonly children?: Array; + readonly children?: Array; } export class Paragraph extends XmlComponent { @@ -139,10 +136,6 @@ export class Paragraph extends XmlComponent { this.properties.push(new RightTabStop(options.tabStop.right.position, options.tabStop.right.leader)); } - if (options.tabStop.maxRight) { - this.properties.push(new MaxRightTabStop(options.tabStop.maxRight.leader)); - } - if (options.tabStop.center) { this.properties.push(new CenterTabStop(options.tabStop.center.position, options.tabStop.center.leader)); } @@ -166,34 +159,18 @@ export class Paragraph extends XmlComponent { if (options.children) { for (const child of options.children) { + if (child instanceof Bookmark) { + this.root.push(child.start); + this.root.push(child.text); + this.root.push(child.end); + continue; + } + this.root.push(child); } } } - public addRun(run: Run): Paragraph { - this.root.push(run); - return this; - } - - public addHyperLink(hyperlink: Hyperlink): Paragraph { - this.root.push(hyperlink); - return this; - } - - public addBookmark(bookmark: Bookmark): Paragraph { - // Bookmarks by spec have three components, a start, text, and end - this.root.push(bookmark.start); - this.root.push(bookmark.text); - this.root.push(bookmark.end); - return this; - } - - public pageBreak(): Paragraph { - this.root.push(new PageBreak()); - return this; - } - public referenceFootnote(id: number): Paragraph { this.root.push(new FootnoteReferenceRun(id)); return this; diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts index c147fe5e67..e586f8807d 100644 --- a/src/file/paragraph/run/run.ts +++ b/src/file/paragraph/run/run.ts @@ -2,6 +2,7 @@ import { ShadingType } from "file/table"; import { XmlComponent } from "file/xml-components"; +import { FieldInstruction } from "file/table-of-contents/field-instruction"; import { Break } from "./break"; import { Caps, SmallCaps } from "./caps"; import { Begin, End, Separate } from "./field"; @@ -56,6 +57,7 @@ export interface IRunOptions { readonly fill: string; readonly color: string; }; + readonly children?: Array; } export class Run extends XmlComponent { @@ -134,6 +136,12 @@ export class Run extends XmlComponent { this.properties.push(new Shading(options.shading.type, options.shading.fill, options.shading.color)); this.properties.push(new ShadowComplexScript(options.shading.type, options.shading.fill, options.shading.color)); } + + if (options.children) { + for (const child of options.children) { + this.root.push(child); + } + } } public break(): Run { diff --git a/src/file/styles/style/paragraph-style.spec.ts b/src/file/styles/style/paragraph-style.spec.ts index 91f109e11e..d3a77b9a15 100644 --- a/src/file/styles/style/paragraph-style.spec.ts +++ b/src/file/styles/style/paragraph-style.spec.ts @@ -1,11 +1,11 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { TabStopPosition } from "file/paragraph"; +import { EMPTY_OBJECT } from "file/xml-components"; import { ParagraphStyle } from "./paragraph-style"; -import { EMPTY_OBJECT } from "file/xml-components"; - describe("ParagraphStyle", () => { describe("#constructor", () => { it("should set the style type to paragraph and use the given style id", () => { @@ -198,7 +198,7 @@ describe("ParagraphStyle", () => { }); it("#maxRightTabStop", () => { - const style = new ParagraphStyle("myStyleId").maxRightTabStop(); + const style = new ParagraphStyle("myStyleId").rightTabStop(TabStopPosition.MAX); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ "w:style": [ diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index e79c6abe17..360e2924de 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -6,12 +6,12 @@ import { KeepLines, KeepNext, LeftTabStop, - MaxRightTabStop, OutlineLevel, ParagraphProperties, Spacing, ThematicBreak, } from "file/paragraph"; +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"; @@ -144,8 +144,8 @@ export class ParagraphStyle extends Style { return this.addParagraphProperty(new ThematicBreak()); } - public maxRightTabStop(): ParagraphStyle { - return this.addParagraphProperty(new MaxRightTabStop()); + public rightTabStop(position: number): ParagraphStyle { + return this.addParagraphProperty(new RightTabStop(position)); } public leftTabStop(position: number): ParagraphStyle { diff --git a/src/file/table-of-contents/table-of-contents.ts b/src/file/table-of-contents/table-of-contents.ts index 57acbb3ace..e3d874e4a3 100644 --- a/src/file/table-of-contents/table-of-contents.ts +++ b/src/file/table-of-contents/table-of-contents.ts @@ -16,18 +16,24 @@ export class TableOfContents extends XmlComponent { const content = new StructuredDocumentTagContent(); - const beginParagraph = new Paragraph({}); - const beginRun = new Run({}); - beginRun.addChildElement(new Begin(true)); - beginRun.addChildElement(new FieldInstruction(properties)); - beginRun.addChildElement(new Separate()); - beginParagraph.addRun(beginRun); + const beginParagraph = new Paragraph({ + children: [ + new Run({ + children: [new Begin(true), new FieldInstruction(properties), new Separate()], + }), + ], + }); + content.addChildElement(beginParagraph); - const endParagraph = new Paragraph({}); - const endRun = new Run({}); - endRun.addChildElement(new End()); - endParagraph.addRun(endRun); + const endParagraph = new Paragraph({ + children: [ + new Run({ + children: [new End()], + }), + ], + }); + content.addChildElement(endParagraph); this.root.push(content); From 591b2f4e0470b715767c1dff2846db0ea35ec97a Mon Sep 17 00:00:00 2001 From: Dolan Date: Fri, 4 Oct 2019 01:20:41 +0100 Subject: [PATCH 02/12] Declarative styles --- demo/2-declaritive-styles.ts | 111 ++++--- src/file/core-properties/properties.ts | 2 + src/file/file.ts | 9 +- src/file/numbering/level.ts | 4 +- src/file/numbering/numbering.spec.ts | 5 +- src/file/paragraph/run/index.ts | 1 + src/file/paragraph/run/underline.spec.ts | 2 +- src/file/paragraph/run/underline.ts | 2 +- src/file/styles/external-styles-factory.ts | 10 +- src/file/styles/factory.ts | 103 +++---- src/file/styles/style/character-style.spec.ts | 127 ++++++-- src/file/styles/style/character-style.ts | 147 ++++++--- src/file/styles/style/components.spec.ts | 4 +- src/file/styles/style/components.ts | 5 +- src/file/styles/style/default-styles.spec.ts | 60 ++-- src/file/styles/style/default-styles.ts | 172 +++++++---- src/file/styles/style/paragraph-style.spec.ts | 234 ++++++++++++--- src/file/styles/style/paragraph-style.ts | 284 ++++++++++-------- src/file/styles/styles.spec.ts | 62 ++-- src/file/styles/styles.ts | 60 ++-- 20 files changed, 920 insertions(+), 484 deletions(-) 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; - } } From 10ab3c70bf728f3820fc4f5326bb40d6c3e58678 Mon Sep 17 00:00:00 2001 From: Dolan Date: Fri, 4 Oct 2019 02:37:22 +0100 Subject: [PATCH 03/12] Add back default styles --- demo/2-declaritive-styles.ts | 6 +++--- src/file/core-properties/properties.ts | 5 +++-- src/file/file.ts | 9 +++++++-- src/file/styles/factory.ts | 11 ++++------- src/file/styles/styles.ts | 21 +++++++-------------- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/demo/2-declaritive-styles.ts b/demo/2-declaritive-styles.ts index 04502264a3..eaf39daffe 100644 --- a/demo/2-declaritive-styles.ts +++ b/demo/2-declaritive-styles.ts @@ -1,13 +1,13 @@ // 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, Styles, TextRun, UnderlineType } from "../build"; +import { Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build"; const doc = new Document({ creator: "Clippy", title: "Sample Document", description: "A brief example of using docx", - styles: new Styles({ + styles: { paragraphStyles: [ { id: "Heading1", @@ -81,7 +81,7 @@ const doc = new Document({ quickFormat: true, }, ], - }), + }, }); const numberedAbstract = doc.Numbering.createAbstractNumbering(); diff --git a/src/file/core-properties/properties.ts b/src/file/core-properties/properties.ts index 7d3e447523..a99c74d98f 100644 --- a/src/file/core-properties/properties.ts +++ b/src/file/core-properties/properties.ts @@ -1,6 +1,7 @@ import { XmlComponent } from "file/xml-components"; + import { DocumentAttributes } from "../document/document-attributes"; -import { Styles } from "../styles"; +import { IStylesOptions } from "../styles"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; export interface IPropertiesOptions { @@ -12,7 +13,7 @@ export interface IPropertiesOptions { readonly lastModifiedBy?: string; readonly revision?: string; readonly externalStyles?: string; - readonly styles?: Styles; + readonly styles?: IStylesOptions; } export class CoreProperties extends XmlComponent { diff --git a/src/file/file.ts b/src/file/file.ts index f68760328d..ccf5fe374c 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -97,10 +97,15 @@ export class File { const stylesFactory = new ExternalStylesFactory(); this.styles = stylesFactory.newInstance(options.externalStyles); } else if (options.styles) { - this.styles = options.styles; + const stylesFactory = new DefaultStylesFactory(); + const defaultStyles = stylesFactory.newInstance(); + this.styles = new Styles({ + ...defaultStyles, + ...options.styles, + }); } else { const stylesFactory = new DefaultStylesFactory(); - this.styles = stylesFactory.newInstance(); + this.styles = new Styles(stylesFactory.newInstance()); } this.addDefaultRelationships(); diff --git a/src/file/styles/factory.ts b/src/file/styles/factory.ts index d43f46b732..f5bca8248d 100644 --- a/src/file/styles/factory.ts +++ b/src/file/styles/factory.ts @@ -1,5 +1,5 @@ import { DocumentAttributes } from "../document/document-attributes"; -import { Styles } from "./styles"; +import { IStylesOptions } from "./styles"; import { DocumentDefaults } from "./defaults"; import { @@ -18,7 +18,7 @@ import { } from "./style"; export class DefaultStylesFactory { - public newInstance(): Styles { + public newInstance(): IStylesOptions { const documentAttributes = new DocumentAttributes({ mc: "http://schemas.openxmlformats.org/markup-compatibility/2006", r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships", @@ -27,7 +27,7 @@ export class DefaultStylesFactory { w15: "http://schemas.microsoft.com/office/word/2012/wordml", Ignorable: "w14 w15", }); - const styles = new Styles({ + return { initialStyles: documentAttributes, importedStyles: [ new DocumentDefaults(), @@ -76,9 +76,6 @@ export class DefaultStylesFactory { new FootnoteText({}), new FootnoteTextChar({}), ], - }); - styles.createDocumentDefaults(); - - return styles; + }; } } diff --git a/src/file/styles/styles.ts b/src/file/styles/styles.ts index 6e5baa8f2a..064411e419 100644 --- a/src/file/styles/styles.ts +++ b/src/file/styles/styles.ts @@ -1,12 +1,11 @@ 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"; -interface IStylesOptions { +export interface IStylesOptions { readonly initialStyles?: BaseXmlComponent; readonly paragraphStyles?: IParagraphStyleOptions[]; readonly characterStyles?: ICharacterStyleOptions[]; @@ -21,6 +20,12 @@ export class Styles extends XmlComponent { this.root.push(options.initialStyles); } + if (options.importedStyles) { + for (const style of options.importedStyles) { + this.root.push(style); + } + } + if (options.paragraphStyles) { for (const style of options.paragraphStyles) { this.root.push(new ParagraphStyle(style)); @@ -32,17 +37,5 @@ export class Styles extends XmlComponent { 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.root.push(defaults); - return defaults; } } From 0d4c7a5fc0c93d3d8bc1e6d8603598b08db816c0 Mon Sep 17 00:00:00 2001 From: Dolan Date: Wed, 9 Oct 2019 02:03:39 +0100 Subject: [PATCH 04/12] Update documentation --- docs/usage/styling-with-js.md | 227 ++++++++++++++--------- src/file/styles/style/paragraph-style.ts | 4 +- 2 files changed, 138 insertions(+), 93 deletions(-) diff --git a/docs/usage/styling-with-js.md b/docs/usage/styling-with-js.md index c39e7a7f40..26d294c942 100644 --- a/docs/usage/styling-with-js.md +++ b/docs/usage/styling-with-js.md @@ -3,112 +3,147 @@ ## Example ```ts -const para = new Paragraph("To whom it may concern:").heading2().center(); +const para = new Paragraph({ + text: "To whom it may concern:", + heading: HeadingLevel.HEADING_2, + alignment: AlignmentType.CENTER, +}); -const name = new TextRun("Name:") - .bold() - .font("Calibri") - .allCaps(); +const name = new TextRun({ + text: "Name:", + bold: true, + font: "Calibri", + allCaps: true, +}); ``` -## Available methods +## Available Options -* For run formatting: - * `.bold()`, `.italics()`, `.smallCaps()`, `.allCaps()`, `.strike()`, `.doubleStrike()`, `.subScript()`, `.superScript()`: Set the formatting property to true - * `.underline(style="single", color=null)`: Set the underline style and color - * `.color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`) - * `.size(halfPts)`: Set the font size, measured in half-points - * `.font(name)`: Set the run's font - * `.style(name)`: Apply a named run style - * `.characterSpacing(value)`: Set the character spacing adjustment (in TWIPs) -* For paragraph formatting: - * `.heading1()`, `.heading2()`, `.heading3()`, `.heading4()`, `.heading5()`, `.title()`: apply the appropriate style to the paragraph - * `.left()`, `.center()`, `.right()`, `.justified()`: set the paragraph's alignment - * `.thematicBreak()`, `.pageBreak()`: Insert a thick rule or a page break beneath the paragraph - * `.leftTabStop(position)`: Add a left tab stop (measured in TWIPs from the left) - * `.maxRightTabStop()`: Add a right tab stop at the far right - * `.bullet()`: Use the default bullet style - * `.setNumbering(numbering, indentLevel)`: Use a custom numbering format for the paragraph - * `.style(name)`: Apply a named paragraph style - * `.indent(start, hanging=0)`: Set the paragraph's indent level (in TWIPs) - * `.spacing({before=0, after=0, line=0})`: Set the line and before/after on the paragraph. Before/after is measured in TWIPs, line is measured in 240ths of a line +### Run formatting -Paragraph styles have all the run formatting methods, except `style()`, and `.left()`, `.center()`, `.right()`, `.justified()`, `.thematicBreak()`, `.leftTabStop(position)`, `.maxRightTabStop()`, `.indent(start, hanging=0)`, and `.spacing({before=0, after=0, line=0})` methods. +- `bold`, `italics`, `smallCaps`, `allCaps`, `strike`, `doubleStrike`, `subScript`, `superScript`: Set the formatting property to true +- `underline(style="single", color=null)`: Set the underline style and color +- `color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`) +- `size(halfPts)`: Set the font size, measured in half-points +- `font(name)`: Set the run's font +- `style(name)`: Apply a named run style +- `characterSpacing(value)`: Set the character spacing adjustment (in TWIPs) + +### Paragraph formatting + +- `heading1`, `heading2`, `heading3`, `heading4`, `heading5`, `title`: apply the appropriate style to the paragraph +- `left`, `center`, `right`, `justified`: set the paragraph's alignment +- `thematicBreak`, `pageBreak`: Insert a thick rule or a page break beneath the paragraph +- `leftTabStop(position)`: Add a left tab stop (measured in TWIPs from the left) +- `maxRightTabStop`: Add a right tab stop at the far right +- `bullet`: Use the default bullet style +- `setNumbering(numbering, indentLevel)`: Use a custom numbering format for the paragraph +- `style(name)`: Apply a named paragraph style +- `indent(start, hanging=0)`: Set the paragraph's indent level (in TWIPs) +- `spacing({before=0, after=0, line=0})`: Set the line and before/after on the paragraph. Before/after is measured in TWIPs, line is measured in 240ths of a line + +Paragraph styles have all the run formatting methods, except `style()`, and `left()`, `center()`, `right()`, `justified()`, `thematicBreak()`, `leftTabStop(position)`, `maxRightTabStop()`, `indent(start, hanging=0)`, and `spacing({before=0, after=0, line=0})` methods. ## Detailed guide -There are 4 items in DOCX that can be styled: +There are 4 items in `docx` that can be styled: -* Characters: Attributes that can change within a paragraph. e.g., bold, italics, etc. -* Paragraphs: Attributes like indent, text alignment, line spacing, etc. -* Tables: Border styles, table formats, etc. -* List items: These are the numbers and bullets that are automatically inserted +- Characters: Attributes that can change within a paragraph. e.g., bold, italics, etc. +- Paragraphs: Attributes like indent, text alignment, line spacing, etc. +- Tables: Border styles, table formats, etc. +- List items: These are the numbers and bullets that are automatically inserted -There are a few different ways of styling this content in DOCX, which somewhat resemble the HTML/CSS approach. In order of greatest to lowest priority: +There are a few different ways of styling this content in `docx`, which somewhat resemble the HTML/CSS approach. In order of greatest to lowest priority: -1. Direct formatting (AKA inline formatting) -2. Centrally defined styles (similar to external CSS) +1. Direct formatting (inline formatting) +2. Declaritive Styles (similar to external CSS) 3. Document defaults (similar to a `*` rule in CSS) Unlike CSS, less specific rules don't _necessarily_ override parent rules. The rules are a bit wonky, but if you're interested, see the [advanced formatting section](#Advanced formatting). -### Direct formatting (AKA inline formatting) +### Direct formatting (inline formatting) -This is the type of formatting that your uncle uses when he types out documents: _N ... a ... m ... e ... :_ Then he grabs the mouse, highlights _Name:_ and moves over to the **B** for bold. This manner of formatting results in markup that is similar to writing `Name:` if you were typing out HTML. DOCX (the format) allows you to specify this for any of the four types of items. `docx` (the library) only supports this type of formatting for paragraphs and characters, using a _fluent_ api. Thus you could do: +This is the type of formatting that your uncle uses when he types out documents: _N ... a ... m ... e ... :_ Then he grabs the mouse, highlights _Name:_ and moves over to the **B** for bold. This manner of formatting results in markup that is similar to writing `Name:` if you were typing out HTML. `docx` (the format) allows you to specify this for any of the four types of items. `docx` (the library) only supports this type of formatting for paragraphs and characters, using a _fluent_ api. Thus you could do: ```ts -const name = new TextRun("Name:") - .bold() - .font("Calibri") - .allCaps(); +const name = new TextRun({ + text: "Name:", + bold: true, + font: "Calibri", + allCaps: true, +}); ``` Or for paragraph formatting: ```ts -const para = new Paragraph("To whom it may concern:").heading2().center(); +const para = new Paragraph({ + text: "To whom it may concern:", + heading: HeadingLevel.HEADING_2, + alignment: AlignmentType.CENTER, +}); ``` -### Centrally defined styles (similar to external CSS) +### Declaritive Styles (similar to external CSS) -DOCX files contain a styles section separate from the main content, much like how HTML includes CSS files. Unlike CSS, DOCX distinguishes between styles meant for tables (which show up in the table formatting toolbar), styles for lists (which show up under bullets and numbering), and styles for runs and paragraphs, which show up as dropdowns offering standard styles, like "Heading 1", "Caption", or any custom styles defined in that document. . `docx` allows you to define these styles using a fluent interface as well. +`docx` files contain a styles section separate from the main content, much like how HTML includes CSS files. Unlike CSS, `docx` distinguishes between styles meant for tables (which show up in the table formatting toolbar), styles for lists (which show up under bullets and numbering), and styles for runs and paragraphs, which show up as dropdowns offering standard styles, like "Heading 1", "Caption", or any custom styles defined in that document. . `docx` allows you to define these styles using a fluent interface as well. -There are three parts to using custom styles with `docx`: +To add styles, define your custom styles in the `document`: -1. Create a container object for the style definitions: - ```ts - const myStyles = new docx.Styles(); - ``` -2. Define your custom styles, similar to the way you would format a paragraph or run - - ```ts - // The first argument is an ID you use to apply the style to paragraphs - // The second argument is a human-friendly name to show in the UI - myStyles - .createParagraphStyle("myWonkyStyle", "My Wonky Style") - .basedOn("Normal") - .next("Normal") - .color("999999") - .italics() - .indent(720) // 720 TWIP === 720 / 20 pt === .5 in - .spacing({ line: 276 }); // 276 / 240 = 1.15x line spacing - - myStyles - .createParagraphStyle("Heading2", "Heading 2") - .basedOn("Normal") - .next("Normal") - .quickFormat() - .size(26) // 26 half-points === 13pt font - .bold() - .underline("double", "FF0000") - .spacing({ before: 240, after: 120 }); // TWIP for both - ``` - -3. When you generate your document, make sure to pass the `styles` container to the `Packer`: - - ```ts - Packer.pack(myOutStream); - ``` +```ts +// The first argument is an ID you use to apply the style to paragraphs +// The second argument is a human-friendly name to show in the UI +const doc = new Document({ + creator: "Clippy", + title: "Sample Document", + description: "A brief example of using docx", + styles: { + paragraphStyles: [ + { + id: "myWonkyStyle", + name: "My Wonky Style", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + italics: true, + color: "999999", + }, + paragraph: { + spacing: { + line: 276, + }, + indent: { + left: 720, + }, + }, + }, + { + id: "Heading2", + name: "Heading 2", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + size: 26 + bold: true, + color: "999999", + { + type: UnderlineType.DOUBLE, + color: "FF0000", + }, + }, + paragraph: { + spacing: { + before: 240, + after: 120 + }, + }, + }, + ] + } +}); +``` **Note**: If you are using the `.headingX` or `.title` methods of paragraphs, you must make sure to define `HeadingX` or `Title` styles for these. Otherwise they'll show up unstyled :(. If you are using the `.bullet` or `.setNumbering` methods, you need to define a `ListParagraph` style or the numbers may not show up. @@ -144,19 +179,29 @@ To determine the value of a styling property, you must first identify whether it The following properties are treated in a special manner; they're called toggle properties: -* Bold -* All caps -* Small caps -* Italics -* Single strike-through -* Hidden -* Imprint -* Emboss -* Character outline -* Character shadow +- Bold +- All caps +- Small caps +- Italics +- Single strike-through +- Hidden +- Imprint +- Emboss +- Character outline +- Character shadow For these properties, the rules state the following conflict resolution in case the property is specified at multiple points for the same item: -* Direct formatting trumps all if specified (either true or false) -* Otherwise, if the property is true in document defaults, the property is set to true -* Otherwise, the property's value is an XOR of its effective table, paragraph, and character values. (So specifying bold `true` on a table style and a paragraph style would result in non-bold text if a paragraph inside the table had that style) +- Direct formatting trumps all if specified (either true or false) +- Otherwise, if the property is true in document defaults, the property is set to true +- Otherwise, the property's value is an XOR of its effective table, paragraph, and character values. (So specifying bold `true` on a table style and a paragraph style would result in non-bold text if a paragraph inside the table had that style) + +## Examples + +### Declaritive styles + +Importing Images from file system path + +[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/2-declaritive-styles.ts ':include') + +_Source: https://github.com/dolanmiu/docx/blob/master/demo/2-declaritive-styles.ts_ diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index ab9f75ccec..077df26292 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -11,7 +11,7 @@ import { Spacing, ThematicBreak, } from "file/paragraph"; -import { RightTabStop } from "file/paragraph/formatting"; +import { IIndentAttributesProperties, RightTabStop } from "file/paragraph/formatting"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; import { UnderlineType } from "file/paragraph/run/underline"; @@ -57,7 +57,7 @@ export interface IBaseParagraphStyleOptions { readonly thematicBreak?: boolean; readonly rightTabStop?: number; readonly leftTabStop?: number; - readonly indent?: object; + readonly indent?: IIndentAttributesProperties; readonly spacing?: ISpacingProperties; readonly keepNext?: boolean; readonly keepLines?: boolean; From 40d1a3a7c2c3e9e4cd22e0709fd6856141cee8cf Mon Sep 17 00:00:00 2001 From: Dolan Date: Wed, 9 Oct 2019 20:56:31 +0100 Subject: [PATCH 05/12] Multiple tab stops --- demo/10-my-cv.ts | 9 +-- docs/usage/tab-stops.md | 63 +++++++++++++------ src/file/numbering/level.ts | 8 +-- .../paragraph/formatting/tab-stop.spec.ts | 10 +-- src/file/paragraph/formatting/tab-stop.ts | 28 ++------- src/file/paragraph/paragraph.spec.ts | 30 +++++---- src/file/paragraph/paragraph.ts | 31 +++------ src/file/styles/style/paragraph-style.ts | 7 +-- 8 files changed, 92 insertions(+), 94 deletions(-) diff --git a/demo/10-my-cv.ts b/demo/10-my-cv.ts index 75c5d9e8bd..b3e7c55e01 100644 --- a/demo/10-my-cv.ts +++ b/demo/10-my-cv.ts @@ -1,7 +1,7 @@ // Generate a CV // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TabStopPosition, TextRun } from "../build"; +import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TabStopPosition, TabStopType, TextRun } from "../build"; // tslint:disable:no-shadowed-variable @@ -226,11 +226,12 @@ class DocumentCreator { public createInstitutionHeader(institutionName: string, dateText: string): Paragraph { return new Paragraph({ - tabStop: { - right: { + tabStops: [ + { + type: TabStopType.RIGHT, position: TabStopPosition.MAX, }, - }, + ], children: [ new TextRun({ text: institutionName, diff --git a/docs/usage/tab-stops.md b/docs/usage/tab-stops.md index e266c0b310..5c7a99da42 100644 --- a/docs/usage/tab-stops.md +++ b/docs/usage/tab-stops.md @@ -13,11 +13,12 @@ Simply call the relevant methods on the paragraph listed below. Then just add a ```ts const paragraph = new Paragraph({ children: [new TextRun("Hey everyone").bold(), new TextRun("11th November 1999").tab()], - tabStop: { - right: { + tabStops: [ + { + type: TabStopType.RIGHT, position: TabStopPosition.MAX, }, - }, + ], }); ``` @@ -26,28 +27,49 @@ The example above will create a left aligned text, and a right aligned text on t ```ts const paragraph = new Paragraph({ children: [new TextRun("Second tab stop here I come!").tab().tab()], - tabStop: { - right: { + tabStops: [ + { + type: TabStopType.RIGHT, position: TabStopPosition.MAX, }, - left: { + { + type: TabStopType.LEFT, position: 1000, }, - }, + ], }); ``` The above shows the use of two tab stops, and how to select/use it. +You can add multiple tab stops of the same `type` too. + +```ts +const paragraph = new Paragraph({ + children: [new TextRun("Multiple tab stops!").tab().tab()], + tabStops: [ + { + type: TabStopType.RIGHT, + position: TabStopPosition.MAX, + }, + { + type: TabStopType.RIGHT, + position: 1000, + }, + ], +}); +``` + ## Left Tab Stop ```ts const paragraph = new Paragraph({ - tabStop: { - left: { + tabStops: [ + { + type: TabStopType.LEFT, position: 2268, }, - }, + ], }); ``` @@ -57,11 +79,12 @@ const paragraph = new Paragraph({ ```ts const paragraph = new Paragraph({ - tabStop: { - center: { + tabStops: [ + { + type: TabStopType.CENTER, position: 2268, }, - }, + ], }); ``` @@ -71,11 +94,12 @@ const paragraph = new Paragraph({ ```ts const paragraph = new Paragraph({ - tabStop: { - right: { + tabStops: [ + { + type: TabStopType.RIGHT, position: 2268, }, - }, + ], }); ``` @@ -85,11 +109,12 @@ const paragraph = new Paragraph({ ```ts const paragraph = new Paragraph({ - tabStop: { - right: { + tabStops: [ + { + type: TabStopType.RIGHT, position: TabStopPosition.MAX, }, - }, + ], }); ``` diff --git a/src/file/numbering/level.ts b/src/file/numbering/level.ts index b0fa183f53..bdc65fbba4 100644 --- a/src/file/numbering/level.ts +++ b/src/file/numbering/level.ts @@ -7,9 +7,9 @@ import { ISpacingProperties, KeepLines, KeepNext, - LeftTabStop, - RightTabStop, Spacing, + TabStop, + TabStopType, ThematicBreak, } from "../paragraph/formatting"; import { ParagraphProperties } from "../paragraph/properties"; @@ -237,11 +237,11 @@ export class LevelBase extends XmlComponent { } public rightTabStop(position: number): Level { - return this.addParagraphProperty(new RightTabStop(position)); + return this.addParagraphProperty(new TabStop(TabStopType.RIGHT, position)); } public leftTabStop(position: number): Level { - this.addParagraphProperty(new LeftTabStop(position)); + this.addParagraphProperty(new TabStop(TabStopType.LEFT, position)); return this; } diff --git a/src/file/paragraph/formatting/tab-stop.spec.ts b/src/file/paragraph/formatting/tab-stop.spec.ts index 558289ba8e..377e7bb060 100644 --- a/src/file/paragraph/formatting/tab-stop.spec.ts +++ b/src/file/paragraph/formatting/tab-stop.spec.ts @@ -2,13 +2,13 @@ import { assert } from "chai"; import { Utility } from "tests/utility"; -import { LeaderType, LeftTabStop, RightTabStop } from "./tab-stop"; +import { LeaderType, TabStop, TabStopType } from "./tab-stop"; describe("LeftTabStop", () => { - let tabStop: LeftTabStop; + let tabStop: TabStop; beforeEach(() => { - tabStop = new LeftTabStop(100); + tabStop = new TabStop(TabStopType.LEFT, 100); }); describe("#constructor()", () => { @@ -29,10 +29,10 @@ describe("LeftTabStop", () => { }); describe("RightTabStop", () => { - let tabStop: RightTabStop; + let tabStop: TabStop; beforeEach(() => { - tabStop = new RightTabStop(100, LeaderType.DOT); + tabStop = new TabStop(TabStopType.RIGHT, 100, LeaderType.DOT); }); describe("#constructor()", () => { diff --git a/src/file/paragraph/formatting/tab-stop.ts b/src/file/paragraph/formatting/tab-stop.ts index e934b80c06..cfe121b119 100644 --- a/src/file/paragraph/formatting/tab-stop.ts +++ b/src/file/paragraph/formatting/tab-stop.ts @@ -2,13 +2,13 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; export class TabStop extends XmlComponent { - constructor(tab: TabStopItem) { + constructor(type: TabStopType, position: number, leader?: LeaderType) { super("w:tabs"); - this.root.push(tab); + this.root.push(new TabStopItem(type, position, leader)); } } -export enum TabValue { +export enum TabStopType { LEFT = "left", RIGHT = "right", CENTER = "center", @@ -33,7 +33,7 @@ export enum TabStopPosition { } export class TabAttributes extends XmlAttributeComponent<{ - readonly val: TabValue; + readonly val: TabStopType; readonly pos: string | number; readonly leader?: LeaderType; }> { @@ -41,7 +41,7 @@ export class TabAttributes extends XmlAttributeComponent<{ } export class TabStopItem extends XmlComponent { - constructor(value: TabValue, position: string | number, leader?: LeaderType) { + constructor(value: TabStopType, position: string | number, leader?: LeaderType) { super("w:tab"); this.root.push( new TabAttributes({ @@ -52,21 +52,3 @@ export class TabStopItem extends XmlComponent { ); } } - -export class LeftTabStop extends TabStop { - constructor(position: number, leader?: LeaderType) { - super(new TabStopItem(TabValue.LEFT, position, leader)); - } -} - -export class RightTabStop extends TabStop { - constructor(position: number, leader?: LeaderType) { - super(new TabStopItem(TabValue.RIGHT, position, leader)); - } -} - -export class CenterTabStop extends TabStop { - constructor(position: number, leader?: LeaderType) { - super(new TabStopItem(TabValue.CENTER, position, leader)); - } -} diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 0536e44505..e8e5f6c8c9 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -4,7 +4,7 @@ import { Formatter } from "export/formatter"; import { EMPTY_OBJECT } from "file/xml-components"; import { Numbering } from "../numbering"; -import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition } from "./formatting"; +import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; import { Paragraph } from "./paragraph"; describe("Paragraph", () => { @@ -256,11 +256,12 @@ describe("Paragraph", () => { describe("#maxRightTabStop()", () => { it("should add right tab stop to JSON", () => { const paragraph = new Paragraph({ - tabStop: { - right: { + tabStops: [ + { + type: TabStopType.RIGHT, position: TabStopPosition.MAX, }, - }, + ], }); const tree = new Formatter().format(paragraph); expect(tree).to.deep.equal({ @@ -289,12 +290,13 @@ describe("Paragraph", () => { describe("#leftTabStop()", () => { it("should add leftTabStop to JSON", () => { const paragraph = new Paragraph({ - tabStop: { - left: { + tabStops: [ + { + type: TabStopType.LEFT, position: 100, leader: LeaderType.HYPHEN, }, - }, + ], }); const tree = new Formatter().format(paragraph); expect(tree).to.deep.equal({ @@ -324,12 +326,13 @@ describe("Paragraph", () => { describe("#rightTabStop()", () => { it("should add rightTabStop to JSON", () => { const paragraph = new Paragraph({ - tabStop: { - right: { + tabStops: [ + { + type: TabStopType.RIGHT, position: 100, leader: LeaderType.DOT, }, - }, + ], }); const tree = new Formatter().format(paragraph); expect(tree).to.deep.equal({ @@ -359,12 +362,13 @@ describe("Paragraph", () => { describe("#centerTabStop()", () => { it("should add centerTabStop to JSON", () => { const paragraph = new Paragraph({ - tabStop: { - center: { + tabStops: [ + { + type: TabStopType.CENTER, position: 100, leader: LeaderType.MIDDLE_DOT, }, - }, + ], }); const tree = new Formatter().format(paragraph); expect(tree).to.deep.equal({ diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index cd8f3a1bcd..5d8b4e0ae0 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -11,17 +11,12 @@ import { KeepLines, KeepNext } from "./formatting/keep"; import { PageBreak, PageBreakBefore } from "./formatting/page-break"; import { ContextualSpacing, ISpacingProperties, Spacing } from "./formatting/spacing"; import { HeadingLevel, Style } from "./formatting/style"; -import { CenterTabStop, LeaderType, LeftTabStop, RightTabStop, TabStopPosition } from "./formatting/tab-stop"; +import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; import { Bookmark, Hyperlink, OutlineLevel } from "./links"; import { ParagraphProperties } from "./properties"; import { PictureRun, Run, SequentialIdentifier, SymbolRun, TextRun } from "./run"; -interface ITabStopOptions { - readonly position: number | TabStopPosition; - readonly leader?: LeaderType; -} - export interface IParagraphOptions { readonly text?: string; readonly border?: IBorderOptions; @@ -36,11 +31,11 @@ export interface IParagraphOptions { readonly indent?: IIndentAttributesProperties; readonly keepLines?: boolean; readonly keepNext?: boolean; - readonly tabStop?: { - readonly left?: ITabStopOptions; - readonly right?: ITabStopOptions; - readonly center?: ITabStopOptions; - }; + readonly tabStops?: Array<{ + readonly position: number | TabStopPosition; + readonly type: TabStopType; + readonly leader?: LeaderType; + }>; readonly style?: string; readonly bullet?: { readonly level: number; @@ -127,17 +122,9 @@ export class Paragraph extends XmlComponent { this.properties.push(new KeepNext()); } - if (options.tabStop) { - if (options.tabStop.left) { - this.properties.push(new LeftTabStop(options.tabStop.left.position, options.tabStop.left.leader)); - } - - if (options.tabStop.right) { - this.properties.push(new RightTabStop(options.tabStop.right.position, options.tabStop.right.leader)); - } - - if (options.tabStop.center) { - this.properties.push(new CenterTabStop(options.tabStop.center.position, options.tabStop.center.leader)); + if (options.tabStops) { + for (const tabStop of options.tabStops) { + this.properties.push(new TabStop(tabStop.type, tabStop.position, tabStop.leader)); } } diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index 077df26292..ab592a9fc6 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -5,13 +5,12 @@ import { ISpacingProperties, KeepLines, KeepNext, - LeftTabStop, OutlineLevel, ParagraphProperties, Spacing, ThematicBreak, } from "file/paragraph"; -import { IIndentAttributesProperties, RightTabStop } from "file/paragraph/formatting"; +import { IIndentAttributesProperties, TabStop, TabStopType } from "file/paragraph/formatting"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; import { UnderlineType } from "file/paragraph/run/underline"; @@ -181,11 +180,11 @@ export class ParagraphStyle extends Style { } if (options.paragraph.rightTabStop) { - this.paragraphProperties.push(new RightTabStop(options.paragraph.rightTabStop)); + this.paragraphProperties.push(new TabStop(TabStopType.RIGHT, options.paragraph.rightTabStop)); } if (options.paragraph.leftTabStop) { - this.paragraphProperties.push(new LeftTabStop(options.paragraph.leftTabStop)); + this.paragraphProperties.push(new TabStop(TabStopType.LEFT, options.paragraph.leftTabStop)); } if (options.paragraph.indent) { From f16126e9480c0f239369545f4506a53db1599ae1 Mon Sep 17 00:00:00 2001 From: Dolan Date: Wed, 9 Oct 2019 21:19:41 +0100 Subject: [PATCH 06/12] Fix tests --- demo/21-bookmarks.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/21-bookmarks.ts b/demo/21-bookmarks.ts index ae7427aaf4..cc0bac5517 100644 --- a/demo/21-bookmarks.ts +++ b/demo/21-bookmarks.ts @@ -1,8 +1,7 @@ // This demo shows how to create bookmarks then link to them with internal hyperlinks // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HeadingLevel, Packer, Paragraph } from "../build"; -import { PageBreak } from "../build/file/paragraph"; +import { Document, HeadingLevel, Packer, PageBreak, Paragraph } from "../build"; const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante."; From 721de305878f68d5f2cbf39f2193e7bd8d32f4db Mon Sep 17 00:00:00 2001 From: Dolan Date: Thu, 10 Oct 2019 01:08:01 +0100 Subject: [PATCH 07/12] Fix demos --- demo/11-declaritive-styles-2.ts | 221 +++++++++++++++-------- demo/27-declaritive-styles-3.ts | 67 ++++--- demo/28-table-of-contents.ts | 26 ++- demo/47-number-of-total-pages-section.ts | 56 +++--- 4 files changed, 244 insertions(+), 126 deletions(-) diff --git a/demo/11-declaritive-styles-2.ts b/demo/11-declaritive-styles-2.ts index c6780c440f..3a0154a0fb 100644 --- a/demo/11-declaritive-styles-2.ts +++ b/demo/11-declaritive-styles-2.ts @@ -1,82 +1,153 @@ // Setting styles with JavaScript configuration // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { AlignmentType, Document, Footer, HeadingLevel, Media, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; +import { + AlignmentType, + Document, + Footer, + HeadingLevel, + Media, + Packer, + Paragraph, + Table, + TableCell, + TableRow, + TabStopPosition, + UnderlineType, +} from "../build"; -const doc = new Document(); - -doc.Styles.createParagraphStyle("Heading1", "Heading 1") - .basedOn("Normal") - .next("Normal") - .quickFormat() - .font("Calibri") - .size(52) - .center() - .bold() - .color("000000") - .spacing({ line: 340 }) - .underline("single", "000000"); - -doc.Styles.createParagraphStyle("Heading2", "Heading 2") - .basedOn("Normal") - .next("Normal") - .font("Calibri") - .quickFormat() - .size(26) - .bold() - .spacing({ line: 340 }); - -doc.Styles.createParagraphStyle("Heading3", "Heading 3") - .basedOn("Normal") - .next("Normal") - .font("Calibri") - .quickFormat() - .size(26) - .bold() - .spacing({ line: 276 }); - -doc.Styles.createParagraphStyle("Heading4", "Heading 4") - .basedOn("Normal") - .next("Normal") - .justified() - .font("Calibri") - .size(26) - .bold(); - -doc.Styles.createParagraphStyle("normalPara", "Normal Para") - .basedOn("Normal") - .next("Normal") - .font("Calibri") - .quickFormat() - .leftTabStop(453.543307087) - .maxRightTabStop() - .size(26) - .spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }); - -doc.Styles.createParagraphStyle("normalPara2", "Normal Para2") - .basedOn("Normal") - .next("Normal") - .quickFormat() - .font("Calibri") - .size(26) - .justified() - .spacing({ line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }); - -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 doc = new Document({ + styles: { + paragraphStyles: [ + { + id: "Heading1", + name: "Heading 1", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + font: "Calibri", + size: 52, + bold: true, + color: "000000", + underline: { + type: UnderlineType.SINGLE, + color: "000000", + }, + }, + paragraph: { + alignment: AlignmentType.CENTER, + spacing: { line: 340 }, + }, + }, + { + id: "Heading2", + name: "Heading 2", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + font: "Calibri", + size: 26, + bold: true, + }, + paragraph: { + spacing: { line: 340 }, + }, + }, + { + id: "Heading3", + name: "Heading 3", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + font: "Calibri", + size: 26, + bold: true, + }, + paragraph: { + spacing: { line: 276 }, + }, + }, + { + id: "Heading4", + name: "Heading 4", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + font: "Calibri", + size: 26, + bold: true, + }, + paragraph: { + alignment: AlignmentType.JUSTIFIED, + }, + }, + { + id: "normalPara", + name: "Normal Para", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + font: "Calibri", + size: 26, + bold: true, + }, + paragraph: { + spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }, + rightTabStop: TabStopPosition.MAX, + leftTabStop: 453.543307087, + }, + }, + { + id: "normalPara2", + name: "Normal Para2", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + font: "Calibri", + size: 26, + }, + paragraph: { + alignment: AlignmentType.JUSTIFIED, + spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }, + }, + }, + { + id: "aside", + name: "Aside", + basedOn: "Normal", + next: "Normal", + run: { + color: "999999", + italics: true, + }, + paragraph: { + spacing: { line: 276 }, + indent: { left: 720 }, + }, + }, + { + id: "wellSpaced", + name: "Well Spaced", + basedOn: "Normal", + paragraph: { + spacing: { line: 276, before: 20 * 72 * 0.1, after: 20 * 72 * 0.05 }, + }, + }, + { + id: "ListParagraph", + name: "List Paragraph", + basedOn: "Normal", + quickFormat: true, + }, + ], + }, +}); const image = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif")); diff --git a/demo/27-declaritive-styles-3.ts b/demo/27-declaritive-styles-3.ts index 31994dfe4a..8419467799 100644 --- a/demo/27-declaritive-styles-3.ts +++ b/demo/27-declaritive-styles-3.ts @@ -1,28 +1,53 @@ // Custom styles using JavaScript configuration // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, HeadingLevel, Packer, Paragraph } from "../build"; +import { Document, HeadingLevel, Packer, Paragraph, UnderlineType } from "../build"; -const doc = new Document(); - -// The first argument is an ID you use to apply the style to paragraphs -// The second argument is a human-friendly name to show in the UI -doc.Styles.createParagraphStyle("myWonkyStyle", "My Wonky Style") - .basedOn("Normal") - .next("Normal") - .color("990000") - .italics() - .indent({ left: 720 }) // 720 TWIP === 720 / 20 pt === .5 in - .spacing({ line: 276 }); // 276 / 240 = 1.15x line spacing - -doc.Styles.createParagraphStyle("Heading2", "Heading 2") - .basedOn("Normal") - .next("Normal") - .quickFormat() - .size(26) // 26 half-points === 13pt font - .bold() - .underline("double", "FF0000") - .spacing({ before: 240, after: 120 }); // TWIP for both +const doc = new Document({ + styles: { + paragraphStyles: [ + { + id: "myWonkyStyle", + name: "My Wonky Style", + basedOn: "Normal", + next: "Normal", + run: { + color: "990000", + italics: true, + }, + paragraph: { + indent: { + left: 720, + }, + spacing: { + line: 276, + }, + }, + }, + { + id: "Heading2", + name: "Heading 2", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + bold: true, + size: 26, + underline: { + type: UnderlineType.DOUBLE, + color: "FF0000", + }, + }, + paragraph: { + spacing: { + before: 240, + after: 120, + }, + }, + }, + ], + }, +}); doc.addSection({ children: [ diff --git a/demo/28-table-of-contents.ts b/demo/28-table-of-contents.ts index 83160b0177..76789ee256 100644 --- a/demo/28-table-of-contents.ts +++ b/demo/28-table-of-contents.ts @@ -3,15 +3,23 @@ import * as fs from "fs"; import { File, HeadingLevel, Packer, Paragraph, StyleLevel, TableOfContents } from "../build"; -const doc = new File(); - -// The first argument is an ID you use to apply the style to paragraphs -// The second argument is a human-friendly name to show in the UI -doc.Styles.createParagraphStyle("MySpectacularStyle", "My Spectacular Style") - .basedOn("Heading1") - .next("Heading1") - .color("990000") - .italics(); +const doc = new File({ + styles: { + paragraphStyles: [ + { + id: "MySpectacularStyle", + name: "My Spectacular Style", + basedOn: "Heading1", + next: "Heading1", + quickFormat: true, + run: { + italics: true, + color: "990000", + }, + }, + ], + }, +}); // WordprocessingML docs for TableOfContents can be found here: // http://officeopenxml.com/WPtableOfContents.php diff --git a/demo/47-number-of-total-pages-section.ts b/demo/47-number-of-total-pages-section.ts index 7ccc44e22a..988206e524 100644 --- a/demo/47-number-of-total-pages-section.ts +++ b/demo/47-number-of-total-pages-section.ts @@ -1,18 +1,26 @@ // Multiple sections with total number of pages in each section // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, PageNumberFormat, TextRun } from "../build"; +import { AlignmentType, Document, Packer, PageNumberFormat, TextRun, Header, Paragraph, Footer, PageBreak } from "../build"; const doc = new Document(); +const header = new Header({ + children: [ + new Paragraph({ + children: [ + new TextRun("Header on another page"), + new TextRun("Page Number: ").pageNumber(), + new TextRun(" to ").numberOfTotalPagesSection(), + ], + alignment: AlignmentType.CENTER, + }), + ], +}); -const header = doc.createHeader(); -header.createParagraph("Header on another page"); -const footer = doc.createFooter(); -footer.createParagraph("Foo Bar corp. ") - .center() - .addRun(new TextRun("Page Number: ").pageNumber()) - .addRun(new TextRun(" to ").numberOfTotalPagesSection()); +const footer = new Footer({ + children: [new Paragraph("Foo Bar corp. ")], +}); doc.addSection({ headers: { @@ -21,13 +29,17 @@ doc.addSection({ footers: { default: footer, }, - pageNumberStart: 1, - pageNumberFormatType: PageNumberFormat.DECIMAL, + properties: { + pageNumberStart: 1, + pageNumberFormatType: PageNumberFormat.DECIMAL, + }, + children: [ + new Paragraph({ + children: [new TextRun("Section 1"), new PageBreak(), new TextRun("Section 1"), new PageBreak()], + }), + ], }); -doc.createParagraph("Section 1").pageBreak(); -doc.createParagraph("Section 1").pageBreak(); - doc.addSection({ headers: { default: header, @@ -35,15 +47,17 @@ doc.addSection({ footers: { default: footer, }, - pageNumberStart: 1, - pageNumberFormatType: PageNumberFormat.DECIMAL, + properties: { + pageNumberStart: 1, + pageNumberFormatType: PageNumberFormat.DECIMAL, + }, + children: [ + new Paragraph({ + children: [new TextRun("Section 2"), new PageBreak(), new TextRun("Section 2"), new PageBreak()], + }), + ], }); -doc.createParagraph("Section 2").pageBreak(); -doc.createParagraph("Section 2").pageBreak(); - -const packer = new Packer(); - -packer.toBuffer(doc).then((buffer) => { +Packer.toBuffer(doc).then((buffer) => { fs.writeFileSync("My Document.docx", buffer); }); From b571a7550f0ffba26e45ff0ecef4a687f789abb3 Mon Sep 17 00:00:00 2001 From: Dolan Date: Thu, 10 Oct 2019 01:18:20 +0100 Subject: [PATCH 08/12] Fix demo --- demo/33-sequential-captions.ts | 50 ++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/demo/33-sequential-captions.ts b/demo/33-sequential-captions.ts index c06c3aa1cc..aed113d0fa 100644 --- a/demo/33-sequential-captions.ts +++ b/demo/33-sequential-captions.ts @@ -1,28 +1,44 @@ // Sequential Captions // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Packer, Paragraph, TextRun } from "../build"; +import { Document, Packer, Paragraph, SequentialIdentifier, TextRun } from "../build"; const doc = new Document(); doc.addSection({ children: [ - new Paragraph("Hello World 1->") - .addSequentialIdentifier("Caption") - .addRun(new TextRun(" text after sequencial caption 2->")) - .addSequentialIdentifier("Caption"), - new Paragraph("Hello World 1->") - .addSequentialIdentifier("Label") - .addRun(new TextRun(" text after sequencial caption 2->")) - .addSequentialIdentifier("Label"), - new Paragraph("Hello World 1->") - .addSequentialIdentifier("Another") - .addRun(new TextRun(" text after sequencial caption 3->")) - .addSequentialIdentifier("Label"), - new Paragraph("Hello World 2->") - .addSequentialIdentifier("Another") - .addRun(new TextRun(" text after sequencial caption 4->")) - .addSequentialIdentifier("Label"), + new Paragraph({ + children: [ + new TextRun("Hello World 1->"), + new SequentialIdentifier("Caption"), + new TextRun(" text after sequencial caption 2->"), + new SequentialIdentifier("Caption"), + ], + }), + new Paragraph({ + children: [ + new TextRun("Hello World 1->"), + new SequentialIdentifier("Label"), + new TextRun(" text after sequencial caption 2->"), + new SequentialIdentifier("Label"), + ], + }), + new Paragraph({ + children: [ + new TextRun("Hello World 1->"), + new SequentialIdentifier("Another"), + new TextRun(" text after sequencial caption 3->"), + new SequentialIdentifier("Label"), + ], + }), + new Paragraph({ + children: [ + new TextRun("Hello World 2->"), + new SequentialIdentifier("Another"), + new TextRun(" text after sequencial caption 4->"), + new SequentialIdentifier("Label"), + ], + }), ], }); From 2bb7e08adeb552b28fec271b680ae396edd4d926 Mon Sep 17 00:00:00 2001 From: Dolan Date: Thu, 10 Oct 2019 01:19:55 +0100 Subject: [PATCH 09/12] Remove unnecessary method --- src/file/paragraph/paragraph.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/file/paragraph/paragraph.ts b/src/file/paragraph/paragraph.ts index 5d8b4e0ae0..b11393ee37 100644 --- a/src/file/paragraph/paragraph.ts +++ b/src/file/paragraph/paragraph.ts @@ -45,7 +45,7 @@ export interface IParagraphOptions { readonly level: number; readonly custom?: boolean; }; - readonly children?: Array; + readonly children?: Array; } export class Paragraph extends XmlComponent { @@ -167,9 +167,4 @@ export class Paragraph extends XmlComponent { this.root.splice(1, 0, run); return this; } - - public addSequentialIdentifier(identifier: string): Paragraph { - this.root.push(new SequentialIdentifier(identifier)); - return this; - } } From 3b289be5ce7c03a990970f1cfd52003404c20937 Mon Sep 17 00:00:00 2001 From: Dolan Date: Thu, 10 Oct 2019 01:25:37 +0100 Subject: [PATCH 10/12] Fix demo --- demo/16-multiple-sections.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/16-multiple-sections.ts b/demo/16-multiple-sections.ts index 10e2098704..6e947a9d8a 100644 --- a/demo/16-multiple-sections.ts +++ b/demo/16-multiple-sections.ts @@ -1,12 +1,12 @@ // Multiple sections and headers // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Footer, Header, Packer, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build"; +import { Document, Footer, Header, Packer, PageBreak, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build"; const doc = new Document(); doc.addSection({ - children: [new Paragraph("Hello World").pageBreak()], + children: [new Paragraph("Hello World"), new PageBreak()], }); doc.addSection({ From 1d5e806ff45127b85a6f7d2e167a31e2e30a51b3 Mon Sep 17 00:00:00 2001 From: Dolan Date: Thu, 10 Oct 2019 21:03:38 +0100 Subject: [PATCH 11/12] Add character style tests --- demo/16-multiple-sections.ts | 4 +- src/file/styles/style/character-style.spec.ts | 215 ++++++++++++++++++ 2 files changed, 217 insertions(+), 2 deletions(-) diff --git a/demo/16-multiple-sections.ts b/demo/16-multiple-sections.ts index 6e947a9d8a..5518eb5416 100644 --- a/demo/16-multiple-sections.ts +++ b/demo/16-multiple-sections.ts @@ -1,12 +1,12 @@ // Multiple sections and headers // Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { Document, Footer, Header, Packer, PageBreak, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build"; +import { Document, Footer, Header, Packer, PageNumberFormat, PageOrientation, Paragraph, TextRun } from "../build"; const doc = new Document(); doc.addSection({ - children: [new Paragraph("Hello World"), new PageBreak()], + children: [new Paragraph("Hello World")], }); doc.addSection({ diff --git a/src/file/styles/style/character-style.spec.ts b/src/file/styles/style/character-style.spec.ts index fa12e5e512..fe269bfceb 100644 --- a/src/file/styles/style/character-style.spec.ts +++ b/src/file/styles/style/character-style.spec.ts @@ -52,6 +52,221 @@ describe("CharacterStyle", () => { ], }); }); + + it("should add smallCaps", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + smallCaps: true, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + + it("should add allCaps", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + allCaps: true, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + + it("should add strike", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + strike: true, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + + it("should add double strike", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + doubleStrike: true, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + + it("should add sub script", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + subScript: true, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [ + { + "w:vertAlign": { + _attr: { + "w:val": "subscript", + }, + }, + }, + ], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + + it("should add font", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + font: "test font", + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [ + { + "w:rFonts": { + _attr: { + "w:ascii": "test font", + "w:cs": "test font", + "w:eastAsia": "test font", + "w:hAnsi": "test font", + }, + }, + }, + ], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + + it("should add character spacing", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + characterSpacing: 100, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:spacing": { _attr: { "w:val": 100 } } }], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); }); describe("formatting methods: style attributes", () => { From db59474f1e75e641e90f47ef1fe41cc9d42d2e6f Mon Sep 17 00:00:00 2001 From: Dolan Date: Thu, 10 Oct 2019 21:10:03 +0100 Subject: [PATCH 12/12] Remove unused method --- src/file/styles/style/style.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/file/styles/style/style.ts b/src/file/styles/style/style.ts index 92df610e0d..d0d69f17ed 100644 --- a/src/file/styles/style/style.ts +++ b/src/file/styles/style/style.ts @@ -25,8 +25,4 @@ export class Style extends XmlComponent { this.root.push(new Name(name)); } } - - public push(styleSegment: XmlComponent): void { - this.root.push(styleSegment); - } }