Merge pull request #447 from dolanmiu/feat/declaritive-numbering

Declarative numbering
This commit is contained in:
Dolan
2019-11-08 10:53:25 +00:00
committed by GitHub
20 changed files with 1136 additions and 755 deletions

View File

@ -1,7 +1,7 @@
// Example on how to customise the look at feel using Styles // Example on how to customise the look at feel using Styles
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build"; import { AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun, UnderlineType } from "../build";
const doc = new Document({ const doc = new Document({
creator: "Clippy", creator: "Clippy",
@ -83,15 +83,23 @@ const doc = new Document({
}, },
], ],
}, },
numbering: {
config: [
{
reference: "my-crazy-numbering",
levels: [
{
level: 0,
format: "lowerLetter",
text: "%1)",
alignment: AlignmentType.LEFT,
},
],
},
],
},
}); });
const numberedAbstract = doc.Numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "left");
const letterNumbering = doc.Numbering.createConcreteNumbering(numberedAbstract);
const letterNumbering5 = doc.Numbering.createConcreteNumbering(numberedAbstract);
letterNumbering5.overrideLevel(0, 5);
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
@ -106,21 +114,21 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "Option1", text: "Option1",
numbering: { numbering: {
num: letterNumbering, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Option5 -- override 2 to 5", text: "Option5 -- override 2 to 5",
numbering: { numbering: {
num: letterNumbering, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Option3", text: "Option3",
numbering: { numbering: {
num: letterNumbering, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),

View File

@ -1,23 +1,37 @@
// Numbered lists // Numbered lists
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Numbering, Packer, Paragraph } from "../build"; import { AlignmentType, Document, Packer, Paragraph } from "../build";
const doc = new Document(); const doc = new Document({
numbering: {
const numbering = new Numbering(); config: [
{
const abstractNum = numbering.createAbstractNumbering(); levels: [
abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 }); {
level: 0,
const concrete = numbering.createConcreteNumbering(abstractNum); format: "upperRoman",
text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
},
},
},
],
reference: "my-crazy-reference",
},
],
},
});
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
text: "line with contextual spacing", text: "line with contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: true, contextualSpacing: true,
@ -28,7 +42,7 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "line with contextual spacing", text: "line with contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: true, contextualSpacing: true,
@ -39,7 +53,7 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "line without contextual spacing", text: "line without contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: false, contextualSpacing: false,
@ -50,7 +64,7 @@ doc.addSection({
new Paragraph({ new Paragraph({
text: "line without contextual spacing", text: "line without contextual spacing",
numbering: { numbering: {
num: concrete, reference: "my-crazy-reference",
level: 0, level: 0,
}, },
contextualSpacing: false, contextualSpacing: false,

View File

@ -1,46 +1,80 @@
// Numbering and bullet points example // Numbering and bullet points example
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Numbering, Packer, Paragraph } from "../build"; import { AlignmentType, Document, Packer, Paragraph } from "../build";
const doc = new Document(); const doc = new Document({
numbering: {
const numbering = new Numbering(); config: [
{
const abstractNum = numbering.createAbstractNumbering(); reference: "my-crazy-numbering",
abstractNum.createLevel(0, "upperRoman", "%1", "start").indent({ left: 720, hanging: 260 }); levels: [
abstractNum.createLevel(1, "decimal", "%2.", "start").indent({ left: 1440, hanging: 980 }); {
abstractNum.createLevel(2, "lowerLetter", "%3)", "start").indent({ left: 2160, hanging: 1700 }); level: 0,
format: "upperRoman",
const concrete = numbering.createConcreteNumbering(abstractNum); text: "%1",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 720, hanging: 260 },
},
},
},
{
level: 1,
format: "decimal",
text: "%2.",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 1440, hanging: 980 },
},
},
},
{
level: 2,
format: "lowerLetter",
text: "%3)",
alignment: AlignmentType.START,
style: {
paragraph: {
indent: { left: 2160, hanging: 1700 },
},
},
},
],
},
],
},
});
doc.addSection({ doc.addSection({
children: [ children: [
new Paragraph({ new Paragraph({
text: "Hey you", text: "Hey you",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 0, level: 0,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "What's up fam", text: "What's up fam",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 1, level: 1,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Hello World 2", text: "Hello World 2",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 1, level: 1,
}, },
}), }),
new Paragraph({ new Paragraph({
text: "Yeah boi", text: "Yeah boi",
numbering: { numbering: {
num: concrete, reference: "my-crazy-numbering",
level: 2, level: 2,
}, },
}), }),

View File

@ -20,11 +20,7 @@ This method is useful for adding different [text](text.md) with different styles
```ts ```ts
const paragraph = new Paragraph({ const paragraph = new Paragraph({
children: [ children: [new TextRun("Lorem Ipsum Foo Bar"), new TextRun("Hello World"), new SymbolRun("F071")],
new TextRun("Lorem Ipsum Foo Bar"),
new TextRun("Hello World"),
new SymbolRun("F071"),
],
}); });
``` ```
@ -60,27 +56,27 @@ doc.addSection({
This is the list of options for a paragraph. A detailed explanation is below: This is the list of options for a paragraph. A detailed explanation is below:
| Property | Type | Mandatory? | Possible Values | | Property | Type | Mandatory? | Possible Values |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
| [text](#text) | `string` | Optional | | | [text](#text) | `string` | Optional | |
| [heading](#heading) | `HeadingLevel` | Optional | `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`, `TITLE` | | [heading](#heading) | `HeadingLevel` | Optional | `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`, `TITLE` |
| [border](#border) | `IBorderOptions` | Optional | `top`, `bottom`, `left`, `right`. Each of these are of type IBorderPropertyOptions. Click here for Example | | [border](#border) | `IBorderOptions` | Optional | `top`, `bottom`, `left`, `right`. Each of these are of type IBorderPropertyOptions. Click here for Example |
| [spacing](#spacing) | `ISpacingProperties` | Optional | See below for ISpacingProperties | | [spacing](#spacing) | `ISpacingProperties` | Optional | See below for ISpacingProperties |
| [outlineLevel](#outline-level) | `number` | Optional | | | [outlineLevel](#outline-level) | `number` | Optional | |
| alignment | `AlignmentType` | Optional | | | alignment | `AlignmentType` | Optional | |
| heading | `HeadingLevel` | Optional | | | heading | `HeadingLevel` | Optional | |
| bidirectional | `boolean` | Optional | | | bidirectional | `boolean` | Optional | |
| thematicBreak | `boolean` | Optional | | | thematicBreak | `boolean` | Optional | |
| pageBreakBefore | `boolean` | Optional | | | pageBreakBefore | `boolean` | Optional | |
| contextualSpacing | `boolean` | Optional | | | contextualSpacing | `boolean` | Optional | |
| indent | `IIndentAttributesProperties` | Optional | | | indent | `IIndentAttributesProperties` | Optional | |
| keepLines | `boolean` | Optional | | | keepLines | `boolean` | Optional | |
| keepNext | `boolean` | Optional | | | keepNext | `boolean` | Optional | |
| children | `(TextRun or PictureRun or Hyperlink)[]` | Optional | | | children | `(TextRun or PictureRun or Hyperlink)[]` | Optional | |
| style | `string` | Optional | | | style | `string` | Optional | |
| tabStop | `{ left?: ITabStopOptions; right?: ITabStopOptions; maxRight?: { leader: LeaderType; }; center?: ITabStopOptions }` | Optional | | | tabStop | `{ left?: ITabStopOptions; right?: ITabStopOptions; maxRight?: { leader: LeaderType; }; center?: ITabStopOptions }` | Optional | |
| bullet | `{ level: number }` | Optional | | | bullet | `{ level: number }` | Optional | |
| numbering | `{ num: Num; level: number; custom?: boolean }` | Optional | | | numbering | `{ num: ConcreteNumbering; level: number; custom?: boolean }` | Optional | |
## Text ## Text
@ -252,10 +248,7 @@ To move to a new page (insert a page break):
```ts ```ts
const paragraph = new docx.Paragraph({ const paragraph = new docx.Paragraph({
children: [ children: [new TextRun("Amazing Heading"), new PageBreak()],
new TextRun("Amazing Heading"),
new PageBreak(),
]
}); });
``` ```

View File

@ -4,6 +4,7 @@ import * as xml from "xml";
import { File } from "file"; import { File } from "file";
import { Formatter } from "../formatter"; import { Formatter } from "../formatter";
import { ImageReplacer } from "./image-replacer"; import { ImageReplacer } from "./image-replacer";
import { NumberingReplacer } from "./numbering-replacer";
interface IXmlifyedFile { interface IXmlifyedFile {
readonly data: string; readonly data: string;
@ -30,10 +31,12 @@ interface IXmlifyedFileMapping {
export class Compiler { export class Compiler {
private readonly formatter: Formatter; private readonly formatter: Formatter;
private readonly imageReplacer: ImageReplacer; private readonly imageReplacer: ImageReplacer;
private readonly numberingReplacer: NumberingReplacer;
constructor() { constructor() {
this.formatter = new Formatter(); this.formatter = new Formatter();
this.imageReplacer = new ImageReplacer(); this.imageReplacer = new ImageReplacer();
this.numberingReplacer = new NumberingReplacer();
} }
public compile(file: File, prettifyXml?: boolean): JSZip { public compile(file: File, prettifyXml?: boolean): JSZip {
@ -89,8 +92,9 @@ export class Compiler {
Document: { Document: {
data: (() => { data: (() => {
const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount); const xmlData = this.imageReplacer.replace(documentXmlData, documentMediaDatas, documentRelationshipCount);
const referenedXmlData = this.numberingReplacer.replace(xmlData, file.Numbering.ConcreteNumbering);
return xmlData; return referenedXmlData;
})(), })(),
path: "word/document.xml", path: "word/document.xml",
}, },

View File

@ -0,0 +1,17 @@
import { ConcreteNumbering } from "file";
export class NumberingReplacer {
public replace(xmlData: string, concreteNumberings: ConcreteNumbering[]): string {
let currentXmlData = xmlData;
for (const concreteNumbering of concreteNumberings) {
if (!concreteNumbering.reference) {
continue;
}
currentXmlData = currentXmlData.replace(new RegExp(`{${concreteNumbering.reference}}`, "g"), concreteNumbering.id.toString());
}
return currentXmlData;
}
}

View File

@ -1,6 +1,7 @@
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes"; import { DocumentAttributes } from "../document/document-attributes";
import { INumberingOptions } from "../numbering";
import { IStylesOptions } from "../styles"; import { IStylesOptions } from "../styles";
import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components"; import { Created, Creator, Description, Keywords, LastModifiedBy, Modified, Revision, Subject, Title } from "./components";
@ -14,6 +15,7 @@ export interface IPropertiesOptions {
readonly revision?: string; readonly revision?: string;
readonly externalStyles?: string; readonly externalStyles?: string;
readonly styles?: IStylesOptions; readonly styles?: IStylesOptions;
readonly numbering?: INumberingOptions;
} }
export class CoreProperties extends XmlComponent { export class CoreProperties extends XmlComponent {

View File

@ -71,7 +71,13 @@ export class File {
sections: ISectionOptions[] = [], sections: ISectionOptions[] = [],
) { ) {
this.coreProperties = new CoreProperties(options); this.coreProperties = new CoreProperties(options);
this.numbering = new Numbering(); this.numbering = new Numbering(
options.numbering
? options.numbering
: {
config: [],
},
);
this.docRelationships = new Relationships(); this.docRelationships = new Relationships();
this.fileRelationships = new Relationships(); this.fileRelationships = new Relationships();
this.appProperties = new AppProperties(); this.appProperties = new AppProperties();

View File

@ -0,0 +1,605 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { EMPTY_OBJECT } from "file/xml-components";
import { AlignmentType, TabStopPosition } from "../paragraph";
import { UnderlineType } from "../paragraph/run/underline";
import { ShadingType } from "../table";
import { AbstractNumbering } from "./abstract-numbering";
describe("AbstractNumbering", () => {
it("stores its ID at its .id property", () => {
const abstractNumbering = new AbstractNumbering(5, []);
expect(abstractNumbering.id).to.equal(5);
});
describe("#createLevel", () => {
it("creates a level with the given characteristics", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 3,
format: "lowerLetter",
text: "%1)",
alignment: AlignmentType.END,
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
it("uses 'start' as the default alignment", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 3,
format: "lowerLetter",
text: "%1)",
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
describe("formatting methods: paragraph properties", () => {
it("#indent", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
indent: { left: 720 },
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }],
});
});
it("#spacing", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
spacing: { before: 50, after: 150 },
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }],
});
});
it("#center", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.CENTER,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }],
});
});
it("#left", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.LEFT,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }],
});
});
it("#right", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.RIGHT,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }],
});
});
it("#justified", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
alignment: AlignmentType.JUSTIFIED,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }],
});
});
it("#thematicBreak", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
thematicBreak: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:pBdr": [
{
"w:bottom": {
_attr: {
"w:color": "auto",
"w:space": 1,
"w:val": "single",
"w:sz": 6,
},
},
},
],
},
],
});
});
it("#leftTabStop", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
leftTabStop: 1200,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }],
},
],
});
});
it("#maxRightTabStop", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
rightTabStop: TabStopPosition.MAX,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }],
},
],
});
});
it("#keepLines", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
keepLines: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:keepLines": EMPTY_OBJECT }],
});
});
it("#keepNext", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
paragraph: {
keepNext: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:pPr": [{ "w:keepNext": EMPTY_OBJECT }],
});
});
});
describe("formatting methods: run properties", () => {
it("#size", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
size: 24,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }],
});
});
it("#smallCaps", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
smallCaps: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }],
});
});
it("#allCaps", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
allCaps: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }],
});
});
it("#strike", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
strike: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }],
});
});
it("#doubleStrike", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
doubleStrike: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }],
});
});
it("#subScript", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
subScript: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }],
});
});
it("#superScript", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
superScript: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }],
});
});
it("#font", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
font: "Times",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [
{ "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } },
],
});
});
it("#bold", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
bold: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:b": { _attr: { "w:val": true } } }],
});
});
it("#italics", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
italics: true,
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:i": { _attr: { "w:val": true } } }],
});
});
it("#highlight", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
highlight: "005599",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
});
});
it("#shadow", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
shadow: {
type: ShadingType.PERCENT_10,
fill: "00FFFF",
color: "FF0000",
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
underline: {},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }],
});
});
it("should set the style if given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
underline: {
type: UnderlineType.DOUBLE,
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "double" } } }],
});
});
it("should set the style and color if given", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
underline: {
type: UnderlineType.DOUBLE,
color: "005599",
},
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "double", "w:color": "005599" } } }],
});
});
});
it("#color", () => {
const abstractNumbering = new AbstractNumbering(1, [
{
level: 0,
format: "lowerRoman",
text: "%0.",
style: {
run: {
color: "123456",
},
},
},
]);
const tree = new Formatter().format(abstractNumbering);
expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({
"w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }],
});
});
});
});
});

