diff --git a/demo/53-chinese.ts b/demo/53-chinese.ts new file mode 100644 index 0000000000..eee6eb9032 --- /dev/null +++ b/demo/53-chinese.ts @@ -0,0 +1,55 @@ +// Chinese text - Chinese text need to use a Chinese font. And ascii text need to use a ascii font. +// Different from the `52-japanese.ts`. +// `52-japanese.ts` will set all characters to use Japanese font. +// `53-chinese.ts` will set Chinese characters to use Chinese font, and set ascii characters to use ascii font. + +// Note that if the OS have not install `KaiTi` font, this demo doesn't work. + +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, HeadingLevel, Packer, Paragraph, TextRun } from "../build"; + +const doc = new Document({ + styles: { + paragraphStyles: [ + { + id: "Normal", + name: "Normal", + basedOn: "Normal", + next: "Normal", + quickFormat: true, + run: { + font: { + ascii: "Times", + eastAsia: "KaiTi", + }, + }, + }, + ], + }, +}); + +doc.addSection({ + children: [ + new Paragraph({ + text: "中文和英文 Chinese and English", + heading: HeadingLevel.HEADING_1, + }), + new Paragraph({ + text: "中文和英文 Chinese and English", + }), + new Paragraph({ + children: [ + new TextRun({ + text: "中文和英文 Chinese and English", + font: { eastAsia: "SimSun" }, // set eastAsia to "SimSun". + // The ascii characters will use the default font ("Times") specified in paragraphStyles + }), + ], + }), + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/docs/usage/styling-with-js.md b/docs/usage/styling-with-js.md index c8bbb5c8db..0dafac2904 100644 --- a/docs/usage/styling-with-js.md +++ b/docs/usage/styling-with-js.md @@ -26,7 +26,7 @@ const name = new TextRun({ - `emphasisMark({type="dot"})`: Set the emphasis mark style - `color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`) - `size(halfPts)`: Set the font size, measured in half-points -- `font(name)`: Set the run's font +- `font(name)` or `font({ascii, cs, eastAsia, hAnsi, hint})`: Set the run's font - `style(name)`: Apply a named run style - `characterSpacing(value)`: Set the character spacing adjustment (in TWIPs) diff --git a/package.json b/package.json index 900a6f6357..09b272cb5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docx", - "version": "5.1.1", + "version": "5.2.0", "description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.", "main": "build/index.js", "scripts": { diff --git a/src/file/numbering/abstract-numbering.spec.ts b/src/file/numbering/abstract-numbering.spec.ts index a2fb61b000..6548a4436d 100644 --- a/src/file/numbering/abstract-numbering.spec.ts +++ b/src/file/numbering/abstract-numbering.spec.ts @@ -417,7 +417,7 @@ describe("AbstractNumbering", () => { }); }); - it("#font", () => { + it("#font by name", () => { const abstractNumbering = new AbstractNumbering(1, [ { level: 0, @@ -447,6 +447,37 @@ describe("AbstractNumbering", () => { }); }); + it("#font for ascii and eastAsia", () => { + const abstractNumbering = new AbstractNumbering(1, [ + { + level: 0, + format: "lowerRoman", + text: "%0.", + style: { + run: { + font: { + ascii: "Times", + eastAsia: "KaiTi", + }, + }, + }, + }, + ]); + const tree = new Formatter().format(abstractNumbering); + expect(tree["w:abstractNum"][2]["w:lvl"]).to.include({ + "w:rPr": [ + { + "w:rFonts": { + _attr: { + "w:ascii": "Times", + "w:eastAsia": "KaiTi", + }, + }, + }, + ], + }); + }); + it("#bold", () => { const abstractNumbering = new AbstractNumbering(1, [ { diff --git a/src/file/paragraph/run/formatting.ts b/src/file/paragraph/run/formatting.ts index 3055ba44e7..51b14be5cf 100644 --- a/src/file/paragraph/run/formatting.ts +++ b/src/file/paragraph/run/formatting.ts @@ -3,7 +3,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"; +export { RunFonts, IFontAttributesProperties } from "./run-fonts"; export class Bold extends XmlComponent { constructor() { diff --git a/src/file/paragraph/run/run-fonts.spec.ts b/src/file/paragraph/run/run-fonts.spec.ts index 1eacc8ecc9..a0180eae81 100644 --- a/src/file/paragraph/run/run-fonts.spec.ts +++ b/src/file/paragraph/run/run-fonts.spec.ts @@ -21,5 +21,12 @@ describe("RunFonts", () => { }, }); }); + + it("uses the font attrs for ascii and eastAsia", () => { + const tree = new Formatter().format(new RunFonts({ ascii: "Times", eastAsia: "KaiTi" })); + expect(tree).to.deep.equal({ + "w:rFonts": { _attr: { "w:ascii": "Times", "w:eastAsia": "KaiTi" } }, + }); + }); }); }); diff --git a/src/file/paragraph/run/run-fonts.ts b/src/file/paragraph/run/run-fonts.ts index 10a441626c..c1eed32613 100644 --- a/src/file/paragraph/run/run-fonts.ts +++ b/src/file/paragraph/run/run-fonts.ts @@ -1,14 +1,14 @@ import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; -interface IRunFontAttributesProperties { - readonly ascii: string; - readonly cs: string; - readonly eastAsia: string; - readonly hAnsi: string; +export interface IFontAttributesProperties { + readonly ascii?: string; + readonly cs?: string; + readonly eastAsia?: string; + readonly hAnsi?: string; readonly hint?: string; } -class RunFontAttributes extends XmlAttributeComponent { +class RunFontAttributes extends XmlAttributeComponent { protected readonly xmlKeys = { ascii: "w:ascii", cs: "w:cs", @@ -19,16 +19,26 @@ class RunFontAttributes extends XmlAttributeComponent { ], }); }); + + it("should set the font for ascii and eastAsia", () => { + const run = new Run({ + font: { + ascii: "Times", + eastAsia: "KaiTi", + }, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + { + "w:rPr": [ + { + "w:rFonts": { + _attr: { + "w:ascii": "Times", + "w:eastAsia": "KaiTi", + }, + }, + }, + ], + }, + ], + }); + }); }); describe("#color", () => { diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts index 8225ebdc95..5a26f3352c 100644 --- a/src/file/paragraph/run/run.ts +++ b/src/file/paragraph/run/run.ts @@ -27,11 +27,16 @@ import { import { NumberOfPages, NumberOfPagesSection, Page } from "./page-number"; import { RunProperties } from "./properties"; import { Text } from "./run-components/text"; -import { RunFonts } from "./run-fonts"; +import { IFontAttributesProperties, RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; import { Style } from "./style"; import { Underline, UnderlineType } from "./underline"; +interface IFontOptions { + readonly name: string; + readonly hint?: string; +} + export interface IRunOptions { readonly bold?: true; readonly italics?: true; @@ -52,10 +57,7 @@ export interface IRunOptions { readonly subScript?: boolean; readonly superScript?: boolean; readonly style?: string; - readonly font?: { - readonly name: string; - readonly hint?: string; - }; + readonly font?: IFontOptions | IFontAttributesProperties; readonly highlight?: string; readonly shading?: { readonly type: ShadingType; @@ -140,7 +142,11 @@ export class Run extends XmlComponent { } if (options.font) { - this.properties.push(new RunFonts(options.font.name, options.font.hint)); + if ("name" in options.font) { + this.properties.push(new RunFonts(options.font.name, options.font.hint)); + } else { + this.properties.push(new RunFonts(options.font)); + } } if (options.highlight) { diff --git a/src/file/styles/defaults/run-properties.ts b/src/file/styles/defaults/run-properties.ts index 1ab8f5752c..2228f84de6 100644 --- a/src/file/styles/defaults/run-properties.ts +++ b/src/file/styles/defaults/run-properties.ts @@ -1,6 +1,6 @@ import { Size, SizeComplexScript } from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; -import { RunFonts } from "file/paragraph/run/run-fonts"; +import { IFontAttributesProperties, RunFonts } from "file/paragraph/run/run-fonts"; import { XmlComponent } from "file/xml-components"; export class RunPropertiesDefaults extends XmlComponent { @@ -18,8 +18,8 @@ export class RunPropertiesDefaults extends XmlComponent { return this; } - public font(fontName: string): RunPropertiesDefaults { - this.properties.push(new RunFonts(fontName)); + public font(font: string | IFontAttributesProperties): RunPropertiesDefaults { + this.properties.push(new RunFonts(font)); return this; } } diff --git a/src/file/styles/style-options.ts b/src/file/styles/style-options.ts index b748c42e79..8ce1e6460b 100644 --- a/src/file/styles/style-options.ts +++ b/src/file/styles/style-options.ts @@ -1,4 +1,11 @@ -import { AlignmentType, EmphasisMarkType, IIndentAttributesProperties, ISpacingProperties, UnderlineType } from "../paragraph"; +import { + AlignmentType, + EmphasisMarkType, + IFontAttributesProperties, + IIndentAttributesProperties, + ISpacingProperties, + UnderlineType, +} from "../paragraph"; import { ShadingType } from "../table"; export interface IRunStyleOptions { @@ -19,7 +26,7 @@ export interface IRunStyleOptions { readonly type?: EmphasisMarkType; }; readonly color?: string; - readonly font?: string; + readonly font?: string | IFontAttributesProperties; readonly characterSpacing?: number; readonly highlight?: string; readonly shadow?: { diff --git a/src/file/styles/style/character-style.spec.ts b/src/file/styles/style/character-style.spec.ts index c660b152be..64c866a8c8 100644 --- a/src/file/styles/style/character-style.spec.ts +++ b/src/file/styles/style/character-style.spec.ts @@ -202,7 +202,7 @@ describe("CharacterStyle", () => { }); }); - it("should add font", () => { + it("should add font by name", () => { const style = new CharacterStyle({ id: "myStyleId", run: { @@ -241,6 +241,46 @@ describe("CharacterStyle", () => { }); }); + it("should add font for ascii and eastAsia", () => { + const style = new CharacterStyle({ + id: "myStyleId", + run: { + font: { + ascii: "test font ascii", + eastAsia: "test font eastAsia", + }, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "character", "w:styleId": "myStyleId" } }, + { + "w:rPr": [ + { + "w:rFonts": { + _attr: { + "w:ascii": "test font ascii", + "w:eastAsia": "test font eastAsia", + }, + }, + }, + ], + }, + { + "w:uiPriority": { + _attr: { + "w:val": 99, + }, + }, + }, + { + "w:unhideWhenUsed": EMPTY_OBJECT, + }, + ], + }); + }); + it("should add character spacing", () => { const style = new CharacterStyle({ id: "myStyleId", diff --git a/src/file/styles/style/character-style.ts b/src/file/styles/style/character-style.ts index f92540dc16..f0d6c24639 100644 --- a/src/file/styles/style/character-style.ts +++ b/src/file/styles/style/character-style.ts @@ -1,6 +1,7 @@ import { EmphasisMarkType } from "file/paragraph/run/emphasis-mark"; import * as formatting from "file/paragraph/run/formatting"; import { RunProperties } from "file/paragraph/run/properties"; +import { IFontAttributesProperties } from "file/paragraph/run/run-fonts"; import { UnderlineType } from "file/paragraph/run/underline"; import { BasedOn, Link, SemiHidden, UiPriority, UnhideWhenUsed } from "./components"; @@ -28,7 +29,7 @@ export interface IBaseCharacterStyleOptions { readonly type?: EmphasisMarkType; }; readonly color?: string; - readonly font?: string; + readonly font?: string | IFontAttributesProperties; readonly characterSpacing?: number; readonly highlight?: string; readonly shadow?: { diff --git a/src/file/styles/style/paragraph-style.spec.ts b/src/file/styles/style/paragraph-style.spec.ts index 3331dc3513..7d01ef87e7 100644 --- a/src/file/styles/style/paragraph-style.spec.ts +++ b/src/file/styles/style/paragraph-style.spec.ts @@ -484,7 +484,7 @@ describe("ParagraphStyle", () => { }); }); - it("#font", () => { + it("#font by name", () => { const style = new ParagraphStyle({ id: "myStyleId", run: { @@ -513,6 +513,36 @@ describe("ParagraphStyle", () => { }); }); + it("#font for ascii and eastAsia", () => { + const style = new ParagraphStyle({ + id: "myStyleId", + run: { + font: { + ascii: "Times", + eastAsia: "KaiTi", + }, + }, + }); + const tree = new Formatter().format(style); + expect(tree).to.deep.equal({ + "w:style": [ + { _attr: { "w:type": "paragraph", "w:styleId": "myStyleId" } }, + { + "w:rPr": [ + { + "w:rFonts": { + _attr: { + "w:ascii": "Times", + "w:eastAsia": "KaiTi", + }, + }, + }, + ], + }, + ], + }); + }); + it("#bold", () => { const style = new ParagraphStyle({ id: "myStyleId",