From 120c3a7bbef10736ed37e5eb806e1975e52a50be Mon Sep 17 00:00:00 2001 From: wangfengming Date: Fri, 22 May 2020 12:22:45 +0800 Subject: [PATCH] :feat: support emphasis mark --- package-lock.json | 2 +- src/file/numbering/abstract-numbering.spec.ts | 55 +++++++- src/file/numbering/level.ts | 4 + src/file/paragraph/run/emphasis-mark.spec.ts | 29 ++++ src/file/paragraph/run/emphasis-mark.ts | 28 ++++ src/file/paragraph/run/formatting.ts | 2 + src/file/paragraph/run/index.ts | 1 + src/file/paragraph/run/run.spec.ts | 36 ++++- src/file/paragraph/run/run.ts | 8 ++ src/file/paragraph/run/symbol-run.spec.ts | 6 + src/file/styles/style-options.ts | 5 +- src/file/styles/style/character-style.spec.ts | 73 +++++++++- src/file/styles/style/character-style.ts | 8 ++ src/file/styles/style/paragraph-style.spec.ts | 125 ++++++++++++++++-- src/file/styles/style/paragraph-style.ts | 4 + 15 files changed, 371 insertions(+), 15 deletions(-) create mode 100644 src/file/paragraph/run/emphasis-mark.spec.ts create mode 100644 src/file/paragraph/run/emphasis-mark.ts diff --git a/package-lock.json b/package-lock.json index f0226f8dc5..f149d49335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "docx", - "version": "5.0.2", + "version": "5.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/file/numbering/abstract-numbering.spec.ts b/src/file/numbering/abstract-numbering.spec.ts index 59655aa40e..a2fb61b000 100644 --- a/src/file/numbering/abstract-numbering.spec.ts +++ b/src/file/numbering/abstract-numbering.spec.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; import { EMPTY_OBJECT } from "file/xml-components"; -import { AlignmentType, TabStopPosition } from "../paragraph"; +import { AlignmentType, EmphasisMarkType, TabStopPosition } from "../paragraph"; import { UnderlineType } from "../paragraph/run/underline"; import { ShadingType } from "../table"; import { AbstractNumbering } from "./abstract-numbering"; @@ -433,7 +433,16 @@ describe("AbstractNumbering", () => { 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" } } }, + { + "w:rFonts": { + _attr: { + "w:ascii": "Times", + "w:cs": "Times", + "w:eastAsia": "Times", + "w:hAnsi": "Times", + }, + }, + }, ], }); }); @@ -582,6 +591,48 @@ describe("AbstractNumbering", () => { }); }); + describe("#emphasisMark", () => { + it("should set emphasisMark to 'dot' if no arguments are given", () => { + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + emphasisMark: {}, + }, + }, + }, + ]); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }); + }); + + it("should set the style if given", () => { + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + emphasisMark: { + type: EmphasisMarkType.DOT, + }, + }, + }, + }, + ]); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }); + }); + }); + it("#color", () => { const abstractNumbering = new AbstractNumbering(1, [ { diff --git a/src/file/numbering/level.ts b/src/file/numbering/level.ts index cbef58dfc0..ddef2b2d78 100644 --- a/src/file/numbering/level.ts +++ b/src/file/numbering/level.ts @@ -177,6 +177,10 @@ export class LevelBase extends XmlComponent { this.runProperties.push(new formatting.Underline(style.run.underline.type, style.run.underline.color)); } + if (style.run.emphasisMark) { + this.runProperties.push(new formatting.EmphasisMark(style.run.emphasisMark.type)); + } + if (style.run.color) { this.runProperties.push(new formatting.Color(style.run.color)); } diff --git a/src/file/paragraph/run/emphasis-mark.spec.ts b/src/file/paragraph/run/emphasis-mark.spec.ts new file mode 100644 index 0000000000..4ad4878f64 --- /dev/null +++ b/src/file/paragraph/run/emphasis-mark.spec.ts @@ -0,0 +1,29 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; + +import * as em from "./emphasis-mark"; + +describe("EmphasisMark", () => { + describe("#constructor()", () => { + it("should create a new EmphasisMark object with w:em as the rootKey", () => { + const emphasisMark = new em.EmphasisMark(); + const tree = new Formatter().format(emphasisMark); + expect(tree).to.deep.equal({ + "w:em": { _attr: { "w:val": "dot" } }, + }); + }); + }); +}); + +describe("DotEmphasisMark", () => { + describe("#constructor()", () => { + it("should put value in attribute", () => { + const emphasisMark = new em.DotEmphasisMark(); + const tree = new Formatter().format(emphasisMark); + expect(tree).to.deep.equal({ + "w:em": { _attr: { "w:val": "dot" } }, + }); + }); + }); +}); diff --git a/src/file/paragraph/run/emphasis-mark.ts b/src/file/paragraph/run/emphasis-mark.ts new file mode 100644 index 0000000000..b8af756072 --- /dev/null +++ b/src/file/paragraph/run/emphasis-mark.ts @@ -0,0 +1,28 @@ +import { Attributes, XmlComponent } from "file/xml-components"; + +export enum EmphasisMarkType { + DOT = "dot", +} + +export abstract class BaseEmphasisMark extends XmlComponent { + protected constructor(emphasisMarkType: EmphasisMarkType) { + super("w:em"); + this.root.push( + new Attributes({ + val: emphasisMarkType, + }), + ); + } +} + +export class EmphasisMark extends BaseEmphasisMark { + constructor(emphasisMarkType: EmphasisMarkType = EmphasisMarkType.DOT) { + super(emphasisMarkType); + } +} + +export class DotEmphasisMark extends BaseEmphasisMark { + constructor() { + super(EmphasisMarkType.DOT); + } +} diff --git a/src/file/paragraph/run/formatting.ts b/src/file/paragraph/run/formatting.ts index 549f2ae552..3055ba44e7 100644 --- a/src/file/paragraph/run/formatting.ts +++ b/src/file/paragraph/run/formatting.ts @@ -1,5 +1,7 @@ import { Attributes, XmlComponent } from "file/xml-components"; + export { Underline } from "./underline"; +export { EmphasisMark } from "./emphasis-mark"; export { SubScript, SuperScript } from "./script"; export { RunFonts } from "./run-fonts"; diff --git a/src/file/paragraph/run/index.ts b/src/file/paragraph/run/index.ts index 21df415f35..745e33ea5a 100644 --- a/src/file/paragraph/run/index.ts +++ b/src/file/paragraph/run/index.ts @@ -5,4 +5,5 @@ export * from "./picture-run"; export * from "./run-fonts"; export * from "./sequential-identifier"; export * from "./underline"; +export * from "./emphasis-mark"; export * from "./tab"; diff --git a/src/file/paragraph/run/run.spec.ts b/src/file/paragraph/run/run.spec.ts index b5febf199e..8712622921 100644 --- a/src/file/paragraph/run/run.spec.ts +++ b/src/file/paragraph/run/run.spec.ts @@ -5,6 +5,7 @@ import { Formatter } from "export/formatter"; import { ShadingType } from "file/table"; import { Run } from "./"; +import { EmphasisMarkType } from "./emphasis-mark"; import { PageNumber } from "./run"; import { UnderlineType } from "./underline"; @@ -84,6 +85,30 @@ describe("Run", () => { }); }); + describe("#emphasisMark()", () => { + it("should default to 'dot'", () => { + const run = new Run({ + emphasisMark: {}, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [{ "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }] }], + }); + }); + + it("should set the style type if given", () => { + const run = new Run({ + emphasisMark: { + type: EmphasisMarkType.DOT, + }, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [{ "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }] }], + }); + }); + }); + describe("#smallCaps()", () => { it("it should add smallCaps to the properties", () => { const run = new Run({ @@ -235,7 +260,16 @@ describe("Run", () => { "w:r": [ { "w:rPr": [ - { "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } }, + { + "w:rFonts": { + _attr: { + "w:ascii": "Times", + "w:cs": "Times", + "w:eastAsia": "Times", + "w:hAnsi": "Times", + }, + }, + }, ], }, ], diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts index 20777b72a2..8225ebdc95 100644 --- a/src/file/paragraph/run/run.ts +++ b/src/file/paragraph/run/run.ts @@ -6,6 +6,7 @@ import { FootnoteReferenceRun } from "file/footnotes/footnote/run/reference-run" import { FieldInstruction } from "file/table-of-contents/field-instruction"; import { Break } from "./break"; import { Caps, SmallCaps } from "./caps"; +import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark"; import { Begin, End, Separate } from "./field"; import { Bold, @@ -38,6 +39,9 @@ export interface IRunOptions { readonly color?: string; readonly type?: UnderlineType; }; + readonly emphasisMark?: { + readonly type?: EmphasisMarkType; + }; readonly color?: string; readonly size?: number; readonly rightToLeft?: boolean; @@ -90,6 +94,10 @@ export class Run extends XmlComponent { this.properties.push(new Underline(options.underline.type, options.underline.color)); } + if (options.emphasisMark) { + this.properties.push(new EmphasisMark(options.emphasisMark.type)); + } + if (options.color) { this.properties.push(new Color(options.color)); } diff --git a/src/file/paragraph/run/symbol-run.spec.ts b/src/file/paragraph/run/symbol-run.spec.ts index f3faee8bb0..c41e6e1d51 100644 --- a/src/file/paragraph/run/symbol-run.spec.ts +++ b/src/file/paragraph/run/symbol-run.spec.ts @@ -1,5 +1,7 @@ import { expect } from "chai"; +import { EmphasisMarkType } from "./emphasis-mark"; + import { Formatter } from "export/formatter"; import { UnderlineType } from "./underline"; @@ -44,6 +46,9 @@ describe("SymbolRun", () => { color: "red", type: UnderlineType.DOUBLE, }, + emphasisMark: { + type: EmphasisMarkType.DOT, + }, color: "green", size: 40, highlight: "yellow", @@ -59,6 +64,7 @@ describe("SymbolRun", () => { { "w:i": { _attr: { "w:val": true } } }, { "w:iCs": { _attr: { "w:val": true } } }, { "w:u": { _attr: { "w:val": "double", "w:color": "red" } } }, + { "w:em": { _attr: { "w:val": "dot" } } }, { "w:color": { _attr: { "w:val": "green" } } }, { "w:sz": { _attr: { "w:val": 40 } } }, { "w:szCs": { _attr: { "w:val": 40 } } }, diff --git a/src/file/styles/style-options.ts b/src/file/styles/style-options.ts index 18a9e9aab9..b748c42e79 100644 --- a/src/file/styles/style-options.ts +++ b/src/file/styles/style-options.ts @@ -1,4 +1,4 @@ -import { AlignmentType, IIndentAttributesProperties, ISpacingProperties, UnderlineType } from "../paragraph"; +import { AlignmentType, EmphasisMarkType, IIndentAttributesProperties, ISpacingProperties, UnderlineType } from "../paragraph"; import { ShadingType } from "../table"; export interface IRunStyleOptions { @@ -15,6 +15,9 @@ export interface IRunStyleOptions { readonly type?: UnderlineType; readonly color?: string; }; + readonly emphasisMark?: { + readonly type?: EmphasisMarkType; + }; readonly color?: string; readonly font?: string; readonly characterSpacing?: number; diff --git a/src/file/styles/style/character-style.spec.ts b/src/file/styles/style/character-style.spec.ts index fe269bfceb..c660b152be 100644 --- a/src/file/styles/style/character-style.spec.ts +++ b/src/file/styles/style/character-style.spec.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { EmphasisMarkType } from "file/paragraph/run/emphasis-mark"; import { UnderlineType } from "file/paragraph/run/underline"; import { ShadingType } from "file/table"; import { EMPTY_OBJECT } from "file/xml-components"; @@ -412,6 +413,66 @@ describe("CharacterStyle", () => { }); }); + describe("#emphasisMark", () => { + it("should set emphasisMark to 'dot' if no arguments are given", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + emphasisMark: {}, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + + it("should set the style if given", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + emphasisMark: { + type: EmphasisMarkType.DOT, + }, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + }); + it("#superScript", () => { const style = new CharacterStyle({ id: "myStyleId", @@ -616,7 +677,17 @@ describe("CharacterStyle", () => { "w:style": [ { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, { - "w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], + "w:rPr": [ + { + "w:shd": { + _attr: { + "w:val": "pct10", + "w:fill": "00FFFF", + "w:color": "FF0000", + }, + }, + }, + ], }, { "w:uiPriority": { diff --git a/src/file/styles/style/character-style.ts b/src/file/styles/style/character-style.ts index 32ff4b6b69..f92540dc16 100644 --- a/src/file/styles/style/character-style.ts +++ b/src/file/styles/style/character-style.ts @@ -1,3 +1,4 @@ +import { EmphasisMarkType } from "file/paragraph/run/emphasis-mark"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; import { UnderlineType } from "file/paragraph/run/underline"; @@ -23,6 +24,9 @@ export interface IBaseCharacterStyleOptions { readonly type?: UnderlineType; readonly color?: string; }; + readonly emphasisMark?: { + readonly type?: EmphasisMarkType; + }; readonly color?: string; readonly font?: string; readonly characterSpacing?: number; @@ -104,6 +108,10 @@ export class CharacterStyle extends Style { this.runProperties.push(new formatting.Underline(options.run.underline.type, options.run.underline.color)); } + if (options.run.emphasisMark) { + this.runProperties.push(new formatting.EmphasisMark(options.run.emphasisMark.type)); + } + if (options.run.color) { this.runProperties.push(new formatting.Color(options.run.color)); } diff --git a/src/file/styles/style/paragraph-style.spec.ts b/src/file/styles/style/paragraph-style.spec.ts index 8d9b4da15a..3331dc3513 100644 --- a/src/file/styles/style/paragraph-style.spec.ts +++ b/src/file/styles/style/paragraph-style.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; -import { AlignmentType, TabStopPosition } from "file/paragraph"; +import { AlignmentType, EmphasisMarkType, TabStopPosition } from "file/paragraph"; import { UnderlineType } from "file/paragraph/run/underline"; import { ShadingType } from "file/table"; import { EMPTY_OBJECT } from "file/xml-components"; @@ -49,7 +49,15 @@ describe("ParagraphStyle", () => { const style = new ParagraphStyle({ id: "myStyleId", quickFormat: true }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:qFormat": EMPTY_OBJECT }], + "w:style": [ + { + _attr: { + "w:type": "paragraph", + "w:styleId": "myStyleId", + }, + }, + { "w:qFormat": EMPTY_OBJECT }, + ], }); }); @@ -299,7 +307,15 @@ describe("ParagraphStyle", () => { }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }] }], + "w:style": [ + { + _attr: { + "w:type": "paragraph", + "w:styleId": "myStyleId", + }, + }, + { "w:pPr": [{ "w:keepLines": EMPTY_OBJECT }] }, + ], }); }); @@ -312,7 +328,15 @@ describe("ParagraphStyle", () => { }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }] }], + "w:style": [ + { + _attr: { + "w:type": "paragraph", + "w:styleId": "myStyleId", + }, + }, + { "w:pPr": [{ "w:keepNext": EMPTY_OBJECT }] }, + ], }); }); @@ -473,7 +497,16 @@ describe("ParagraphStyle", () => { { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:rPr": [ - { "w:rFonts": { _attr: { "w:ascii": "Times", "w:cs": "Times", "w:eastAsia": "Times", "w:hAnsi": "Times" } } }, + { + "w:rFonts": { + _attr: { + "w:ascii": "Times", + "w:cs": "Times", + "w:eastAsia": "Times", + "w:hAnsi": "Times", + }, + }, + }, ], }, ], @@ -550,7 +583,17 @@ describe("ParagraphStyle", () => { "w:style": [ { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { - "w:rPr": [{ "w:shd": { _attr: { "w:val": "pct10", "w:fill": "00FFFF", "w:color": "FF0000" } } }], + "w:rPr": [ + { + "w:shd": { + _attr: { + "w:val": "pct10", + "w:fill": "00FFFF", + "w:color": "FF0000", + }, + }, + }, + ], }, ], }); @@ -617,6 +660,46 @@ describe("ParagraphStyle", () => { }); }); + describe("#emphasisMark", () => { + it("should set emphasisMark to 'dot' if no arguments are given", () => { + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + emphasisMark: {}, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }, + ], + }); + }); + + it("should set the style if given", () => { + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + emphasisMark: { + type: EmphasisMarkType.DOT, + }, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:rPr": [{ "w:em": { _attr: { "w:val": "dot" } } }], + }, + ], + }); + }); + }); + it("#color", () => { const style = new ParagraphStyle({ id: "myStyleId", @@ -639,7 +722,15 @@ describe("ParagraphStyle", () => { const style = new ParagraphStyle({ id: "myStyleId", link: "MyLink" }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:link": { _attr: { "w:val": "MyLink" } } }], + "w:style": [ + { + _attr: { + "w:type": "paragraph", + "w:styleId": "myStyleId", + }, + }, + { "w:link": { _attr: { "w:val": "MyLink" } } }, + ], }); }); @@ -647,7 +738,15 @@ describe("ParagraphStyle", () => { const style = new ParagraphStyle({ id: "myStyleId", semiHidden: true }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:semiHidden": EMPTY_OBJECT }], + "w:style": [ + { + _attr: { + "w:type": "paragraph", + "w:styleId": "myStyleId", + }, + }, + { "w:semiHidden": EMPTY_OBJECT }, + ], }); }); @@ -672,7 +771,15 @@ describe("ParagraphStyle", () => { const style = new ParagraphStyle({ id: "myStyleId", unhideWhenUsed: true }); const tree = new Formatter().format(style); expect(tree).to.deep.equal({ - "w:style": [{ _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, { "w:unhideWhenUsed": EMPTY_OBJECT }], + "w:style": [ + { + _attr: { + "w:type": "paragraph", + "w:styleId": "myStyleId", + }, + }, + { "w:unhideWhenUsed": EMPTY_OBJECT }, + ], }); }); }); diff --git a/src/file/styles/style/paragraph-style.ts b/src/file/styles/style/paragraph-style.ts index 3adb1edbbe..0a53c9251a 100644 --- a/src/file/styles/style/paragraph-style.ts +++ b/src/file/styles/style/paragraph-style.ts @@ -114,6 +114,10 @@ export class ParagraphStyle extends Style { this.runProperties.push(new formatting.Underline(options.run.underline.type, options.run.underline.color)); } + if (options.run.emphasisMark) { + this.runProperties.push(new formatting.EmphasisMark(options.run.emphasisMark.type)); + } + if (options.run.color) { this.runProperties.push(new formatting.Color(options.run.color)); }