diff --git a/.gitignore b/.gitignore index 1510fe9647..e6143b4db7 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,12 @@ docs/.nojekyll !.vscode/extensions.json .history +# IntelliJ +.idea + # Lock files package-lock.json +yarn.lock # Documents My Document.docx diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..469d080845 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v8 \ No newline at end of file diff --git a/README.md b/README.md index 7e25666ee0..020cb78f96 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- clippy the assistant + clippy the assistant

@@ -17,11 +17,20 @@ [![PRs Welcome][pr-image]][pr-url]

- drawing + drawing

# Demo +## Browser + +Here are examples of `docx` being used with basic `HTML/JS` in a browser environment. + +* https://codepen.io/anon/pen/dqoVgQ +* https://jsfiddle.net/3xhezb5w/2 + +## Node + Press `endpoint` on the `RunKit` website: ![RunKit Instructions](https://user-images.githubusercontent.com/2917613/38582539-f84311b6-3d07-11e8-90db-5885ae02c3c4.png) @@ -33,9 +42,11 @@ Press `endpoint` on the `RunKit` website: * https://runkit.com/dolanmiu/docx-demo5 - Images * https://runkit.com/dolanmiu/docx-demo6 - Margins * https://runkit.com/dolanmiu/docx-demo7 - Landscape -* https://runkit.com/dolanmiu/docx-demo8/1.0.1 - Header and Footer +* https://runkit.com/dolanmiu/docx-demo8 - Header and Footer * https://runkit.com/dolanmiu/docx-demo10 - **My CV generated with docx** +More [here](https://docx.js.org/#/examples) and [here](https://github.com/dolanmiu/docx/tree/master/demo) + # How to use & Documentation Please refer to the [documentation at https://docx.js.org/](https://docx.js.org/) for details on how to use this library, examples and much more! diff --git a/demo/demo27.ts b/demo/demo27.ts index bbba325052..e04fc07e02 100644 --- a/demo/demo27.ts +++ b/demo/demo27.ts @@ -1,28 +1,34 @@ -// Creates two paragraphs, one with a border and one without -// Import from 'docx' rather than '../build' if you install from npm import * as fs from "fs"; -import { File, Packer, Paragraph, TableOfContents } from "../build"; +import { Document, Packer } from "../build"; -const doc = new File(); +const doc = new Document(); +const myStyles = doc.Styles; -// WordprocessingML docs for TableOfContents can be found here: -// http://officeopenxml.com/WPtableOfContents.php -// Creates an table of contents with default properties -const toc = new TableOfContents(); +// 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("990000") + .italics() + .indent({left: 720}) // 720 TWIP === 720 / 20 pt === .5 in + .spacing({line: 276}); // 276 / 240 = 1.15x line spacing -// A TableOfContents must be added via File class. -doc.addTableOfContents(toc) +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 -doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); -doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); - -doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore()); -doc.addParagraph(new Paragraph("I'm a other text very nicely written.'")); -doc.addParagraph(new Paragraph("Header #2.1").heading2()); -doc.addParagraph(new Paragraph("I'm a another text very nicely written.'")); +doc.createParagraph("Hello").style("myWonkyStyle"); +doc.createParagraph("World").heading2(); // Uses the Heading2 style const packer = new Packer(); packer.toBuffer(doc).then((buffer) => { - fs.writeFileSync("tmp/My Document.docx", buffer); + fs.writeFileSync("My Document.docx", buffer); + console.log("Document created successfully at project root!"); }); diff --git a/demo/demo28.ts b/demo/demo28.ts new file mode 100644 index 0000000000..3fdd14da46 --- /dev/null +++ b/demo/demo28.ts @@ -0,0 +1,28 @@ +// Creates two paragraphs, one with a border and one without +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { File, Packer, Paragraph, TableOfContents } from "../build"; + +const doc = new File(); + +// WordprocessingML docs for TableOfContents can be found here: +// http://officeopenxml.com/WPtableOfContents.php +// Creates an table of contents with default properties +const toc = new TableOfContents(); + +// A TableOfContents must be added via File class. +doc.addTableOfContents(toc); + +doc.addParagraph(new Paragraph("Header #1").heading1().pageBreakBefore()); +doc.addParagraph(new Paragraph("I'm a little text very nicely written.'")); + +doc.addParagraph(new Paragraph("Header #2").heading1().pageBreakBefore()); +doc.addParagraph(new Paragraph("I'm a other text very nicely written.'")); +doc.addParagraph(new Paragraph("Header #2.1").heading2()); +doc.addParagraph(new Paragraph("I'm a another text very nicely written.'")); + +const packer = new Packer(); + +packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("tmp/My Document.docx", buffer); +}); diff --git a/docs/README.md b/docs/README.md index f3134b8fa3..4fc5757477 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,5 @@