View File

@ -1,5 +1,6 @@
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { Level } from "./level";
import { ILevelsOptions, Level } from "./level";
import { MultiLevelType } from "./multi-level-type"; import { MultiLevelType } from "./multi-level-type";
interface IAbstractNumberingAttributesProperties { interface IAbstractNumberingAttributesProperties {
@ -17,7 +18,7 @@ class AbstractNumberingAttributes extends XmlAttributeComponent<IAbstractNumberi
export class AbstractNumbering extends XmlComponent { export class AbstractNumbering extends XmlComponent {
public readonly id: number; public readonly id: number;
constructor(id: number) { constructor(id: number, levelOptions: ILevelsOptions[]) {
super("w:abstractNum"); super("w:abstractNum");
this.root.push( this.root.push(
new AbstractNumberingAttributes({ new AbstractNumberingAttributes({
@ -27,15 +28,9 @@ export class AbstractNumbering extends XmlComponent {
); );
this.root.push(new MultiLevelType("hybridMultilevel")); this.root.push(new MultiLevelType("hybridMultilevel"));
this.id = id; this.id = id;
}
public addLevel(level: Level): void { for (const option of levelOptions) {
this.root.push(level); this.root.push(new Level(option));
} }
public createLevel(num: number, format: string, text: string, align: string = "start"): Level {
const level = new Level(num, format, text, align);
this.addLevel(level);
return level;
} }
} }

View File

@ -0,0 +1,79 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { LevelForOverride } from "./level";
import { ConcreteNumbering } from "./num";
describe("ConcreteNumbering", () => {
describe("#overrideLevel", () => {
let concreteNumbering: ConcreteNumbering;
beforeEach(() => {
concreteNumbering = new ConcreteNumbering(0, 1);
});
it("sets a new override level for the given level number", () => {
concreteNumbering.overrideLevel(3);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{ _attr: { "w:ilvl": 3 } },
{
"w:lvl": [
{ _attr: { "w:ilvl": 3, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
it("sets the startOverride element if start is given", () => {
concreteNumbering.overrideLevel(1, 9);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{
_attr: {
"w:ilvl": 1,
},
},
{
"w:startOverride": {
_attr: {
"w:val": 9,
},
},
},
{
"w:lvl": [
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
it("sets the lvl element if overrideLevel.Level is accessed", () => {
const ol = concreteNumbering.overrideLevel(1);
expect(ol.Level).to.be.instanceof(LevelForOverride);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{ _attr: { "w:ilvl": 1 } },
{
"w:lvl": [
{ _attr: { "w:ilvl": 1, "w15:tentative": 1 } },
{ "w:start": { _attr: { "w:val": 1 } } },
{ "w:lvlJc": { _attr: { "w:val": "start" } } },
],
},
],
});
});
});
});

View File

@ -2,9 +2,7 @@ import { Attributes, XmlAttributeComponent, XmlComponent } from "file/xml-compon
import { import {
Alignment, Alignment,
AlignmentType, AlignmentType,
IIndentAttributesProperties,
Indent, Indent,
ISpacingProperties,
KeepLines, KeepLines,
KeepNext, KeepNext,
Spacing, Spacing,
@ -15,7 +13,7 @@ import {
import { ParagraphProperties } from "../paragraph/properties"; import { ParagraphProperties } from "../paragraph/properties";
import * as formatting from "../paragraph/run/formatting"; import * as formatting from "../paragraph/run/formatting";
import { RunProperties } from "../paragraph/run/properties"; import { RunProperties } from "../paragraph/run/properties";
import { UnderlineType } from "../paragraph/run/underline"; import { IParagraphStyleOptions2, IRunStyleOptions } from "../styles/style-options";
interface ILevelAttributesProperties { interface ILevelAttributesProperties {
readonly ilvl?: number; readonly ilvl?: number;
@ -63,7 +61,7 @@ class LevelText extends XmlComponent {
} }
class LevelJc extends XmlComponent { class LevelJc extends XmlComponent {
constructor(value: string) { constructor(value: AlignmentType) {
super("w:lvlJc"); super("w:lvlJc");
this.root.push( this.root.push(
new Attributes({ new Attributes({
@ -79,6 +77,19 @@ export enum LevelSuffix {
TAB = "tab", TAB = "tab",
} }
export interface ILevelsOptions {
readonly level: number;
readonly format?: string;
readonly text?: string;
readonly alignment?: AlignmentType;
readonly start?: number;
readonly suffix?: LevelSuffix;
readonly style?: {
readonly run?: IRunStyleOptions;
readonly paragraph?: IParagraphStyleOptions2;
};
}
class Suffix extends XmlComponent { class Suffix extends XmlComponent {
constructor(value: LevelSuffix) { constructor(value: LevelSuffix) {
super("w:suff"); super("w:suff");
@ -94,7 +105,7 @@ export class LevelBase extends XmlComponent {
private readonly paragraphProperties: ParagraphProperties; private readonly paragraphProperties: ParagraphProperties;
private readonly runProperties: RunProperties; private readonly runProperties: RunProperties;
constructor(level: number, start?: number, numberFormat?: string, levelText?: string, lvlJc?: string) { constructor({ level, format, text, alignment = AlignmentType.START, start = 1, style, suffix }: ILevelsOptions) {
super("w:lvl"); super("w:lvl");
this.root.push( this.root.push(
new LevelAttributes({ new LevelAttributes({
@ -103,17 +114,15 @@ export class LevelBase extends XmlComponent {
}), }),
); );
if (start !== undefined) { this.root.push(new Start(start));
this.root.push(new Start(start)); this.root.push(new LevelJc(alignment));
if (format) {
this.root.push(new NumberFormat(format));
} }
if (numberFormat !== undefined) {
this.root.push(new NumberFormat(numberFormat)); if (text) {
} this.root.push(new LevelText(text));
if (levelText !== undefined) {
this.root.push(new LevelText(levelText));
}
if (lvlJc !== undefined) {
this.root.push(new LevelJc(lvlJc));
} }
this.paragraphProperties = new ParagraphProperties({}); this.paragraphProperties = new ParagraphProperties({});
@ -121,156 +130,112 @@ export class LevelBase extends XmlComponent {
this.root.push(this.paragraphProperties); this.root.push(this.paragraphProperties);
this.root.push(this.runProperties); this.root.push(this.runProperties);
}
public setSuffix(value: LevelSuffix): LevelBase { if (suffix) {
this.root.push(new Suffix(value)); this.root.push(new Suffix(suffix));
return this; }
}
public addParagraphProperty(property: XmlComponent): Level { if (style) {
this.paragraphProperties.push(property); if (style.run) {
return this; if (style.run.size) {
} this.runProperties.push(new formatting.Size(style.run.size));
}
public addRunProperty(property: XmlComponent): Level { if (style.run.bold) {
this.runProperties.push(property); this.runProperties.push(new formatting.Bold());
return this; }
}
// ---------- Run formatting ---------------------- // if (style.run.italics) {
this.runProperties.push(new formatting.Italics());
}
public size(twips: number): Level { if (style.run.smallCaps) {
this.addRunProperty(new formatting.Size(twips)); this.runProperties.push(new formatting.SmallCaps());
return this; }
}
public bold(): Level { if (style.run.allCaps) {
this.addRunProperty(new formatting.Bold()); this.runProperties.push(new formatting.Caps());
return this; }
}
public italics(): Level { if (style.run.strike) {
this.addRunProperty(new formatting.Italics()); this.runProperties.push(new formatting.Strike());
return this; }
}
public smallCaps(): Level { if (style.run.doubleStrike) {
this.addRunProperty(new formatting.SmallCaps()); this.runProperties.push(new formatting.DoubleStrike());
return this; }
}
public allCaps(): Level { if (style.run.subScript) {
this.addRunProperty(new formatting.Caps()); this.runProperties.push(new formatting.SubScript());
return this; }
}
public strike(): Level { if (style.run.superScript) {
this.addRunProperty(new formatting.Strike()); this.runProperties.push(new formatting.SuperScript());
return this; }
}
public doubleStrike(): Level { if (style.run.underline) {
this.addRunProperty(new formatting.DoubleStrike()); this.runProperties.push(new formatting.Underline(style.run.underline.type, style.run.underline.color));
return this; }
}
public subScript(): Level { if (style.run.color) {
this.addRunProperty(new formatting.SubScript()); this.runProperties.push(new formatting.Color(style.run.color));
return this; }
}
public superScript(): Level { if (style.run.font) {
this.addRunProperty(new formatting.SuperScript()); this.runProperties.push(new formatting.RunFonts(style.run.font));
return this; }
}
public underline(underlineType?: UnderlineType, color?: string): Level { if (style.run.highlight) {
this.addRunProperty(new formatting.Underline(underlineType, color)); this.runProperties.push(new formatting.Highlight(style.run.highlight));
return this; }
}
public color(color: string): Level { if (style.run.shadow) {
this.addRunProperty(new formatting.Color(color)); this.runProperties.push(new formatting.Shading(style.run.shadow.type, style.run.shadow.fill, style.run.shadow.color));
return this; }
} }
public font(fontName: string): Level { if (style.paragraph) {
this.addRunProperty(new formatting.RunFonts(fontName)); if (style.paragraph.alignment) {
return this; this.paragraphProperties.push(new Alignment(style.paragraph.alignment));
} }
public highlight(color: string): Level { if (style.paragraph.thematicBreak) {
this.addRunProperty(new formatting.Highlight(color)); this.paragraphProperties.push(new ThematicBreak());
return this; }
}
public shadow(value: string, fill: string, color: string): Level { if (style.paragraph.rightTabStop) {
this.addRunProperty(new formatting.Shading(value, fill, color)); this.paragraphProperties.push(new TabStop(TabStopType.RIGHT, style.paragraph.rightTabStop));
return this; }
}
// --------------------- Paragraph formatting ------------------------ //
public center(): Level { if (style.paragraph.leftTabStop) {
this.addParagraphProperty(new Alignment(AlignmentType.CENTER)); this.paragraphProperties.push(new TabStop(TabStopType.LEFT, style.paragraph.leftTabStop));
return this; }
}
public left(): Level { if (style.paragraph.indent) {
this.addParagraphProperty(new Alignment(AlignmentType.LEFT)); this.paragraphProperties.push(new Indent(style.paragraph.indent));
return this; }
}
public right(): Level { if (style.paragraph.spacing) {
this.addParagraphProperty(new Alignment(AlignmentType.RIGHT)); this.paragraphProperties.push(new Spacing(style.paragraph.spacing));
return this; }
}
public justified(): Level { if (style.paragraph.keepNext) {
this.addParagraphProperty(new Alignment(AlignmentType.BOTH)); this.paragraphProperties.push(new KeepNext());
return this; }
}
public thematicBreak(): Level { if (style.paragraph.keepLines) {
this.addParagraphProperty(new ThematicBreak()); this.paragraphProperties.push(new KeepLines());
return this; }
} }
}
public rightTabStop(position: number): Level {
return this.addParagraphProperty(new TabStop(TabStopType.RIGHT, position));
}
public leftTabStop(position: number): Level {
this.addParagraphProperty(new TabStop(TabStopType.LEFT, position));
return this;
}
public indent(attrs: IIndentAttributesProperties): Level {
this.addParagraphProperty(new Indent(attrs));
return this;
}
public spacing(params: ISpacingProperties): Level {
this.addParagraphProperty(new Spacing(params));
return this;
}
public keepNext(): Level {
this.addParagraphProperty(new KeepNext());
return this;
}
public keepLines(): Level {
this.addParagraphProperty(new KeepLines());
return this;
} }
} }
export class Level extends LevelBase { export class Level extends LevelBase {
// This is the level that sits under abstractNum. We make a // This is the level that sits under abstractNum. We make a
// handful of properties required // handful of properties required
constructor(level: number, numberFormat: string, levelText: string, lvlJc: string) { constructor(options: ILevelsOptions) {
super(level, 1, numberFormat, levelText, lvlJc); super(options);
} }
} }

View File

@ -20,10 +20,10 @@ class NumAttributes extends XmlAttributeComponent<INumAttributesProperties> {
protected readonly xmlKeys = { numId: "w:numId" }; protected readonly xmlKeys = { numId: "w:numId" };
} }
export class Num extends XmlComponent { export class ConcreteNumbering extends XmlComponent {
public readonly id: number; public readonly id: number;
constructor(numId: number, abstractNumId: number) { constructor(numId: number, abstractNumId: number, public readonly reference?: string) {
super("w:num"); super("w:num");
this.root.push( this.root.push(
new NumAttributes({ new NumAttributes({
@ -55,7 +55,9 @@ export class LevelOverride extends XmlComponent {
this.root.push(new StartOverride(start)); this.root.push(new StartOverride(start));
} }
this.lvl = new LevelForOverride(this.levelNum); this.lvl = new LevelForOverride({
level: this.levelNum,
});
this.root.push(this.lvl); this.root.push(this.lvl);
} }

View File

@ -2,24 +2,15 @@ import { expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { AbstractNumbering } from "./abstract-numbering";
import { LevelForOverride } from "./level";
import { Num } from "./num";
import { Numbering } from "./numbering"; import { Numbering } from "./numbering";
import { EMPTY_OBJECT } from "file/xml-components";
import { TabStopPosition } from "../paragraph";
import { UnderlineType } from "../paragraph/run/underline";
describe("Numbering", () => { describe("Numbering", () => {
let numbering: Numbering;
beforeEach(() => {
numbering = new Numbering();
});
describe("#constructor", () => { describe("#constructor", () => {
it("creates a default numbering with one abstract and one concrete instance", () => { it("creates a default numbering with one abstract and one concrete instance", () => {
const numbering = new Numbering({
config: [],
});
const tree = new Formatter().format(numbering); const tree = new Formatter().format(numbering);
expect(Object.keys(tree)).to.deep.equal(["w:numbering"]); expect(Object.keys(tree)).to.deep.equal(["w:numbering"]);
const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]); const abstractNums = tree["w:numbering"].filter((el) => el["w:abstractNum"]);
@ -48,418 +39,4 @@ describe("Numbering", () => {
}); });
}); });
}); });
describe("#createAbstractNumbering", () => {
it("returns a new AbstractNumbering instance", () => {
const a2 = numbering.createAbstractNumbering();
expect(a2).to.be.instanceof(AbstractNumbering);
});
it("assigns a unique ID to each abstract numbering it creates", () => {
const a2 = numbering.createAbstractNumbering();
const a3 = numbering.createAbstractNumbering();
expect(a2.id).not.to.equal(a3.id);
});
});
describe("#createConcreteNumbering", () => {
it("returns a new Num instance with its abstract ID set to the AbstractNumbering's ID", () => {
const a2 = numbering.createAbstractNumbering();
const n = numbering.createConcreteNumbering(a2);
expect(n).to.be.instanceof(Num);
const tree = new Formatter().format(numbering);
const serializedN = tree["w:numbering"].find((obj) => obj["w:num"] && obj["w:num"][0]._attr["w:numId"] === n.id);
expect(serializedN["w:num"][1]["w:abstractNumId"]._attr["w:val"]).to.equal(a2.id);
});
it("assigns a unique ID to each concrete numbering it creates", () => {
const a2 = numbering.createAbstractNumbering();
const n = numbering.createConcreteNumbering(a2);
const n2 = numbering.createConcreteNumbering(a2);
expect(n.id).not.to.equal(n2.id);
});
});
});
describe("AbstractNumbering", () => {
it("stores its ID at its .id property", () => {
const abstractNumbering = new AbstractNumbering(5);
expect(abstractNumbering.id).to.equal(5);
});
describe("#createLevel", () => {
it("creates a level with the given characteristics", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)", "end");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "end" } } });
expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
it("uses 'start' as the default alignment", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(3, "lowerLetter", "%1)");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({ _attr: { "w:ilvl": 3, "w15:tentative": 1 } });
expect(tree["w:lvl"]).to.include({ "w:start": { _attr: { "w:val": 1 } } });
expect(tree["w:lvl"]).to.include({ "w:lvlJc": { _attr: { "w:val": "start" } } });
expect(tree["w:lvl"]).to.include({ "w:numFmt": { _attr: { "w:val": "lowerLetter" } } });
expect(tree["w:lvl"]).to.include({ "w:lvlText": { _attr: { "w:val": "%1)" } } });
});
describe("formatting methods: paragraph properties", () => {
it("#indent", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").indent({ left: 720 });
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:ind": { _attr: { "w:left": 720 } } }],
});
});
it("#spacing", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").spacing({ before: 50, after: 150 });
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:spacing": { _attr: { "w:before": 50, "w:after": 150 } } }],
});
});
it("#center", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerLetter", "%0.").center();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "center" } } }],
});
});
it("#left", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.", "left").left();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "left" } } }],
});
});
it("#right", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").right();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "right" } } }],
});
});
it("#justified", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").justified();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:jc": { _attr: { "w:val": "both" } } }],
});
});
it("#thematicBreak", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").thematicBreak();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [
{
"w:pBdr": [
{
"w:bottom": {
_attr: {
"w:color": "auto",
"w:space": 1,
"w:val": "single",
"w:sz": 6,
},
},
},
],
},
],
});
});
it("#leftTabStop", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").leftTabStop(1200);
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "left", "w:pos": 1200 } } }],
},
],
});
});
it("#maxRightTabStop", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").rightTabStop(TabStopPosition.MAX);
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [
{
"w:tabs": [{ "w:tab": { _attr: { "w:val": "right", "w:pos": 9026 } } }],
},
],
});
});
it("#keepLines", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepLines();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:keepLines": EMPTY_OBJECT }],
});
});
it("#keepNext", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").keepNext();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:pPr": [{ "w:keepNext": EMPTY_OBJECT }],
});
});
});
describe("formatting methods: run properties", () => {
it("#size", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").size(24);
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:sz": { _attr: { "w:val": 24 } } }],
});
});
it("#smallCaps", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").smallCaps();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:smallCaps": { _attr: { "w:val": true } } }],
});
});
it("#allCaps", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").allCaps();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:caps": { _attr: { "w:val": true } } }],
});
});
it("#strike", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").strike();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:strike": { _attr: { "w:val": true } } }],
});
});
it("#doubleStrike", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").doubleStrike();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:dstrike": { _attr: { "w:val": true } } }],
});
});
it("#subScript", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").subScript();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "subscript" } } }],
});
});
it("#superScript", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").superScript();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:vertAlign": { _attr: { "w:val": "superscript" } } }],
});
});
it("#font", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").font("Times");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [
{ "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } },
],
});
});
it("#bold", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").bold();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:b": { _attr: { "w:val": true } } }],
});
});
it("#italics", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").italics();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:i": { _attr: { "w:val": true } } }],
});
});
it("#highlight", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").highlight("005599");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:highlight": { _attr: { "w:val": "005599" } } }],
});
});
it("#shadow", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").shadow("pct10", "00FFFF", "FF0000");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }],
});
});
describe("#underline", () => {
it("should set underline to 'single' if no arguments are given", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").underline();
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:u": { _attr: { "w:val": "single" } } }],
});
});
it("should set the style if given", () => {
const abstractNumbering = new AbstractNumbering(1);
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" } } }],
});
});
it("should set the style and color if given", () => {
const abstractNumbering = new AbstractNumbering(1);
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" } } }],
});
});
});
it("#color", () => {
const abstractNumbering = new AbstractNumbering(1);
const level = abstractNumbering.createLevel(0, "lowerRoman", "%0.").color("123456");
const tree = new Formatter().format(level);
expect(tree["w:lvl"]).to.include({
"w:rPr": [{ "w:color": { _attr: { "w:val": "123456" } } }],
});
});
});
});
});
describe("concrete numbering", () => {
describe("#overrideLevel", () => {
let numbering;
let abstractNumbering;
let concreteNumbering;
beforeEach(() => {
numbering = new Numbering();
abstractNumbering = numbering.createAbstractNumbering();
concreteNumbering = numbering.createConcreteNumbering(abstractNumbering);
});
it("sets a new override level for the given level number", () => {
concreteNumbering.overrideLevel(3);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{
_attr: {
"w:ilvl": 3,
},
},
{
"w:lvl": {
_attr: {
"w:ilvl": 3,
"w15:tentative": 1,
},
},
},
],
});
});
it("sets the startOverride element if start is given", () => {
concreteNumbering.overrideLevel(1, 9);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{
_attr: {
"w:ilvl": 1,
},
},
{
"w:startOverride": {
_attr: {
"w:val": 9,
},
},
},
{
"w:lvl": {
_attr: {
"w:ilvl": 1,
"w15:tentative": 1,
},
},
},
],
});
});
it("sets the lvl element if overrideLevel.Level is accessed", () => {
const ol = concreteNumbering.overrideLevel(1);
expect(ol.Level).to.be.instanceof(LevelForOverride);
const tree = new Formatter().format(concreteNumbering);
expect(tree["w:num"]).to.include({
"w:lvlOverride": [
{ _attr: { "w:ilvl": 1 } },
{
"w:lvl": { _attr: { "w15:tentative": 1, "w:ilvl": 1 } },
},
],
});
});
});
}); });

