diff --git a/.github/workflows/demos.yml b/.github/workflows/demos.yml
index 8f9675f05e..5de2594afa 100644
--- a/.github/workflows/demos.yml
+++ b/.github/workflows/demos.yml
@@ -716,3 +716,93 @@ jobs:
with:
xml-file: build/extracted-doc/word/document.xml
xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/75-tab-stops.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/76-compatibility.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/77-side-by-side-tables.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/78-thai-distributed.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/79-table-from-data-source.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/80-thai-distributed.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/81-continuous-header.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/82-new-headers-new-section.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/83-setting-languages.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
+ - name: Run Demo
+ run: npm run ts-node -- ./demo/84-positional-tabs.ts
+ - name: Extract Word Document
+ run: npm run extract
+ - name: Validate XML
+ uses: ChristophWurst/xmllint-action@v1
+ with:
+ xml-file: build/extracted-doc/word/document.xml
+ xml-schema-file: ooxml-schemas/microsoft/wml-2010.xsd
diff --git a/demo/75-tab-stops.ts b/demo/75-tab-stops.ts
index a2257419d8..a9ea5deed1 100644
--- a/demo/75-tab-stops.ts
+++ b/demo/75-tab-stops.ts
@@ -1,7 +1,7 @@
-// Exporting the document as a stream
+// Example of using tab stops
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
-import { Document, HeadingLevel, Packer, Paragraph, TabStopPosition, TabStopType, TextRun } from "../build";
+import { Document, HeadingLevel, Packer, Paragraph, TabStopPosition, TabStopType, TextRun, Tab } from "../build";
const columnWidth = TabStopPosition.MAX / 4;
const receiptTabStops = [
@@ -30,7 +30,7 @@ const doc = new Document({
tabStops: twoTabStops,
children: [
new TextRun({
- text: "To Bob.\tBy Alice.",
+ children: ["To Bob.", new Tab(), "By Alice."],
bold: true,
}),
],
diff --git a/demo/84-positional-tabs.ts b/demo/84-positional-tabs.ts
new file mode 100644
index 0000000000..1d61b93de7
--- /dev/null
+++ b/demo/84-positional-tabs.ts
@@ -0,0 +1,60 @@
+// Simple example apply positional tabs to a document
+// Import from 'docx' rather than '../build' if you install from npm
+import * as fs from "fs";
+import {
+ Document,
+ Packer,
+ Paragraph,
+ PositionalTab,
+ Tab,
+ TextRun,
+ PositionalTabAlignment,
+ PositionalTabRelativeTo,
+ PositionalTabLeader,
+} from "../build";
+
+const doc = new Document({
+ sections: [
+ {
+ properties: {},
+ children: [
+ new Paragraph({
+ children: [
+ new TextRun("Full name"),
+ new TextRun({
+ children: [
+ new PositionalTab({
+ alignment: PositionalTabAlignment.RIGHT,
+ relativeTo: PositionalTabRelativeTo.MARGIN,
+ leader: PositionalTabLeader.DOT,
+ }),
+ "John Doe",
+ ],
+ bold: true,
+ }),
+ ],
+ }),
+ new Paragraph({
+ children: [
+ new TextRun("Hello World"),
+ new TextRun({
+ children: [
+ new PositionalTab({
+ alignment: PositionalTabAlignment.CENTER,
+ relativeTo: PositionalTabRelativeTo.INDENT,
+ leader: PositionalTabLeader.HYPHEN,
+ }),
+ "Foo bar",
+ ],
+ bold: true,
+ }),
+ ],
+ }),
+ ],
+ },
+ ],
+});
+
+Packer.toBuffer(doc).then((buffer) => {
+ fs.writeFileSync("My Document.docx", buffer);
+});
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index cebe089854..8805f6a417 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -20,7 +20,7 @@
- [Hyperlinks](usage/hyperlinks.md)
- [Numbering](usage/numbering.md)
- [Tables](usage/tables.md)
- - [Tab Stops](usage/tab-stops.md)
+ - [Tabs](usage/tabs.md)
- [Table of Contents](usage/table-of-contents.md)
- [Page Numbers](usage/page-numbers.md)
- [Change Tracking](usage/change-tracking.md)
diff --git a/docs/usage/tab-stops.md b/docs/usage/tab-stops.md
deleted file mode 100644
index 5c859a1bac..0000000000
--- a/docs/usage/tab-stops.md
+++ /dev/null
@@ -1,121 +0,0 @@
-# Tab Stops
-
-> 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**: 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)
-
-
-
-Simply declare the tab stops on the paragraph, as shown below. Use the tab character `\t` to indicate the tab position within the `text` property of a `TextRun`. Adding multiple `tabStops` will mean you can add additional `\t` characters until the desired `tabStop` is selected. Example is shown below.
-
-## Example
-
-```ts
-const paragraph = new Paragraph({
- children: [new TextRun({ text: "Hey everyone", bold: true}), new TextRun("\t11th November 1999")],
- tabStops: [
- {
- type: TabStopType.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. Not ideal!
-
-```ts
-const paragraph = new Paragraph({
- children: [new TextRun("\t\tSecond tab stop here I come!")],
- tabStops: [
- {
- type: TabStopType.RIGHT,
- position: TabStopPosition.MAX,
- },
- {
- 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 \ttab \tstops!")],
- tabStops: [
- {
- type: TabStopType.RIGHT,
- position: TabStopPosition.MAX,
- },
- {
- type: TabStopType.RIGHT,
- position: 1000,
- },
- ],
-});
-```
-
-## Left Tab Stop
-
-```ts
-const paragraph = new Paragraph({
- tabStops: [
- {
- type: TabStopType.LEFT,
- position: 2268,
- },
- ],
-});
-```
-
-2268 is the distance from the left side.
-
-## Center Tab Stop
-
-```ts
-const paragraph = new Paragraph({
- tabStops: [
- {
- type: TabStopType.CENTER,
- position: 2268,
- },
- ],
-});
-```
-
-2268 is the distance from the center.
-
-## Right Tab Stop
-
-```ts
-const paragraph = new Paragraph({
- tabStops: [
- {
- type: TabStopType.RIGHT,
- position: 2268,
- },
- ],
-});
-```
-
-2268 is the distance from the left side.
-
-## Max Right Tab Stop
-
-```ts
-const paragraph = new Paragraph({
- tabStops: [
- {
- type: TabStopType.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/tabs.md b/docs/usage/tabs.md
new file mode 100644
index 0000000000..f2ab2f2841
--- /dev/null
+++ b/docs/usage/tabs.md
@@ -0,0 +1,184 @@
+# Tabs and Tab Stops
+
+## Tab Stops
+
+> 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**: 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)
+
+
+
+Simply declare the tab stops on the paragraph, as shown below. Use the tab character `\t` or add the `new Tab()` child to indicate the tab position within the `text` property of a `TextRun`. Adding multiple `tabStops` will mean you can add additional `\t` characters until the desired `tabStop` is selected. Example is shown below.
+
+### Example
+
+```ts
+const paragraph = new Paragraph({
+ children: [
+ new TextRun({ text: "Hey everyone", bold: true }),
+ new TextRun("\t11th November 1999"),
+ new TextRun({
+ children: [new Tab(), "11th November 1999"],
+ }),
+ ],
+ tabStops: [
+ {
+ type: TabStopType.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. Not ideal!
+
+```ts
+const paragraph = new Paragraph({
+ children: [new TextRun("\t\tSecond tab stop here I come!")],
+ tabStops: [
+ {
+ type: TabStopType.RIGHT,
+ position: TabStopPosition.MAX,
+ },
+ {
+ 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 \ttab \tstops!")],
+ tabStops: [
+ {
+ type: TabStopType.RIGHT,
+ position: TabStopPosition.MAX,
+ },
+ {
+ type: TabStopType.RIGHT,
+ position: 1000,
+ },
+ ],
+});
+
+const paragraph = new Paragraph({
+ children: [
+ new TextRun({
+ children: ["Multiple ", new Tab(), "tab ", new Tab(), "stops!"],
+ }),
+ ],
+ tabStops: [
+ {
+ type: TabStopType.RIGHT,
+ position: TabStopPosition.MAX,
+ },
+ {
+ type: TabStopType.RIGHT,
+ position: 1000,
+ },
+ ],
+});
+```
+
+### Left Tab Stop
+
+```ts
+const paragraph = new Paragraph({
+ tabStops: [
+ {
+ type: TabStopType.LEFT,
+ position: 2268,
+ },
+ ],
+});
+```
+
+2268 is the distance from the left side.
+
+### Center Tab Stop
+
+```ts
+const paragraph = new Paragraph({
+ tabStops: [
+ {
+ type: TabStopType.CENTER,
+ position: 2268,
+ },
+ ],
+});
+```
+
+2268 is the distance from the center.
+
+### Right Tab Stop
+
+```ts
+const paragraph = new Paragraph({
+ tabStops: [
+ {
+ type: TabStopType.RIGHT,
+ position: 2268,
+ },
+ ],
+});
+```
+
+2268 is the distance from the left side.
+
+### Max Right Tab Stop
+
+```ts
+const paragraph = new Paragraph({
+ tabStops: [
+ {
+ type: TabStopType.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.
+
+## Positional Tabs
+
+> Positional tab allow you to create a tab stop that is relative to the margin, or the page. This is useful if you want to create a table of contents, or a table of figures.
+
+They are easier to use than the normal tab stops, as you can use the `PositionalTab` class to create a tab stop, and then add the text to the `TextRun` children. Useful for most cases.
+
+
+
+### Example
+
+```ts
+new Paragraph({
+ children: [
+ new TextRun("Full name"),
+ new TextRun({
+ children: [
+ new PositionalTab({
+ alignment: PositionalTabAlignment.RIGHT,
+ relativeTo: PositionalTabRelativeTo.MARGIN,
+ leader: PositionalTabLeader.DOT,
+ }),
+ "John Doe",
+ ],
+ bold: true,
+ }),
+ ],
+}),
+```
+
+### Options
+
+| Option | Type | Description | Possible Values |
+| ---------- | ------------------------- | ------------------------------------- | ------------------------------------------------------------- |
+| alignment | `PositionalTabAlignment` | The alignment of the tab stop | `LEFT`, `RIGHT`, `CENTER` |
+| relativeTo | `PositionalTabRelativeTo` | The relative position of the tab stop | `MARGIN`, `INDENT` |
+| leader | `PositionalTabLeader` | The leader of the tab stop | `NONE`, `DOT`, `HYPHEN`, `UNDERSCORE`, `MIDDLE_DOT`, `EQUALS` |
diff --git a/src/file/paragraph/run/empty-children.spec.ts b/src/file/paragraph/run/empty-children.spec.ts
new file mode 100644
index 0000000000..af9ccb4e35
--- /dev/null
+++ b/src/file/paragraph/run/empty-children.spec.ts
@@ -0,0 +1,229 @@
+import { expect } from "chai";
+
+import { Formatter } from "@export/formatter";
+import {
+ AnnotationReference,
+ CarriageReturn,
+ ContinuationSeparator,
+ DayLong,
+ DayShort,
+ EndnoteReference,
+ FootnoteReferenceElement,
+ LastRenderedPageBreak,
+ MonthLong,
+ MonthShort,
+ NoBreakHyphen,
+ PageNumberElement,
+ Separator,
+ SoftHyphen,
+ Tab,
+ YearLong,
+ YearShort,
+} from "./empty-children";
+
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// ...
+//
+//
+//
+// ...
+//
+
+describe("NoBreakHyphen", () => {
+ describe("#constructor()", () => {
+ it("should create a NoBreakHyphen with correct root key", () => {
+ const tree = new Formatter().format(new NoBreakHyphen());
+ expect(tree).to.deep.equal({
+ "w:noBreakHyphen": {},
+ });
+ });
+ });
+});
+
+describe("SoftHyphen", () => {
+ describe("#constructor()", () => {
+ it("should create a SoftHyphen with correct root key", () => {
+ const tree = new Formatter().format(new SoftHyphen());
+ expect(tree).to.deep.equal({
+ "w:softHyphen": {},
+ });
+ });
+ });
+});
+
+describe("DayShort", () => {
+ describe("#constructor()", () => {
+ it("should create a DayShort with correct root key", () => {
+ const tree = new Formatter().format(new DayShort());
+ expect(tree).to.deep.equal({
+ "w:dayShort": {},
+ });
+ });
+ });
+});
+
+describe("MonthShort", () => {
+ describe("#constructor()", () => {
+ it("should create a MonthShort with correct root key", () => {
+ const tree = new Formatter().format(new MonthShort());
+ expect(tree).to.deep.equal({
+ "w:monthShort": {},
+ });
+ });
+ });
+});
+
+describe("YearShort", () => {
+ describe("#constructor()", () => {
+ it("should create a YearShort with correct root key", () => {
+ const tree = new Formatter().format(new YearShort());
+ expect(tree).to.deep.equal({
+ "w:yearShort": {},
+ });
+ });
+ });
+});
+
+describe("DayLong", () => {
+ describe("#constructor()", () => {
+ it("should create a DayLong with correct root key", () => {
+ const tree = new Formatter().format(new DayLong());
+ expect(tree).to.deep.equal({
+ "w:dayLong": {},
+ });
+ });
+ });
+});
+
+describe("MonthLong", () => {
+ describe("#constructor()", () => {
+ it("should create a MonthLong with correct root key", () => {
+ const tree = new Formatter().format(new MonthLong());
+ expect(tree).to.deep.equal({
+ "w:monthLong": {},
+ });
+ });
+ });
+});
+
+describe("YearLong", () => {
+ describe("#constructor()", () => {
+ it("should create a YearLong with correct root key", () => {
+ const tree = new Formatter().format(new YearLong());
+ expect(tree).to.deep.equal({
+ "w:yearLong": {},
+ });
+ });
+ });
+});
+
+describe("AnnotationReference", () => {
+ describe("#constructor()", () => {
+ it("should create a AnnotationReference with correct root key", () => {
+ const tree = new Formatter().format(new AnnotationReference());
+ expect(tree).to.deep.equal({
+ "w:annotationRef": {},
+ });
+ });
+ });
+});
+
+describe("FootnoteReferenceElement", () => {
+ describe("#constructor()", () => {
+ it("should create a FootnoteReferenceElement with correct root key", () => {
+ const tree = new Formatter().format(new FootnoteReferenceElement());
+ expect(tree).to.deep.equal({
+ "w:footnoteRef": {},
+ });
+ });
+ });
+});
+
+describe("EndnoteReference", () => {
+ describe("#constructor()", () => {
+ it("should create a EndnoteReference with correct root key", () => {
+ const tree = new Formatter().format(new EndnoteReference());
+ expect(tree).to.deep.equal({
+ "w:endnoteRef": {},
+ });
+ });
+ });
+});
+
+describe("Separator", () => {
+ describe("#constructor()", () => {
+ it("should create a Separator with correct root key", () => {
+ const tree = new Formatter().format(new Separator());
+ expect(tree).to.deep.equal({
+ "w:separator": {},
+ });
+ });
+ });
+});
+
+describe("ContinuationSeparator", () => {
+ describe("#constructor()", () => {
+ it("should create a ContinuationSeparator with correct root key", () => {
+ const tree = new Formatter().format(new ContinuationSeparator());
+ expect(tree).to.deep.equal({
+ "w:continuationSeparator": {},
+ });
+ });
+ });
+});
+
+describe("PageNumberElement", () => {
+ describe("#constructor()", () => {
+ it("should create a PageNumberElement with correct root key", () => {
+ const tree = new Formatter().format(new PageNumberElement());
+ expect(tree).to.deep.equal({
+ "w:pgNum": {},
+ });
+ });
+ });
+});
+
+describe("CarriageReturn", () => {
+ describe("#constructor()", () => {
+ it("should create a CarriageReturn with correct root key", () => {
+ const tree = new Formatter().format(new CarriageReturn());
+ expect(tree).to.deep.equal({
+ "w:cr": {},
+ });
+ });
+ });
+});
+
+describe("Tab", () => {
+ describe("#constructor()", () => {
+ it("should create a Tab with correct root key", () => {
+ const tree = new Formatter().format(new Tab());
+ expect(tree).to.deep.equal({
+ "w:tab": {},
+ });
+ });
+ });
+});
+
+describe("LastRenderedPageBreak", () => {
+ describe("#constructor()", () => {
+ it("should create a LastRenderedPageBreak with correct root key", () => {
+ const tree = new Formatter().format(new LastRenderedPageBreak());
+ expect(tree).to.deep.equal({
+ "w:lastRenderedPageBreak": {},
+ });
+ });
+ });
+});
diff --git a/src/file/paragraph/run/empty-children.ts b/src/file/paragraph/run/empty-children.ts
new file mode 100644
index 0000000000..b72ba255d6
--- /dev/null
+++ b/src/file/paragraph/run/empty-children.ts
@@ -0,0 +1,127 @@
+import { EmptyElement } from "@file/xml-components";
+
+//
+// ...
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// ...
+//
+//
+//
+// ...
+//
+//
+//
+
+export class NoBreakHyphen extends EmptyElement {
+ public constructor() {
+ super("w:noBreakHyphen");
+ }
+}
+
+export class SoftHyphen extends EmptyElement {
+ public constructor() {
+ super("w:softHyphen");
+ }
+}
+
+export class DayShort extends EmptyElement {
+ public constructor() {
+ super("w:dayShort");
+ }
+}
+
+export class MonthShort extends EmptyElement {
+ public constructor() {
+ super("w:monthShort");
+ }
+}
+
+export class YearShort extends EmptyElement {
+ public constructor() {
+ super("w:yearShort");
+ }
+}
+
+export class DayLong extends EmptyElement {
+ public constructor() {
+ super("w:dayLong");
+ }
+}
+
+export class MonthLong extends EmptyElement {
+ public constructor() {
+ super("w:monthLong");
+ }
+}
+
+export class YearLong extends EmptyElement {
+ public constructor() {
+ super("w:yearLong");
+ }
+}
+
+export class AnnotationReference extends EmptyElement {
+ public constructor() {
+ super("w:annotationRef");
+ }
+}
+
+export class FootnoteReferenceElement extends EmptyElement {
+ public constructor() {
+ super("w:footnoteRef");
+ }
+}
+
+export class EndnoteReference extends EmptyElement {
+ public constructor() {
+ super("w:endnoteRef");
+ }
+}
+
+export class Separator extends EmptyElement {
+ public constructor() {
+ super("w:separator");
+ }
+}
+
+export class ContinuationSeparator extends EmptyElement {
+ public constructor() {
+ super("w:continuationSeparator");
+ }
+}
+
+export class PageNumberElement extends EmptyElement {
+ public constructor() {
+ super("w:pgNum");
+ }
+}
+
+export class CarriageReturn extends EmptyElement {
+ public constructor() {
+ super("w:cr");
+ }
+}
+
+export class Tab extends EmptyElement {
+ public constructor() {
+ super("w:tab");
+ }
+}
+
+export class LastRenderedPageBreak extends EmptyElement {
+ public constructor() {
+ super("w:lastRenderedPageBreak");
+ }
+}
diff --git a/src/file/paragraph/run/index.ts b/src/file/paragraph/run/index.ts
index 34819e2c02..f459256c59 100644
--- a/src/file/paragraph/run/index.ts
+++ b/src/file/paragraph/run/index.ts
@@ -7,6 +7,7 @@ export * from "./run-fonts";
export * from "./sequential-identifier";
export * from "./underline";
export * from "./emphasis-mark";
-export * from "./tab";
export * from "./simple-field";
export * from "./comment-run";
+export * from "./empty-children";
+export * from "./positional-tab";
diff --git a/src/file/paragraph/run/positional-tab.spec.ts b/src/file/paragraph/run/positional-tab.spec.ts
new file mode 100644
index 0000000000..56fe20c273
--- /dev/null
+++ b/src/file/paragraph/run/positional-tab.spec.ts
@@ -0,0 +1,27 @@
+import { expect } from "chai";
+
+import { Formatter } from "@export/formatter";
+
+import { PositionalTab, PositionalTabAlignment, PositionalTabLeader, PositionalTabRelativeTo } from "./positional-tab";
+
+describe("PositionalTab", () => {
+ it("should create a PositionalTab with correct root key", () => {
+ const tree = new Formatter().format(
+ new PositionalTab({
+ alignment: PositionalTabAlignment.CENTER,
+ relativeTo: PositionalTabRelativeTo.MARGIN,
+ leader: PositionalTabLeader.DOT,
+ }),
+ );
+
+ expect(tree).to.deep.equal({
+ "w:ptab": {
+ _attr: {
+ "w:alignment": "center",
+ "w:relativeTo": "margin",
+ "w:leader": "dot",
+ },
+ },
+ });
+ });
+});
diff --git a/src/file/paragraph/run/positional-tab.ts b/src/file/paragraph/run/positional-tab.ts
new file mode 100644
index 0000000000..e3b002ed81
--- /dev/null
+++ b/src/file/paragraph/run/positional-tab.ts
@@ -0,0 +1,80 @@
+import { NextAttributeComponent, XmlComponent } from "@file/xml-components";
+
+//
+//
+//
+//
+//
+//
+//
+export enum PositionalTabAlignment {
+ LEFT = "left",
+ CENTER = "center",
+ RIGHT = "right",
+}
+
+//
+//
+//
+//
+//
+//
+export enum PositionalTabRelativeTo {
+ MARGIN = "margin",
+ INDENT = "indent",
+}
+
+//
+//
+//
+//
+//
+//
+//
+//
+//
+export enum PositionalTabLeader {
+ NONE = "none",
+ DOT = "dot",
+ HYPHEN = "hyphen",
+ UNDERSCORE = "underscore",
+ MIDDLE_DOT = "middleDot",
+}
+
+export interface PositionalTabOptions {
+ readonly alignment: PositionalTabAlignment;
+ readonly relativeTo: PositionalTabRelativeTo;
+ readonly leader: PositionalTabLeader;
+}
+
+//
+//
+//
+//
+//
+export class PositionalTab extends XmlComponent {
+ public constructor(options: PositionalTabOptions) {
+ super("w:ptab");
+
+ this.root.push(
+ new NextAttributeComponent<{
+ readonly alignment: PositionalTabAlignment;
+ readonly relativeTo: PositionalTabRelativeTo;
+ readonly leader: PositionalTabLeader;
+ }>({
+ alignment: {
+ key: "w:alignment",
+ value: options.alignment,
+ },
+ relativeTo: {
+ key: "w:relativeTo",
+ value: options.relativeTo,
+ },
+ leader: {
+ key: "w:leader",
+ value: options.leader,
+ },
+ }),
+ );
+ }
+}
diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts
index 4fb047e0c7..023fff404e 100644
--- a/src/file/paragraph/run/run.ts
+++ b/src/file/paragraph/run/run.ts
@@ -9,10 +9,91 @@ import { Begin, End, Separate } from "./field";
import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number";
import { IRunPropertiesOptions, RunProperties } from "./properties";
import { Text } from "./run-components/text";
-import { Tab } from "./tab";
+import {
+ AnnotationReference,
+ CarriageReturn,
+ ContinuationSeparator,
+ DayLong,
+ DayShort,
+ EndnoteReference,
+ FootnoteReferenceElement,
+ LastRenderedPageBreak,
+ MonthLong,
+ MonthShort,
+ NoBreakHyphen,
+ PageNumberElement,
+ Separator,
+ SoftHyphen,
+ Tab,
+ YearLong,
+ YearShort,
+} from "./empty-children";
+import { PositionalTab } from "./positional-tab";
export interface IRunOptions extends IRunPropertiesOptions {
- readonly children?: readonly (Begin | FieldInstruction | Separate | End | PageNumber | FootnoteReferenceRun | Tab | string)[];
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ readonly children?: readonly (
+ | Begin
+ | FieldInstruction
+ | Separate
+ | End
+ | PageNumber
+ | FootnoteReferenceRun
+ | Break
+ | AnnotationReference
+ | CarriageReturn
+ | ContinuationSeparator
+ | DayLong
+ | DayShort
+ | EndnoteReference
+ | FootnoteReferenceElement
+ | LastRenderedPageBreak
+ | MonthLong
+ | MonthShort
+ | NoBreakHyphen
+ | PageNumberElement
+ | Separator
+ | SoftHyphen
+ | Tab
+ | YearLong
+ | YearShort
+ | PositionalTab
+ | string
+ )[];
readonly break?: number;
readonly text?: string;
}
diff --git a/src/file/paragraph/run/tab.spec.ts b/src/file/paragraph/run/tab.spec.ts
deleted file mode 100644
index 03381200a5..0000000000
--- a/src/file/paragraph/run/tab.spec.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { expect } from "chai";
-
-import { Formatter } from "@export/formatter";
-
-import { Tab } from "./tab";
-
-describe("Tab", () => {
- let tab: Tab;
-
- beforeEach(() => {
- tab = new Tab();
- });
-
- describe("#constructor()", () => {
- it("should create a Tab with correct root key", () => {
- const tree = new Formatter().format(tab);
- expect(tree).to.deep.equal({
- "w:tab": {},
- });
- });
- });
-});
diff --git a/src/file/paragraph/run/tab.ts b/src/file/paragraph/run/tab.ts
deleted file mode 100644
index 061e3deb3a..0000000000
--- a/src/file/paragraph/run/tab.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_tab_topic_ID0EM6AO.html
-import { XmlComponent } from "@file/xml-components";
-
-//
-// ...
-//
-//
-// TODO: this is unused and undocumented currently.
-// I think the intended use was for users to import, and insert as a child of `Run`.
-export class Tab extends XmlComponent {
- public constructor() {
- super("w:tab");
- }
-}
diff --git a/src/file/xml-components/simple-elements.ts b/src/file/xml-components/simple-elements.ts
index 8f4b3f5e90..b81901232c 100644
--- a/src/file/xml-components/simple-elements.ts
+++ b/src/file/xml-components/simple-elements.ts
@@ -35,6 +35,11 @@ export class HpsMeasureElement extends XmlComponent {
// This represents element type CT_String, which indicate a string value.
//
+//
+export class EmptyElement extends XmlComponent {}
+
+// This represents element type CT_Empty, which indicate aan empty element.
+//
//
//
//