- clippy the assistant + clippy the assistant

@@ -54,6 +54,10 @@ exporter.pack("My First Document"); [@h4buli](https://github.com/h4buli) +

+ clippy the assistant +

+ --- Made with 💖 diff --git a/docs/usage/styling-with-js.md b/docs/usage/styling-with-js.md index 3e482bca14..4f362192af 100644 --- a/docs/usage/styling-with-js.md +++ b/docs/usage/styling-with-js.md @@ -20,6 +20,7 @@ const name = new TextRun("Name:") * `.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 diff --git a/logo/logo-small.gif b/logo/logo-small.gif new file mode 100644 index 0000000000..4b4a567c02 Binary files /dev/null and b/logo/logo-small.gif differ diff --git a/logo/logo-small.png b/logo/logo-small.png new file mode 100644 index 0000000000..966fd4b1e7 Binary files /dev/null and b/logo/logo-small.png differ diff --git a/logo/logo-small.psd b/logo/logo-small.psd new file mode 100644 index 0000000000..4c6b32123c Binary files /dev/null and b/logo/logo-small.psd differ diff --git a/logo/logo.psd b/logo/logo.psd new file mode 100644 index 0000000000..9763168e2c Binary files /dev/null and b/logo/logo.psd differ diff --git a/package.json b/package.json index 330ad72bac..e63dd166bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docx", - "version": "4.0.0", + "version": "4.1.0", "description": "Generate .docx documents with JavaScript (formerly Office-Clippy)", "main": "build/index.js", "scripts": { @@ -82,5 +82,8 @@ "typedoc": "^0.11.1", "typescript": "2.9.2", "webpack": "^3.10.0" + }, + "engines": { + "node": ">=8" } } diff --git a/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts b/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts index a9d1ae46ee..e486ae5c72 100644 --- a/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts +++ b/src/file/document/body/section-properties/page-margin/page-margin-attributes.ts @@ -8,6 +8,7 @@ export interface IPageMarginAttributes { header?: number; footer?: number; gutter?: number; + mirror?: boolean; } export class PageMarginAttributes extends XmlAttributeComponent { @@ -19,5 +20,6 @@ export class PageMarginAttributes extends XmlAttributeComponent { header: 708, footer: 708, gutter: 0, + mirror: false, space: 708, linePitch: 360, headerId: 100, @@ -40,6 +41,7 @@ describe("SectionProperties", () => { "w:left": 1440, "w:header": 708, "w:gutter": 0, + "w:mirrorMargins": false, }, }, ], @@ -69,6 +71,7 @@ describe("SectionProperties", () => { "w:left": 1440, "w:header": 708, "w:gutter": 0, + "w:mirrorMargins": false, }, }, ], @@ -99,6 +102,7 @@ describe("SectionProperties", () => { "w:left": 1440, "w:header": 708, "w:gutter": 0, + "w:mirrorMargins": false, }, }, ], @@ -124,6 +128,7 @@ describe("SectionProperties", () => { "w:left": 1440, "w:header": 708, "w:gutter": 0, + "w:mirrorMargins": false, }, }, ], @@ -150,6 +155,7 @@ describe("SectionProperties", () => { "w:left": 1440, "w:header": 708, "w:gutter": 0, + "w:mirrorMargins": false, }, }, ], diff --git a/src/file/document/body/section-properties/section-properties.ts b/src/file/document/body/section-properties/section-properties.ts index c23964cd14..5042c8a3ab 100644 --- a/src/file/document/body/section-properties/section-properties.ts +++ b/src/file/document/body/section-properties/section-properties.ts @@ -38,6 +38,7 @@ export class SectionProperties extends XmlComponent { header: 708, footer: 708, gutter: 0, + mirror: false, space: 708, linePitch: 360, orientation: PageOrientation.PORTRAIT, @@ -69,6 +70,7 @@ export class SectionProperties extends XmlComponent { mergedOptions.header, mergedOptions.footer, mergedOptions.gutter, + mergedOptions.mirror, ), ); this.root.push(new Columns(mergedOptions.space)); diff --git a/src/file/document/index.ts b/src/file/document/index.ts index 6b128299f6..3430666623 100644 --- a/src/file/document/index.ts +++ b/src/file/document/index.ts @@ -1,2 +1,3 @@ export * from "./document"; +export * from "./document-attributes"; export * from "./body"; diff --git a/src/file/file.ts b/src/file/file.ts index dcf0b0499c..8d1b07f827 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -21,7 +21,7 @@ import { PageReferenceInstruction, TableOfContents } from "./table-of-contents"; export class File { private readonly document: Document; - private readonly styles: Styles; + private styles: Styles; private readonly coreProperties: CoreProperties; private readonly numbering: Numbering; private readonly media: Media; @@ -225,6 +225,10 @@ 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/paragraph/run/formatting.ts b/src/file/paragraph/run/formatting.ts index 8b1be933ec..30d437e268 100644 --- a/src/file/paragraph/run/formatting.ts +++ b/src/file/paragraph/run/formatting.ts @@ -25,6 +25,17 @@ export class BoldComplexScript extends XmlComponent { } } +export class CharacterSpacing extends XmlComponent { + constructor(value: number) { + super("w:spacing"); + this.root.push( + new Attributes({ + val: value, + }), + ); + } +} + export class Italics extends XmlComponent { constructor() { super("w:i"); diff --git a/src/file/styles/style/index.ts b/src/file/styles/style/index.ts index 655cc28d2a..ee2d6d84aa 100644 --- a/src/file/styles/style/index.ts +++ b/src/file/styles/style/index.ts @@ -47,12 +47,14 @@ export class ParagraphStyle extends Style { this.root.push(this.runProperties); } - public addParagraphProperty(property: XmlComponent): void { + public addParagraphProperty(property: XmlComponent): ParagraphStyle { this.paragraphProperties.push(property); + return this; } - public addRunProperty(property: XmlComponent): void { + public addRunProperty(property: XmlComponent): ParagraphStyle { this.runProperties.push(property); + return this; } public basedOn(parentId: string): ParagraphStyle { @@ -73,121 +75,101 @@ export class ParagraphStyle extends Style { // ---------- Run formatting ---------------------- // public size(twips: number): ParagraphStyle { - this.addRunProperty(new formatting.Size(twips)); - this.addRunProperty(new formatting.SizeComplexScript(twips)); - return this; + return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); } public bold(): ParagraphStyle { - this.addRunProperty(new formatting.Bold()); - return this; + return this.addRunProperty(new formatting.Bold()); } public italics(): ParagraphStyle { - this.addRunProperty(new formatting.Italics()); - return this; + return this.addRunProperty(new formatting.Italics()); } public smallCaps(): ParagraphStyle { - this.addRunProperty(new formatting.SmallCaps()); - return this; + return this.addRunProperty(new formatting.SmallCaps()); } public allCaps(): ParagraphStyle { - this.addRunProperty(new formatting.Caps()); - return this; + return this.addRunProperty(new formatting.Caps()); } public strike(): ParagraphStyle { - this.addRunProperty(new formatting.Strike()); - return this; + return this.addRunProperty(new formatting.Strike()); } public doubleStrike(): ParagraphStyle { - this.addRunProperty(new formatting.DoubleStrike()); - return this; + return this.addRunProperty(new formatting.DoubleStrike()); } public subScript(): ParagraphStyle { - this.addRunProperty(new formatting.SubScript()); - return this; + return this.addRunProperty(new formatting.SubScript()); } public superScript(): ParagraphStyle { - this.addRunProperty(new formatting.SuperScript()); - return this; + return this.addRunProperty(new formatting.SuperScript()); } public underline(underlineType?: string, color?: string): ParagraphStyle { - this.addRunProperty(new formatting.Underline(underlineType, color)); - return this; + return this.addRunProperty(new formatting.Underline(underlineType, color)); } public color(color: string): ParagraphStyle { - this.addRunProperty(new formatting.Color(color)); - return this; + return this.addRunProperty(new formatting.Color(color)); } public font(fontName: string): ParagraphStyle { - this.addRunProperty(new formatting.RunFonts(fontName)); - return this; + return this.addRunProperty(new formatting.RunFonts(fontName)); + } + + public characterSpacing(value: number): ParagraphStyle { + return this.addRunProperty(new formatting.CharacterSpacing(value)); } // --------------------- Paragraph formatting ------------------------ // public center(): ParagraphStyle { - this.addParagraphProperty(new paragraph.Alignment("center")); - return this; + return this.addParagraphProperty(new paragraph.Alignment("center")); } public left(): ParagraphStyle { - this.addParagraphProperty(new paragraph.Alignment("left")); - return this; + return this.addParagraphProperty(new paragraph.Alignment("left")); } public right(): ParagraphStyle { - this.addParagraphProperty(new paragraph.Alignment("right")); - return this; + return this.addParagraphProperty(new paragraph.Alignment("right")); } public justified(): ParagraphStyle { - this.addParagraphProperty(new paragraph.Alignment("both")); - return this; + return this.addParagraphProperty(new paragraph.Alignment("both")); } public thematicBreak(): ParagraphStyle { - this.addParagraphProperty(new paragraph.ThematicBreak()); - return this; + return this.addParagraphProperty(new paragraph.ThematicBreak()); } public maxRightTabStop(): ParagraphStyle { - this.addParagraphProperty(new paragraph.MaxRightTabStop()); - return this; + return this.addParagraphProperty(new paragraph.MaxRightTabStop()); } public leftTabStop(position: number): ParagraphStyle { - this.addParagraphProperty(new paragraph.LeftTabStop(position)); - return this; + return this.addParagraphProperty(new paragraph.LeftTabStop(position)); } public indent(attrs: object): ParagraphStyle { - this.addParagraphProperty(new paragraph.Indent(attrs)); - return this; + return this.addParagraphProperty(new paragraph.Indent(attrs)); } public spacing(params: paragraph.ISpacingProperties): ParagraphStyle { - this.addParagraphProperty(new paragraph.Spacing(params)); - return this; + return this.addParagraphProperty(new paragraph.Spacing(params)); } public keepNext(): ParagraphStyle { - this.addParagraphProperty(new paragraph.KeepNext()); - return this; + return this.addParagraphProperty(new paragraph.KeepNext()); } public keepLines(): ParagraphStyle { - this.addParagraphProperty(new paragraph.KeepLines()); - return this; + return this.addParagraphProperty(new paragraph.KeepLines()); } } @@ -267,24 +249,21 @@ export class CharacterStyle extends Style { return this; } - public addRunProperty(property: XmlComponent): void { + public addRunProperty(property: XmlComponent): CharacterStyle { this.runProperties.push(property); + return this; } public color(color: string): CharacterStyle { - this.addRunProperty(new formatting.Color(color)); - return this; + return this.addRunProperty(new formatting.Color(color)); } public underline(underlineType?: string, color?: string): CharacterStyle { - this.addRunProperty(new formatting.Underline(underlineType, color)); - return this; + return this.addRunProperty(new formatting.Underline(underlineType, color)); } public size(twips: number): CharacterStyle { - this.addRunProperty(new formatting.Size(twips)); - this.addRunProperty(new formatting.SizeComplexScript(twips)); - return this; + return this.addRunProperty(new formatting.Size(twips)).addRunProperty(new formatting.SizeComplexScript(twips)); } } diff --git a/src/file/styles/styles.spec.ts b/src/file/styles/styles.spec.ts index 1c828337b2..6262b3cb9e 100644 --- a/src/file/styles/styles.spec.ts +++ b/src/file/styles/styles.spec.ts @@ -219,6 +219,20 @@ describe("ParagraphStyle", () => { }); }); + it("#character spacing", () => { + const style = new ParagraphStyle("myStyleId").characterSpacing(24); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { "w:pPr": [] }, + { + "w:rPr": [{ "w:spacing": [{ _attr: { "w:val": 24 } }] }], + }, + ], + }); + }); + it("#left", () => { const style = new ParagraphStyle("myStyleId").left(); const tree = new Formatter().format(style); diff --git a/src/file/table/properties.ts b/src/file/table/properties.ts index 7c5b0bad76..91ef393df3 100644 --- a/src/file/table/properties.ts +++ b/src/file/table/properties.ts @@ -3,13 +3,13 @@ import { WidthType } from "./table-cell"; import { TableCellMargin } from "./table-cell-margin"; export class TableProperties extends XmlComponent { - private readonly cellMargain: TableCellMargin; + private readonly cellMargin: TableCellMargin; constructor() { super("w:tblPr"); - this.cellMargain = new TableCellMargin(); - this.root.push(this.cellMargain); + this.cellMargin = new TableCellMargin(); + this.root.push(this.cellMargin); } public setWidth(type: WidthType, w: number | string): TableProperties { @@ -28,7 +28,7 @@ export class TableProperties extends XmlComponent { } public get CellMargin(): TableCellMargin { - return this.cellMargain; + return this.cellMargin; } }