View File

@ -1,17 +1,27 @@
import { Indent } from "file/paragraph"; // http://officeopenxml.com/WPnumbering.php
import { AlignmentType } from "file/paragraph";
import { IXmlableObject, XmlComponent } from "file/xml-components"; import { IXmlableObject, XmlComponent } from "file/xml-components";
import { DocumentAttributes } from "../document/document-attributes"; import { DocumentAttributes } from "../document/document-attributes";
import { AbstractNumbering } from "./abstract-numbering"; import { AbstractNumbering } from "./abstract-numbering";
import { Num } from "./num"; import { ILevelsOptions } from "./level";
import { ConcreteNumbering } from "./num";
export interface INumberingOptions {
readonly config: Array<{
readonly levels: ILevelsOptions[];
readonly reference: string;
}>;
}
export class Numbering extends XmlComponent { export class Numbering extends XmlComponent {
// tslint:disable-next-line:readonly-keyword // tslint:disable-next-line:readonly-keyword
private nextId: number; private nextId: number;
private readonly abstractNumbering: XmlComponent[] = []; private readonly abstractNumbering: AbstractNumbering[] = [];
private readonly concreteNumbering: XmlComponent[] = []; private readonly concreteNumbering: ConcreteNumbering[] = [];
constructor() { constructor(options: INumberingOptions) {
super("w:numbering"); super("w:numbering");
this.root.push( this.root.push(
new DocumentAttributes({ new DocumentAttributes({
@ -37,39 +47,114 @@ export class Numbering extends XmlComponent {
this.nextId = 0; this.nextId = 0;
const abstractNumbering = this.createAbstractNumbering(); const abstractNumbering = this.createAbstractNumbering([
{
abstractNumbering.createLevel(0, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 720, hanging: 360 })); level: 0,
format: "bullet",
abstractNumbering.createLevel(1, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 1440, hanging: 360 })); text: "\u25CF",
alignment: AlignmentType.LEFT,
abstractNumbering.createLevel(2, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 2160, hanging: 360 })); style: {
paragraph: {
abstractNumbering.createLevel(3, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 2880, hanging: 360 })); indent: { left: 720, hanging: 360 },
},
abstractNumbering.createLevel(4, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 3600, hanging: 360 })); },
},
abstractNumbering.createLevel(5, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 4320, hanging: 360 })); {
level: 1,
abstractNumbering.createLevel(6, "bullet", "\u25CF", "left").addParagraphProperty(new Indent({ left: 5040, hanging: 360 })); format: "bullet",
text: "\u25CB",
abstractNumbering.createLevel(7, "bullet", "\u25CB", "left").addParagraphProperty(new Indent({ left: 5760, hanging: 360 })); alignment: AlignmentType.LEFT,
style: {
abstractNumbering.createLevel(8, "bullet", "\u25A0", "left").addParagraphProperty(new Indent({ left: 6480, hanging: 360 })); paragraph: {
indent: { left: 1440, hanging: 360 },
},
},
},
{
level: 2,
format: "bullet",
text: "\u25A0",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 2160, hanging: 360 },
},
},
},
{
level: 3,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 2880, hanging: 360 },
},
},
},
{
level: 4,
format: "bullet",
text: "\u25CB",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 3600, hanging: 360 },
},
},
},
{
level: 5,
format: "bullet",
text: "\u25A0",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 4320, hanging: 360 },
},
},
},
{
level: 6,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 5040, hanging: 360 },
},
},
},
{
level: 7,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 5760, hanging: 360 },
},
},
},
{
level: 8,
format: "bullet",
text: "\u25CF",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 6480, hanging: 360 },
},
},
},
]);
this.createConcreteNumbering(abstractNumbering); this.createConcreteNumbering(abstractNumbering);
}
public createAbstractNumbering(): AbstractNumbering { for (const con of options.config) {
const num = new AbstractNumbering(this.nextId++); const currentAbstractNumbering = this.createAbstractNumbering(con.levels);
this.abstractNumbering.push(num); this.createConcreteNumbering(currentAbstractNumbering, con.reference);
return num; }
}
public createConcreteNumbering(abstractNumbering: AbstractNumbering): Num {
const num = new Num(this.nextId++, abstractNumbering.id);
this.concreteNumbering.push(num);
return num;
} }
public prepForXml(): IXmlableObject | undefined { public prepForXml(): IXmlableObject | undefined {
@ -77,4 +162,20 @@ export class Numbering extends XmlComponent {
this.concreteNumbering.forEach((x) => this.root.push(x)); this.concreteNumbering.forEach((x) => this.root.push(x));
return super.prepForXml(); return super.prepForXml();
} }
private createConcreteNumbering(abstractNumbering: AbstractNumbering, reference?: string): ConcreteNumbering {
const num = new ConcreteNumbering(this.nextId++, abstractNumbering.id, reference);
this.concreteNumbering.push(num);
return num;
}
private createAbstractNumbering(options: ILevelsOptions[]): AbstractNumbering {
const num = new AbstractNumbering(this.nextId++, options);
this.abstractNumbering.push(num);
return num;
}
public get ConcreteNumbering(): ConcreteNumbering[] {
return this.concreteNumbering;
}
} }

