Feat/embedded fonts (#2174)

* #239 Embedded fonts

* Add boilerplate for font table

* Fix linting

* Fix linting

* Fix odttf naming

* Correct writing of fonts to relationships and font table

new uuid function

* Add font to run

* Add demo

Fix tests

* Add character set support

* Add tests

* Write tests
This commit is contained in:
Dolan
2023-12-30 02:23:54 +00:00
committed by GitHub
parent fa401297da
commit 010ef05ce3
28 changed files with 794 additions and 30 deletions

View File

@ -29,7 +29,16 @@ describe("ContentTypes", () => {
Default: { _attr: { ContentType: "application/vnd.openxmlformats-package.relationships+xml", Extension: "rels" } },
});
expect(tree["Types"][7]).to.deep.equal({ Default: { _attr: { ContentType: "application/xml", Extension: "xml" } } });
expect(tree["Types"][8]).to.deep.equal({
Default: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.obfuscatedFont",
Extension: "odttf",
},
},
});
expect(tree["Types"][9]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
@ -37,7 +46,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][9]).to.deep.equal({
expect(tree["Types"][10]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
@ -45,7 +54,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][10]).to.deep.equal({
expect(tree["Types"][11]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-package.core-properties+xml",
@ -53,7 +62,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][11]).to.deep.equal({
expect(tree["Types"][12]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.custom-properties+xml",
@ -61,7 +70,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][12]).to.deep.equal({
expect(tree["Types"][13]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.extended-properties+xml",
@ -69,7 +78,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][13]).to.deep.equal({
expect(tree["Types"][14]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
@ -77,7 +86,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][14]).to.deep.equal({
expect(tree["Types"][15]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
@ -85,7 +94,7 @@ describe("ContentTypes", () => {
},
},
});
expect(tree["Types"][15]).to.deep.equal({
expect(tree["Types"][16]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
@ -102,7 +111,7 @@ describe("ContentTypes", () => {
contentTypes.addFooter(102);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][17]).to.deep.equal({
expect(tree["Types"][19]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
@ -111,7 +120,7 @@ describe("ContentTypes", () => {
},
});
expect(tree["Types"][18]).to.deep.equal({
expect(tree["Types"][20]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
@ -128,7 +137,7 @@ describe("ContentTypes", () => {
contentTypes.addHeader(202);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][17]).to.deep.equal({
expect(tree["Types"][19]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
@ -137,7 +146,7 @@ describe("ContentTypes", () => {
},
});
expect(tree["Types"][18]).to.deep.equal({
expect(tree["Types"][20]).to.deep.equal({
Override: {
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",

View File

@ -20,6 +20,7 @@ export class ContentTypes extends XmlComponent {
this.root.push(new Default("image/gif", "gif"));
this.root.push(new Default("application/vnd.openxmlformats-package.relationships+xml", "rels"));
this.root.push(new Default("application/xml", "xml"));
this.root.push(new Default("application/vnd.openxmlformats-officedocument.obfuscatedFont", "odttf"));
this.root.push(
new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", "/word/document.xml"),
@ -33,6 +34,7 @@ export class ContentTypes extends XmlComponent {
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml", "/word/footnotes.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml", "/word/settings.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml", "/word/comments.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml", "/word/fontTable.xml"));
}
public addFooter(index: number): void {

View File

@ -1,5 +1,6 @@
import { ICommentsOptions } from "@file/paragraph/run/comment-run";
import { ICompatibilityOptions } from "@file/settings/compatibility";
import { FontOptions } from "@file/fonts/font-table";
import { StringContainer, XmlComponent } from "@file/xml-components";
import { dateTimeValue } from "@util/values";
@ -40,6 +41,7 @@ export interface IPropertiesOptions {
readonly customProperties?: readonly ICustomPropertyOptions[];
readonly evenAndOddHeaderAndFooters?: boolean;
readonly defaultTabStop?: number;
readonly fonts?: readonly FontOptions[];
}
// <xs:element name="coreProperties" type="CT_CoreProperties"/>

View File

@ -1,3 +1,4 @@
import { XmlComponent } from "./xml-components";
import { Document, IDocumentOptions } from "./document";
import { Footer } from "./footer/footer";
import { FootNotes } from "./footnotes";
@ -5,7 +6,7 @@ import { Header } from "./header/header";
import { Relationships } from "./relationships";
export interface IViewWrapper {
readonly View: Document | Footer | Header | FootNotes;
readonly View: Document | Footer | Header | FootNotes | XmlComponent;
readonly Relationships: Relationships;
}

View File

@ -17,6 +17,7 @@ import { Styles } from "./styles";
import { ExternalStylesFactory } from "./styles/external-styles-factory";
import { DefaultStylesFactory } from "./styles/factory";
import { FileChild } from "./file-child";
import { FontWrapper } from "./fonts/font-wrapper";
export interface ISectionOptions {
readonly headers?: {
@ -53,6 +54,7 @@ export class File {
private readonly appProperties: AppProperties;
private readonly styles: Styles;
private readonly comments: Comments;
private readonly fontWrapper: FontWrapper;
public constructor(options: IPropertiesOptions) {
this.coreProperties = new CoreProperties({
@ -109,6 +111,8 @@ export class File {
this.footnotesWrapper.View.createFootNote(parseFloat(key), options.footnotes[key].children);
}
}
this.fontWrapper = new FontWrapper(options.fonts ?? []);
}
private addSection({ headers = {}, footers = {}, children, properties }: ISectionOptions): void {
@ -292,4 +296,8 @@ export class File {
public get Comments(): Comments {
return this.comments;
}
public get FontTable(): FontWrapper {
return this.fontWrapper;
}
}

View File

@ -0,0 +1,33 @@
import { XmlComponent } from "@file/xml-components";
import { CharacterSet, createFont } from "./font";
export const createRegularFont = ({
name,
index,
fontKey,
characterSet,
}: {
readonly name: string;
readonly index: number;
readonly fontKey: string;
readonly characterSet?: (typeof CharacterSet)[keyof typeof CharacterSet];
}): XmlComponent =>
createFont({
name,
sig: {
usb0: "E0002AFF",
usb1: "C000247B",
usb2: "00000009",
usb3: "00000000",
csb0: "000001FF",
csb1: "00000000",
},
charset: characterSet,
family: "auto",
pitch: "variable",
embedRegular: {
fontKey,
id: `rId${index}`,
},
});

View File

@ -0,0 +1,44 @@
import { BuilderElement, XmlComponent } from "@file/xml-components";
import { createRegularFont } from "./create-regular-font";
import { FontOptionsWithKey } from "./font-wrapper";
import { CharacterSet } from "./font";
// <xsd:complexType name="CT_FontsList">
// <xsd:sequence>
// <xsd:element name="font" type="CT_Font" minOccurs="0" maxOccurs="unbounded"/>
// </xsd:sequence>
// </xsd:complexType>
export type FontOptions = {
readonly name: string;
readonly data: Buffer;
readonly characterSet?: (typeof CharacterSet)[keyof typeof CharacterSet];
};
export const createFontTable = (fonts: readonly FontOptionsWithKey[]): XmlComponent =>
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_Font_topic_ID0ERNCU.html
// http://www.datypic.com/sc/ooxml/e-w_fonts.html
new BuilderElement({
name: "w:fonts",
attributes: {
mc: { key: "xmlns:mc", value: "http://schemas.openxmlformats.org/markup-compatibility/2006" },
r: { key: "xmlns:r", value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships" },
w: { key: "xmlns:w", value: "http://schemas.openxmlformats.org/wordprocessingml/2006/main" },
w14: { key: "xmlns:w14", value: "http://schemas.microsoft.com/office/word/2010/wordml" },
w15: { key: "xmlns:w15", value: "http://schemas.microsoft.com/office/word/2012/wordml" },
w16cex: { key: "xmlns:w16cex", value: "http://schemas.microsoft.com/office/word/2018/wordml/cex" },
w16cid: { key: "xmlns:w16cid", value: "http://schemas.microsoft.com/office/word/2016/wordml/cid" },
w16: { key: "xmlns:w16", value: "http://schemas.microsoft.com/office/word/2018/wordml" },
w16sdtdh: { key: "xmlns:w16sdtdh", value: "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" },
w16se: { key: "xmlns:w16se", value: "http://schemas.microsoft.com/office/word/2015/wordml/symex" },
Ignorable: { key: "mc:Ignorable", value: "w14 w15 w16se w16cid w16 w16cex w16sdtdh" },
},
children: fonts.map((font, i) =>
createRegularFont({
name: font.name,
index: i + 1,
fontKey: font.fontKey,
}),
),
});

View File

@ -0,0 +1,36 @@
import { IViewWrapper } from "@file/document-wrapper";
import { Relationships } from "@file/relationships";
import { XmlComponent } from "@file/xml-components";
import { uniqueUuid } from "@util/convenience-functions";
import { FontOptions, createFontTable } from "./font-table";
export type FontOptionsWithKey = FontOptions & { readonly fontKey: string };
export class FontWrapper implements IViewWrapper {
private readonly fontTable: XmlComponent;
private readonly relationships: Relationships;
public readonly fontOptionsWithKey: readonly FontOptionsWithKey[] = [];
public constructor(public readonly options: readonly FontOptions[]) {
this.fontOptionsWithKey = options.map((o) => ({ ...o, fontKey: uniqueUuid() }));
this.fontTable = createFontTable(this.fontOptionsWithKey);
this.relationships = new Relationships();
for (let i = 0; i < options.length; i++) {
this.relationships.createRelationship(
i + 1,
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/font",
`fonts/${options[i].name}.odttf`,
);
}
}
public get View(): XmlComponent {
return this.fontTable;
}
public get Relationships(): Relationships {
return this.relationships;
}
}

223
src/file/fonts/font.spec.ts Normal file
View File

@ -0,0 +1,223 @@
import { describe, expect, it } from "vitest";
import { Formatter } from "@export/formatter";
import { createFont } from "./font";
describe("font", () => {
it("should work", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
altName: "Times New Roman",
family: "roman",
charset: "00",
panose1: "02020603050405020304",
pitch: "variable",
embedRegular: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).to.deep.equal({
"w:font": [
{
_attr: {
"w:name": "Times New Roman",
},
},
{
"w:altName": {
_attr: {
"w:val": "Times New Roman",
},
},
},
{
"w:panose1": {
_attr: {
"w:val": "02020603050405020304",
},
},
},
{
"w:charset": {
_attr: {
"w:val": "00",
},
},
},
{
"w:family": {
_attr: {
"w:val": "roman",
},
},
},
{
"w:pitch": {
_attr: {
"w:val": "variable",
},
},
},
{
"w:embedRegular": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
],
});
});
it("should work for embedBold", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedBold: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedBold": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
]),
});
});
it("should work for embedBoldItalic", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedBoldItalic: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedBoldItalic": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
]),
});
});
it("should work for embedItalic", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedItalic: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedItalic": {
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
},
]),
});
});
it("should work for notTrueType", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedRegular: {
id: "rId0",
fontKey: "00000000-0000-0000-0000-000000000000",
subsetted: true,
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedRegular": [
{
_attr: {
"r:id": "rId0",
"w:fontKey": "{00000000-0000-0000-0000-000000000000}",
},
},
{
"w:subsetted": {},
},
],
},
]),
});
});
it("should work for subsetted", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
notTrueType: true,
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:notTrueType": {},
},
]),
});
});
it("should work without fontKey", () => {
const tree = new Formatter().format(
createFont({
name: "Times New Roman",
embedItalic: {
id: "rId0",
},
}),
);
expect(tree).toStrictEqual({
"w:font": expect.arrayContaining([
{
"w:embedItalic": {
_attr: {
"r:id": "rId0",
},
},
},
]),
});
});
});

156
src/file/fonts/font.ts Normal file
View File

@ -0,0 +1,156 @@
import { BuilderElement, createStringElement, OnOffElement, XmlComponent } from "@file/xml-components";
// <xsd:complexType name="CT_Font">
// <xsd:sequence>
// <xsd:element name="altName" type="CT_String" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="panose1" type="CT_Panose" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="charset" type="CT_Charset" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="family" type="CT_FontFamily" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="notTrueType" type="CT_OnOff" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="pitch" type="CT_Pitch" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="sig" type="CT_FontSig" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedRegular" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedBold" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedItalic" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// <xsd:element name="embedBoldItalic" type="CT_FontRel" minOccurs="0" maxOccurs="1"/>
// </xsd:sequence>
// <xsd:attribute name="name" type="s:ST_String" use="required"/>
// </xsd:complexType>
// <xsd:complexType name="CT_FontRel">
// <xsd:complexContent>
// <xsd:extension base="CT_Rel">
// <xsd:attribute name="fontKey" type="s:ST_Guid" />
// <xsd:attribute name="subsetted" type="s:ST_OnOff" />
// </xsd:extension>
// </xsd:complexContent>
// </xsd:complexType>
// http://www.datypic.com/sc/ooxml/e-w_embedRegular-1.html
export interface IFontRelationshipOptions {
/**
* Relationship to Part
*/
readonly id: string;
/**
* Embedded Font Obfuscation Key
*/
readonly fontKey?: string;
/**
* Embedded Font Is Subsetted
*/
readonly subsetted?: boolean;
}
export const CharacterSet = {
ANSI: "00",
DEFAULT: "01",
SYMBOL: "02",
MAC: "4D",
JIS: "80",
HANGUL: "81",
JOHAB: "82",
GB_2312: "86",
CHINESEBIG5: "88",
GREEK: "A1",
TURKISH: "A2",
VIETNAMESE: "A3",
HEBREW: "B1",
ARABIC: "B2",
BALTIC: "BA",
RUSSIAN: "CC",
THAI: "DE",
EASTEUROPE: "EE",
OEM: "FF",
} as const;
export type FontOptions = {
readonly name: string;
readonly altName?: string;
readonly panose1?: string;
readonly charset?: (typeof CharacterSet)[keyof typeof CharacterSet];
readonly family?: string;
readonly notTrueType?: boolean;
readonly pitch?: string;
readonly sig?: {
readonly usb0: string;
readonly usb1: string;
readonly usb2: string;
readonly usb3: string;
readonly csb0: string;
readonly csb1: string;
};
readonly embedRegular?: IFontRelationshipOptions;
readonly embedBold?: IFontRelationshipOptions;
readonly embedItalic?: IFontRelationshipOptions;
readonly embedBoldItalic?: IFontRelationshipOptions;
};
const createFontRelationship = ({ id, fontKey, subsetted }: IFontRelationshipOptions, name: string): XmlComponent =>
new BuilderElement({
name,
attributes: {
id: { key: "r:id", value: id },
...(fontKey ? { fontKey: { key: "w:fontKey", value: `{${fontKey}}` } } : {}),
},
children: [...(subsetted ? [new OnOffElement("w:subsetted", subsetted)] : [])],
});
export const createFont = ({
name,
altName,
panose1,
charset,
family,
notTrueType,
pitch,
sig,
embedRegular,
embedBold,
embedItalic,
embedBoldItalic,
}: FontOptions): XmlComponent =>
// http://www.datypic.com/sc/ooxml/e-w_font-1.html
new BuilderElement({
name: "w:font",
attributes: {
name: { key: "w:name", value: name },
},
children: [
// http://www.datypic.com/sc/ooxml/e-w_altName-1.html
...(altName ? [createStringElement("w:altName", altName)] : []),
// http://www.datypic.com/sc/ooxml/e-w_panose1-1.html
...(panose1 ? [createStringElement("w:panose1", panose1)] : []),
// http://www.datypic.com/sc/ooxml/e-w_charset-1.html
...(charset ? [createStringElement("w:charset", charset)] : []),
// http://www.datypic.com/sc/ooxml/e-w_family-1.html
...(family ? [createStringElement("w:family", family)] : []),
// http://www.datypic.com/sc/ooxml/e-w_notTrueType-1.html
...(notTrueType ? [new OnOffElement("w:notTrueType", notTrueType)] : []),
...(pitch ? [createStringElement("w:pitch", pitch)] : []),
// http://www.datypic.com/sc/ooxml/e-w_sig-1.html
...(sig
? [
new BuilderElement({
name: "w:sig",
attributes: {
usb0: { key: "w:usb0", value: sig.usb0 },
usb1: { key: "w:usb1", value: sig.usb1 },
usb2: { key: "w:usb2", value: sig.usb2 },
usb3: { key: "w:usb3", value: sig.usb3 },
csb0: { key: "w:csb0", value: sig.csb0 },
csb1: { key: "w:csb1", value: sig.csb1 },
},
}),
]
: []),
// http://www.datypic.com/sc/ooxml/e-w_embedRegular-1.html
...(embedRegular ? [createFontRelationship(embedRegular, "w:embedRegular")] : []),
// http://www.datypic.com/sc/ooxml/e-w_embedBold-1.html
...(embedBold ? [createFontRelationship(embedBold, "w:embedBold")] : []),
// http://www.datypic.com/sc/ooxml/e-w_embedItalic-1.html
...(embedItalic ? [createFontRelationship(embedItalic, "w:embedItalic")] : []),
// http://www.datypic.com/sc/ooxml/e-w_embedBoldItalic-1.html
...(embedBoldItalic ? [createFontRelationship(embedBoldItalic, "w:embedBoldItalic")] : []),
],
});

1
src/file/fonts/index.ts Normal file
View File

@ -0,0 +1 @@
export { CharacterSet } from "./font";

View File

@ -0,0 +1,22 @@
const obfuscatedStartOffset = 0;
const obfuscatedEndOffset = 32;
const guidSize = 32;
export const obfuscate = (buf: Buffer, fontKey: string): Buffer => {
const guid = fontKey.replace(/-/g, "");
if (guid.length !== guidSize) {
throw new Error(`Error: Cannot extract GUID from font filename: ${fontKey}`);
}
const hexStrings = guid.replace(/(..)/g, "$1 ").trim().split(" ");
const hexNumbers = hexStrings.map((hexString) => parseInt(hexString, 16));
// eslint-disable-next-line functional/immutable-data
hexNumbers.reverse();
const bytesToObfuscate = buf.slice(obfuscatedStartOffset, obfuscatedEndOffset);
// eslint-disable-next-line no-bitwise
const obfuscatedBytes = bytesToObfuscate.map((byte, i) => byte ^ hexNumbers[i % hexNumbers.length]);
const out = Buffer.concat([buf.slice(0, obfuscatedStartOffset), obfuscatedBytes, buf.slice(obfuscatedEndOffset)]);
return out;
};

View File

@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { obfuscate } from "./obfuscate-ttf-to-odttf";
describe("obfuscate", () => {
it("should work", () => {
const buffer = obfuscate(Buffer.from(""), "00000000-0000-0000-0000-000000000000");
expect(buffer).toBeDefined();
});
it("should throw error if uuid is not correct", () => {
expect(() => obfuscate(Buffer.from(""), "bad-uuid")).toThrowError();
});
});

View File

@ -18,3 +18,4 @@ export * from "./shared";
export * from "./border";
export * from "./vertical-align";
export * from "./checkbox";
export * from "./fonts";

View File

@ -70,8 +70,8 @@ export class ImageRun extends Run {
.split("")
.map((c) => c.charCodeAt(0)),
);
/* c8 ignore next 6 */
} else {
/* c8 ignore next 4 */
// Not possible to test this branch in NodeJS
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const b = require("buf" + "fer");

View File

@ -17,7 +17,8 @@ export type RelationshipType =
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
| "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
export const TargetModeType = {
EXTERNAL: "External",

View File

@ -30,6 +30,7 @@ export const convertToXmlComponent = (element: XmlElement): ImportedXmlComponent
return element.text as string;
default:
return undefined;
/* c8 ignore next 2 */
}
};

View File

@ -55,6 +55,14 @@ export class StringValueElement extends XmlComponent {
}
}
export const createStringElement = (name: string, value: string): XmlComponent =>
new BuilderElement({
name,
attributes: {
value: { key: "w:val", value },
},
});
// This represents various number element types.
export class NumberValueElement extends XmlComponent {
public constructor(name: string, val: number) {
@ -82,19 +90,23 @@ export class StringContainer extends XmlComponent {
}
export class BuilderElement<T extends AttributeData> extends XmlComponent {
public constructor(options: {
public constructor({
name,
attributes,
children,
}: {
readonly name: string;
readonly attributes?: AttributePayload<T>;
readonly children?: readonly XmlComponent[];
}) {
super(options.name);
super(name);
if (options.attributes) {
this.root.push(new NextAttributeComponent(options.attributes));
if (attributes) {
this.root.push(new NextAttributeComponent(attributes));
}
for (const child of options.children ?? []) {
this.root.push(child);
if (children) {
this.root.push(...children);
}
}
}