diff --git a/demo/49-table-borders.ts b/demo/49-table-borders.ts new file mode 100644 index 0000000000..2aebf1feb1 --- /dev/null +++ b/demo/49-table-borders.ts @@ -0,0 +1,37 @@ +// Add custom borders to the table itself +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { BorderStyle, Document, Packer, Paragraph, Table, TableCell, TableRow } from "../build"; + +const doc = new Document(); + +const table = new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph("Hello")], + }), + new TableCell({ + children: [], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [], + }), + new TableCell({ + children: [new Paragraph("World")], + }), + ], + }), + ], +}); + +doc.addSection({ children: [table] }); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/package-lock.json b/package-lock.json index 1313298723..ee20450d0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1098,7 +1098,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -1135,7 +1135,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -1169,7 +1169,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -1642,7 +1642,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -1655,7 +1655,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -1894,7 +1894,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -3921,7 +3921,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-ci": { @@ -4345,7 +4345,7 @@ }, "jsesc": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, @@ -4771,7 +4771,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -4935,6 +4935,11 @@ "dev": true, "optional": true }, + "nanoid": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.6.tgz", + "integrity": "sha512-2NDzpiuEy3+H0AVtdt8LoFi7PnqkOnIzYmJQp7xsEU6VexLluHQwKREuiz57XaQC5006seIadPrIZJhyS2n7aw==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -6600,7 +6605,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -6634,6 +6639,14 @@ "rechoir": "^0.6.2" } }, + "shortid": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.15.tgz", + "integrity": "sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw==", + "requires": { + "nanoid": "^2.1.0" + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", diff --git a/src/file/table/table-properties/index.ts b/src/file/table/table-properties/index.ts index dde973ee7a..aefcc9e0aa 100644 --- a/src/file/table/table-properties/index.ts +++ b/src/file/table/table-properties/index.ts @@ -1,3 +1,4 @@ export * from "./table-properties"; export * from "./table-float-properties"; export * from "./table-layout"; +export * from "./table-borders"; diff --git a/src/file/table/table-properties/table-borders.spec.ts b/src/file/table/table-properties/table-borders.spec.ts new file mode 100644 index 0000000000..679192f120 --- /dev/null +++ b/src/file/table/table-properties/table-borders.spec.ts @@ -0,0 +1,550 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { BorderStyle } from "file/styles"; + +import { TableBorders } from "./table-borders"; + +describe("TableBorders", () => { + describe("#constructor", () => { + describe("default borders", () => { + it("should add a table cell top border using default width type", () => { + const tableBorders = new TableBorders({}); + const tree = new Formatter().format(tableBorders); + + expect(tree).to.deep.equal({ + "w:tblBorders": [ + { + "w:top": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideH": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideV": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + ], + }); + }); + }); + + describe("top border", () => { + it("should add a table cell top border", () => { + const tableBorders = new TableBorders({ + top: { + style: BorderStyle.DOUBLE, + size: 1, + color: "red", + }, + }); + + const tree = new Formatter().format(tableBorders); + expect(tree).to.deep.equal({ + "w:tblBorders": [ + { + "w:top": { + _attr: { + "w:color": "red", + "w:space": 0, + "w:sz": 1, + "w:val": "double", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideH": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideV": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + ], + }); + }); + }); + + describe("left border", () => { + it("should add a table cell left border", () => { + const tableBorders = new TableBorders({ + left: { + style: BorderStyle.DOUBLE, + size: 1, + color: "red", + }, + }); + const tree = new Formatter().format(tableBorders); + + expect(tree).to.deep.equal({ + "w:tblBorders": [ + { + "w:top": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "red", + "w:space": 0, + "w:sz": 1, + "w:val": "double", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideH": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideV": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + ], + }); + }); + }); + + describe("bottom border", () => { + it("should add a table cell bottom border", () => { + const tableBorders = new TableBorders({ + bottom: { + style: BorderStyle.DOUBLE, + size: 1, + color: "red", + }, + }); + const tree = new Formatter().format(tableBorders); + + expect(tree).to.deep.equal({ + "w:tblBorders": [ + { + "w:top": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "red", + "w:space": 0, + "w:sz": 1, + "w:val": "double", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideH": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideV": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + ], + }); + }); + }); + + describe("right border", () => { + it("should add a table cell right border", () => { + const tableBorders = new TableBorders({ + right: { + style: BorderStyle.DOUBLE, + size: 1, + color: "red", + }, + }); + const tree = new Formatter().format(tableBorders); + + expect(tree).to.deep.equal({ + "w:tblBorders": [ + { + "w:top": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "red", + "w:space": 0, + "w:sz": 1, + "w:val": "double", + }, + }, + }, + { + "w:insideH": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideV": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + ], + }); + }); + }); + + describe("inside horizontal border", () => { + it("should add a table cell inside horizontal border", () => { + const tableBorders = new TableBorders({ + insideHorizontal: { + style: BorderStyle.DOUBLE, + size: 1, + color: "red", + }, + }); + const tree = new Formatter().format(tableBorders); + + expect(tree).to.deep.equal({ + "w:tblBorders": [ + { + "w:top": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideH": { + _attr: { + "w:color": "red", + "w:space": 0, + "w:sz": 1, + "w:val": "double", + }, + }, + }, + { + "w:insideV": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + ], + }); + }); + }); + + describe("inside vertical border", () => { + it("should add a table cell inside horizontal border", () => { + const tableBorders = new TableBorders({ + insideVertical: { + style: BorderStyle.DOUBLE, + size: 1, + color: "red", + }, + }); + const tree = new Formatter().format(tableBorders); + + expect(tree).to.deep.equal({ + "w:tblBorders": [ + { + "w:top": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:left": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:bottom": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:right": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideH": { + _attr: { + "w:color": "auto", + "w:space": 0, + "w:sz": 4, + "w:val": "single", + }, + }, + }, + { + "w:insideV": { + _attr: { + "w:color": "red", + "w:space": 0, + "w:sz": 1, + "w:val": "double", + }, + }, + }, + ], + }); + }); + }); + }); +}); diff --git a/src/file/table/table-properties/table-borders.ts b/src/file/table/table-properties/table-borders.ts index 204bccf496..7c7ac96ca9 100644 --- a/src/file/table/table-properties/table-borders.ts +++ b/src/file/table/table-properties/table-borders.ts @@ -1,14 +1,95 @@ +// http://officeopenxml.com/WPtableBorders.php +import { BorderStyle } from "file/styles"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; +export interface ITableBordersOptions { + readonly top?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly bottom?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly left?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly right?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly insideHorizontal?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; + readonly insideVertical?: { + readonly style: BorderStyle; + readonly size: number; + readonly color: string; + }; +} + export class TableBorders extends XmlComponent { - constructor() { + constructor(options: ITableBordersOptions) { super("w:tblBorders"); - this.root.push(new TableBordersElement("w:top", "single", 4, 0, "auto")); - this.root.push(new TableBordersElement("w:left", "single", 4, 0, "auto")); - this.root.push(new TableBordersElement("w:bottom", "single", 4, 0, "auto")); - this.root.push(new TableBordersElement("w:right", "single", 4, 0, "auto")); - this.root.push(new TableBordersElement("w:insideH", "single", 4, 0, "auto")); - this.root.push(new TableBordersElement("w:insideV", "single", 4, 0, "auto")); + + if (options.top) { + this.root.push(new TableBordersElement("w:top", options.top.style, options.top.size, 0, options.top.color)); + } else { + this.root.push(new TableBordersElement("w:top", BorderStyle.SINGLE, 4, 0, "auto")); + } + + if (options.left) { + this.root.push(new TableBordersElement("w:left", options.left.style, options.left.size, 0, options.left.color)); + } else { + this.root.push(new TableBordersElement("w:left", BorderStyle.SINGLE, 4, 0, "auto")); + } + + if (options.bottom) { + this.root.push(new TableBordersElement("w:bottom", options.bottom.style, options.bottom.size, 0, options.bottom.color)); + } else { + this.root.push(new TableBordersElement("w:bottom", BorderStyle.SINGLE, 4, 0, "auto")); + } + + if (options.right) { + this.root.push(new TableBordersElement("w:right", options.right.style, options.right.size, 0, options.right.color)); + } else { + this.root.push(new TableBordersElement("w:right", BorderStyle.SINGLE, 4, 0, "auto")); + } + + if (options.insideHorizontal) { + this.root.push( + new TableBordersElement( + "w:insideH", + options.insideHorizontal.style, + options.insideHorizontal.size, + 0, + options.insideHorizontal.color, + ), + ); + } else { + this.root.push(new TableBordersElement("w:insideH", BorderStyle.SINGLE, 4, 0, "auto")); + } + + if (options.insideVertical) { + this.root.push( + new TableBordersElement( + "w:insideV", + options.insideVertical.style, + options.insideVertical.size, + 0, + options.insideVertical.color, + ), + ); + } else { + this.root.push(new TableBordersElement("w:insideV", BorderStyle.SINGLE, 4, 0, "auto")); + } } } diff --git a/src/file/table/table-properties/table-properties.ts b/src/file/table/table-properties/table-properties.ts index fd1f844978..6744558d1a 100644 --- a/src/file/table/table-properties/table-properties.ts +++ b/src/file/table/table-properties/table-properties.ts @@ -2,7 +2,7 @@ import { IgnoreIfEmptyXmlComponent } from "file/xml-components"; import { ITableShadingAttributesProperties, TableShading } from "../shading"; import { WidthType } from "../table-cell"; -import { TableBorders } from "./table-borders"; +import { ITableBordersOptions, TableBorders } from "./table-borders"; import { TableCellMargin } from "./table-cell-margin"; import { ITableFloatOptions, TableFloatProperties } from "./table-float-properties"; import { TableLayout, TableLayoutType } from "./table-layout"; @@ -27,8 +27,8 @@ export class TableProperties extends IgnoreIfEmptyXmlComponent { this.root.push(new TableLayout(type)); } - public setBorder(): TableProperties { - this.root.push(new TableBorders()); + public setBorder(borderOptions: ITableBordersOptions): TableProperties { + this.root.push(new TableBorders(borderOptions)); return this; } diff --git a/src/file/table/table.ts b/src/file/table/table.ts index 08bd47eda2..17c5f4fefd 100644 --- a/src/file/table/table.ts +++ b/src/file/table/table.ts @@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components"; import { TableGrid } from "./grid"; import { TableCell, VerticalMergeType, WidthType } from "./table-cell"; -import { ITableFloatOptions, TableProperties } from "./table-properties"; +import { ITableBordersOptions, ITableFloatOptions, TableProperties } from "./table-properties"; import { TableLayoutType } from "./table-properties/table-layout"; import { TableRow } from "./table-row"; @@ -32,6 +32,7 @@ export interface ITableOptions { }; readonly float?: ITableFloatOptions; readonly layout?: TableLayoutType; + readonly borders?: ITableBordersOptions; } export class Table extends XmlComponent { @@ -44,11 +45,17 @@ export class Table extends XmlComponent { margins: { marginUnitType, top, bottom, right, left } = { marginUnitType: WidthType.AUTO, top: 0, bottom: 0, right: 0, left: 0 }, float, layout, + borders, }: ITableOptions) { super("w:tbl"); this.properties = new TableProperties(); this.root.push(this.properties); - this.properties.setBorder(); + + if (borders) { + this.properties.setBorder(borders); + } else { + this.properties.setBorder({}); + } if (width) { this.properties.setWidth(width.size, width.type);