View File

@ -1,7 +1,7 @@
import { Attributes, XmlComponent } from "file/xml-components"; import { Attributes, XmlComponent } from "file/xml-components";
export class NumberProperties extends XmlComponent { export class NumberProperties extends XmlComponent {
constructor(numberId: number, indentLevel: number) { constructor(numberId: number | string, indentLevel: number) {
super("w:numPr"); super("w:numPr");
this.root.push(new IndentLevel(indentLevel)); this.root.push(new IndentLevel(indentLevel));
this.root.push(new NumberId(numberId)); this.root.push(new NumberId(numberId));
@ -20,11 +20,11 @@ class IndentLevel extends XmlComponent {
} }
class NumberId extends XmlComponent { class NumberId extends XmlComponent {
constructor(id: number) { constructor(id: number | string) {
super("w:numId"); super("w:numId");
this.root.push( this.root.push(
new Attributes({ new Attributes({
val: id, val: typeof id === "string" ? `{${id}}` : id,
}), }),
); );
} }

View File

@ -3,7 +3,6 @@ import { assert, expect } from "chai";
import { Formatter } from "export/formatter"; import { Formatter } from "export/formatter";
import { EMPTY_OBJECT } from "file/xml-components"; import { EMPTY_OBJECT } from "file/xml-components";
import { Numbering } from "../numbering";
import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting";
import { Paragraph } from "./paragraph"; import { Paragraph } from "./paragraph";
@ -596,14 +595,9 @@ describe("Paragraph", () => {
describe("#setNumbering", () => { describe("#setNumbering", () => {
it("should add list paragraph style to JSON", () => { it("should add list paragraph style to JSON", () => {
const numbering = new Numbering();
const numberedAbstract = numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
const paragraph = new Paragraph({ const paragraph = new Paragraph({
numbering: { numbering: {
num: letterNumbering, reference: "test id",
level: 0, level: 0,
}, },
}); });
@ -622,14 +616,9 @@ describe("Paragraph", () => {
}); });
it("it should add numbered properties", () => { it("it should add numbered properties", () => {
const numbering = new Numbering();
const numberedAbstract = numbering.createAbstractNumbering();
numberedAbstract.createLevel(0, "lowerLetter", "%1)", "start");
const letterNumbering = numbering.createConcreteNumbering(numberedAbstract);
const paragraph = new Paragraph({ const paragraph = new Paragraph({
numbering: { numbering: {
num: letterNumbering, reference: "test id",
level: 0, level: 0,
}, },
}); });
@ -640,10 +629,7 @@ describe("Paragraph", () => {
"w:pPr": [ "w:pPr": [
{ "w:pStyle": { _attr: { "w:val": "ListParagraph" } } }, { "w:pStyle": { _attr: { "w:val": "ListParagraph" } } },
{ {
"w:numPr": [ "w:numPr": [{ "w:ilvl": { _attr: { "w:val": 0 } } }, { "w:numId": { _attr: { "w:val": "{test id}" } } }],
{ "w:ilvl": { _attr: { "w:val": 0 } } },
{ "w:numId": { _attr: { "w:val": letterNumbering.id } } },
],
}, },
], ],
}, },

View File

@ -1,6 +1,5 @@
// http://officeopenxml.com/WPparagraph.php // http://officeopenxml.com/WPparagraph.php
import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run"; import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run";
import { Num } from "file/numbering/num";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { Alignment, AlignmentType } from "./formatting/alignment"; import { Alignment, AlignmentType } from "./formatting/alignment";
@ -41,7 +40,7 @@ export interface IParagraphOptions {
readonly level: number; readonly level: number;
}; };
readonly numbering?: { readonly numbering?: {
readonly num: Num; readonly reference: string;
readonly level: number; readonly level: number;
readonly custom?: boolean; readonly custom?: boolean;
}; };
@ -141,7 +140,7 @@ export class Paragraph extends XmlComponent {
if (!options.numbering.custom) { if (!options.numbering.custom) {
this.properties.push(new Style("ListParagraph")); this.properties.push(new Style("ListParagraph"));
} }
this.properties.push(new NumberProperties(options.numbering.num.id, options.numbering.level)); this.properties.push(new NumberProperties(options.numbering.reference, options.numbering.level));
} }
if (options.children) { if (options.children) {

View File

@ -0,0 +1,39 @@
import { AlignmentType, IIndentAttributesProperties, ISpacingProperties, UnderlineType } from "../paragraph";
import { ShadingType } from "../table";
export interface IRunStyleOptions {
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;
};
}
export interface IParagraphStyleOptions2 {
readonly alignment?: AlignmentType;
readonly thematicBreak?: boolean;
readonly rightTabStop?: number;
readonly leftTabStop?: number;
readonly indent?: IIndentAttributesProperties;
readonly spacing?: ISpacingProperties;
readonly keepNext?: boolean;
readonly keepLines?: boolean;
readonly outlineLevel?: number;
}

View File

@ -1,21 +1,9 @@
import { import { Alignment, Indent, KeepLines, KeepNext, OutlineLevel, ParagraphProperties, Spacing, ThematicBreak } from "file/paragraph";
Alignment, import { TabStop, TabStopType } from "file/paragraph/formatting";
AlignmentType,
Indent,
ISpacingProperties,
KeepLines,
KeepNext,
OutlineLevel,
ParagraphProperties,
Spacing,
ThematicBreak,
} from "file/paragraph";
import { IIndentAttributesProperties, TabStop, TabStopType } from "file/paragraph/formatting";
import * as formatting from "file/paragraph/run/formatting"; import * as formatting from "file/paragraph/run/formatting";
import { RunProperties } from "file/paragraph/run/properties"; import { RunProperties } from "file/paragraph/run/properties";
import { UnderlineType } from "file/paragraph/run/underline";
import { ShadingType } from "file/table";
import { IParagraphStyleOptions2, IRunStyleOptions } from "../style-options";
import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; import { BasedOn, Link, Next, QuickFormat, SemiHidden, UiPriority, UnhideWhenUsed } from "./components";
import { Style } from "./style"; import { Style } from "./style";
@ -27,41 +15,8 @@ export interface IBaseParagraphStyleOptions {
readonly semiHidden?: boolean; readonly semiHidden?: boolean;
readonly uiPriority?: number; readonly uiPriority?: number;
readonly unhideWhenUsed?: boolean; readonly unhideWhenUsed?: boolean;
readonly run?: { readonly run?: IRunStyleOptions;
readonly size?: number; readonly paragraph?: IParagraphStyleOptions2;
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?: IIndentAttributesProperties;
readonly spacing?: ISpacingProperties;
readonly keepNext?: boolean;
readonly keepLines?: boolean;
readonly outlineLevel?: number;
};
} }
export interface IParagraphStyleOptions extends IBaseParagraphStyleOptions { export interface IParagraphStyleOptions extends IBaseParagraphStyleOptions {