From 4513bb529b47d9c6fbec4feafcc8eb3bfa66b879 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Thu, 29 Dec 2022 09:57:15 +0000 Subject: [PATCH] Add more run properties and Universal measure --- .../section-properties/properties/column.ts | 28 ++--- .../section-properties/properties/columns.ts | 29 ++--- .../properties/line-number.ts | 30 ++--- .../properties/page-margin.ts | 64 ++++------ .../properties/page-size.ts | 30 ++--- .../section-properties/section-properties.ts | 9 +- src/file/paragraph/formatting/indent.ts | 94 ++++++++------- src/file/paragraph/run/formatting.ts | 4 +- src/file/paragraph/run/properties.ts | 40 ++++++- src/file/paragraph/run/run.spec.ts | 113 ++++++++++++++++++ src/file/table/grid.ts | 18 +-- src/file/table/table-width.ts | 22 ++-- src/file/xml-components/simple-elements.ts | 9 +- src/util/values.spec.ts | 1 - src/util/values.ts | 41 +++++-- 15 files changed, 342 insertions(+), 190 deletions(-) diff --git a/src/file/document/body/section-properties/properties/column.ts b/src/file/document/body/section-properties/properties/column.ts index b0b12d9c8e..b90606f436 100644 --- a/src/file/document/body/section-properties/properties/column.ts +++ b/src/file/document/body/section-properties/properties/column.ts @@ -1,25 +1,23 @@ -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { twipsMeasureValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { PositiveUniversalMeasure, twipsMeasureValue } from "@util/values"; -export interface IColumnAttributes { - readonly width: number | string; - readonly space?: number | string; -} +// +// +// +// -export class ColumnAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - width: "w:w", - space: "w:space", - }; -} +type IColumnAttributes = { + readonly width: number | PositiveUniversalMeasure; + readonly space?: number | PositiveUniversalMeasure; +}; export class Column extends XmlComponent { public constructor({ width, space }: IColumnAttributes) { super("w:col"); this.root.push( - new ColumnAttributes({ - width: twipsMeasureValue(width), - space: space === undefined ? undefined : twipsMeasureValue(space), + new NextAttributeComponent({ + width: { key: "w:w", value: twipsMeasureValue(width) }, + space: { key: "w:space", value: space === undefined ? undefined : twipsMeasureValue(space) }, }), ); } diff --git a/src/file/document/body/section-properties/properties/columns.ts b/src/file/document/body/section-properties/properties/columns.ts index dc09e60462..8cf204d1a6 100644 --- a/src/file/document/body/section-properties/properties/columns.ts +++ b/src/file/document/body/section-properties/properties/columns.ts @@ -1,5 +1,5 @@ -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { decimalNumber, twipsMeasureValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { decimalNumber, PositiveUniversalMeasure, twipsMeasureValue } from "@util/values"; import { Column } from "./column"; @@ -12,32 +12,23 @@ import { Column } from "./column"; // // // -export interface IColumnsAttributes { - readonly space?: number | string; +export type IColumnsAttributes = { + readonly space?: number | PositiveUniversalMeasure; readonly count?: number; readonly separate?: boolean; readonly equalWidth?: boolean; readonly children?: readonly Column[]; -} - -export class ColumnsAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - space: "w:space", - count: "w:num", - separate: "w:sep", - equalWidth: "w:equalWidth", - }; -} +}; export class Columns extends XmlComponent { public constructor({ space, count, separate, equalWidth, children }: IColumnsAttributes) { super("w:cols"); this.root.push( - new ColumnsAttributes({ - space: space === undefined ? undefined : twipsMeasureValue(space), - count: count === undefined ? undefined : decimalNumber(count), - separate, - equalWidth, + new NextAttributeComponent>({ + space: { key: "w:space", value: space === undefined ? undefined : twipsMeasureValue(space) }, + count: { key: "w:num", value: count === undefined ? undefined : decimalNumber(count) }, + separate: { key: "w:sep", value: separate }, + equalWidth: { key: "w:equalWidth", value: equalWidth }, }), ); diff --git a/src/file/document/body/section-properties/properties/line-number.ts b/src/file/document/body/section-properties/properties/line-number.ts index 4d0e622119..845c7b70e1 100644 --- a/src/file/document/body/section-properties/properties/line-number.ts +++ b/src/file/document/body/section-properties/properties/line-number.ts @@ -1,6 +1,6 @@ // http://officeopenxml.com/WPsectionLineNumbering.php -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { decimalNumber, twipsMeasureValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { decimalNumber, PositiveUniversalMeasure, twipsMeasureValue } from "@util/values"; // // @@ -26,27 +26,23 @@ export interface ILineNumberAttributes { readonly countBy?: number; readonly start?: number; readonly restart?: LineNumberRestartFormat; - readonly distance?: number | string; -} - -export class LineNumberAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - countBy: "w:countBy", - start: "w:start", - restart: "w:restart", - distance: "w:distance", - }; + readonly distance?: number | PositiveUniversalMeasure; } export class LineNumberType extends XmlComponent { public constructor({ countBy, start, restart, distance }: ILineNumberAttributes) { super("w:lnNumType"); this.root.push( - new LineNumberAttributes({ - countBy: countBy === undefined ? undefined : decimalNumber(countBy), - start: start === undefined ? undefined : decimalNumber(start), - restart, - distance: distance === undefined ? undefined : twipsMeasureValue(distance), + new NextAttributeComponent<{ + readonly countBy?: number; + readonly start?: number; + readonly restart?: LineNumberRestartFormat; + readonly distance?: number | PositiveUniversalMeasure; + }>({ + countBy: { key: "w:countBy", value: countBy === undefined ? undefined : decimalNumber(countBy) }, + start: { key: "w:start", value: start === undefined ? undefined : decimalNumber(start) }, + restart: { key: "w:restart", value: restart }, + distance: { key: "w:distance", value: distance === undefined ? undefined : twipsMeasureValue(distance) }, }), ); } diff --git a/src/file/document/body/section-properties/properties/page-margin.ts b/src/file/document/body/section-properties/properties/page-margin.ts index ed0044a417..5e905c0f73 100644 --- a/src/file/document/body/section-properties/properties/page-margin.ts +++ b/src/file/document/body/section-properties/properties/page-margin.ts @@ -1,5 +1,5 @@ -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { signedTwipsMeasureValue, twipsMeasureValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { PositiveUniversalMeasure, signedTwipsMeasureValue, twipsMeasureValue, UniversalMeasure } from "@util/values"; // // @@ -10,48 +10,36 @@ import { signedTwipsMeasureValue, twipsMeasureValue } from "@util/values"; // // // -export interface IPageMarginAttributes { - readonly top?: number | string; - readonly right?: number | string; - readonly bottom?: number | string; - readonly left?: number | string; - readonly header?: number | string; - readonly footer?: number | string; - readonly gutter?: number | string; -} - -export class PageMarginAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - top: "w:top", - right: "w:right", - bottom: "w:bottom", - left: "w:left", - header: "w:header", - footer: "w:footer", - gutter: "w:gutter", - }; -} +export type IPageMarginAttributes = { + readonly top?: number | UniversalMeasure; + readonly right?: number | PositiveUniversalMeasure; + readonly bottom?: number | UniversalMeasure; + readonly left?: number | PositiveUniversalMeasure; + readonly header?: number | PositiveUniversalMeasure; + readonly footer?: number | PositiveUniversalMeasure; + readonly gutter?: number | PositiveUniversalMeasure; +}; export class PageMargin extends XmlComponent { public constructor( - top: number | string, - right: number | string, - bottom: number | string, - left: number | string, - header: number | string, - footer: number | string, - gutter: number | string, + top: number | UniversalMeasure, + right: number | PositiveUniversalMeasure, + bottom: number | UniversalMeasure, + left: number | PositiveUniversalMeasure, + header: number | PositiveUniversalMeasure, + footer: number | PositiveUniversalMeasure, + gutter: number | PositiveUniversalMeasure, ) { super("w:pgMar"); this.root.push( - new PageMarginAttributes({ - top: signedTwipsMeasureValue(top), - right: twipsMeasureValue(right), - bottom: signedTwipsMeasureValue(bottom), - left: twipsMeasureValue(left), - header: twipsMeasureValue(header), - footer: twipsMeasureValue(footer), - gutter: twipsMeasureValue(gutter), + new NextAttributeComponent({ + top: { key: "w:top", value: signedTwipsMeasureValue(top) }, + right: { key: "w:right", value: twipsMeasureValue(right) }, + bottom: { key: "w:bottom", value: signedTwipsMeasureValue(bottom) }, + left: { key: "w:left", value: twipsMeasureValue(left) }, + header: { key: "w:header", value: twipsMeasureValue(header) }, + footer: { key: "w:footer", value: twipsMeasureValue(footer) }, + gutter: { key: "w:gutter", value: twipsMeasureValue(gutter) }, }), ); } diff --git a/src/file/document/body/section-properties/properties/page-size.ts b/src/file/document/body/section-properties/properties/page-size.ts index c1e10c48c4..efc4ad404f 100644 --- a/src/file/document/body/section-properties/properties/page-size.ts +++ b/src/file/document/body/section-properties/properties/page-size.ts @@ -1,5 +1,5 @@ -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { twipsMeasureValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { PositiveUniversalMeasure, twipsMeasureValue } from "@util/values"; // // @@ -18,22 +18,14 @@ export enum PageOrientation { // // // -export interface IPageSizeAttributes { - readonly width?: number | string; - readonly height?: number | string; +export type IPageSizeAttributes = { + readonly width?: number | PositiveUniversalMeasure; + readonly height?: number | PositiveUniversalMeasure; readonly orientation?: PageOrientation; -} - -export class PageSizeAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - width: "w:w", - height: "w:h", - orientation: "w:orient", - }; -} +}; export class PageSize extends XmlComponent { - public constructor(width: number | string, height: number | string, orientation: PageOrientation) { + public constructor(width: number | PositiveUniversalMeasure, height: number | PositiveUniversalMeasure, orientation: PageOrientation) { super("w:pgSz"); const flip = orientation === PageOrientation.LANDSCAPE; @@ -42,10 +34,10 @@ export class PageSize extends XmlComponent { const heightTwips = twipsMeasureValue(height); this.root.push( - new PageSizeAttributes({ - width: flip ? heightTwips : widthTwips, - height: flip ? widthTwips : heightTwips, - orientation: orientation, + new NextAttributeComponent({ + width: { key: "w:w", value: flip ? heightTwips : widthTwips }, + height: { key: "w:h", value: flip ? widthTwips : heightTwips }, + orientation: { key: "w:orient", value: orientation }, }), ); } diff --git a/src/file/document/body/section-properties/section-properties.ts b/src/file/document/body/section-properties/section-properties.ts index 5323b77c74..05d8f1edb7 100644 --- a/src/file/document/body/section-properties/section-properties.ts +++ b/src/file/document/body/section-properties/section-properties.ts @@ -17,6 +17,7 @@ import { IPageNumberTypeAttributes, PageNumberType } from "./properties/page-num import { IPageSizeAttributes, PageOrientation, PageSize } from "./properties/page-size"; import { PageTextDirection, PageTextDirectionType } from "./properties/page-text-direction"; import { SectionType, Type } from "./properties/section-type"; +import { PositiveUniversalMeasure, UniversalMeasure } from "@util/values"; export interface IHeaderFooterGroup { readonly default?: T; @@ -76,10 +77,10 @@ export interface ISectionPropertiesOptions { // export const sectionMarginDefaults = { - TOP: "1in", - RIGHT: "1in", - BOTTOM: "1in", - LEFT: "1in", + TOP: "1in" as UniversalMeasure, + RIGHT: "1in" as PositiveUniversalMeasure, + BOTTOM: "1in" as UniversalMeasure, + LEFT: "1in" as PositiveUniversalMeasure, HEADER: 708, FOOTER: 708, GUTTER: 0, diff --git a/src/file/paragraph/formatting/indent.ts b/src/file/paragraph/formatting/indent.ts index daa5d2925e..059cf9c2a9 100644 --- a/src/file/paragraph/formatting/indent.ts +++ b/src/file/paragraph/formatting/indent.ts @@ -1,39 +1,14 @@ // http://officeopenxml.com/WPindentation.php -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { signedTwipsMeasureValue, twipsMeasureValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { PositiveUniversalMeasure, signedTwipsMeasureValue, twipsMeasureValue, UniversalMeasure } from "@util/values"; export interface IIndentAttributesProperties { - readonly start?: number | string; - readonly end?: number | string; - readonly left?: number | string; - readonly right?: number | string; - readonly hanging?: number | string; - readonly firstLine?: number | string; -} - -// -// -// -// -// -// -// -// -// -// -// -// -// -// -class IndentAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { - start: "w:start", - end: "w:end", - left: "w:left", - right: "w:right", - hanging: "w:hanging", - firstLine: "w:firstLine", - }; + readonly start?: number | UniversalMeasure; + readonly end?: number | UniversalMeasure; + readonly left?: number | UniversalMeasure; + readonly right?: number | UniversalMeasure; + readonly hanging?: number | PositiveUniversalMeasure; + readonly firstLine?: number | PositiveUniversalMeasure; } // @@ -43,14 +18,53 @@ class IndentAttributes extends XmlAttributeComponent + // + // + // + // + // + // + // + // + // + // + // + // + // this.root.push( - new IndentAttributes({ - start: start === undefined ? undefined : signedTwipsMeasureValue(start), - end: end === undefined ? undefined : signedTwipsMeasureValue(end), - left: left === undefined ? undefined : signedTwipsMeasureValue(left), - right: right === undefined ? undefined : signedTwipsMeasureValue(right), - hanging: hanging === undefined ? undefined : twipsMeasureValue(hanging), - firstLine: firstLine === undefined ? undefined : twipsMeasureValue(firstLine), + new NextAttributeComponent<{ + readonly start?: number | UniversalMeasure; + readonly end?: number | UniversalMeasure; + readonly left?: number | UniversalMeasure; + readonly right?: number | UniversalMeasure; + readonly hanging?: number | PositiveUniversalMeasure; + readonly firstLine?: number | PositiveUniversalMeasure; + }>({ + start: { + key: "w:start", + value: start === undefined ? undefined : signedTwipsMeasureValue(start), + }, + end: { + key: "w:end", + value: end === undefined ? undefined : signedTwipsMeasureValue(end), + }, + left: { + key: "w:left", + value: left === undefined ? undefined : signedTwipsMeasureValue(left), + }, + right: { + key: "w:right", + value: right === undefined ? undefined : signedTwipsMeasureValue(right), + }, + hanging: { + key: "w:hanging", + value: hanging === undefined ? undefined : twipsMeasureValue(hanging), + }, + firstLine: { + key: "w:firstLine", + value: firstLine === undefined ? undefined : twipsMeasureValue(firstLine), + }, }), ); } diff --git a/src/file/paragraph/run/formatting.ts b/src/file/paragraph/run/formatting.ts index b78e3291a7..3629dd36ee 100644 --- a/src/file/paragraph/run/formatting.ts +++ b/src/file/paragraph/run/formatting.ts @@ -1,8 +1,8 @@ import { Attributes, XmlComponent } from "@file/xml-components"; -import { hexColorValue, signedTwipsMeasureValue } from "@util/values"; +import { hexColorValue, signedTwipsMeasureValue, UniversalMeasure } from "@util/values"; export class CharacterSpacing extends XmlComponent { - public constructor(value: number | string) { + public constructor(value: number | UniversalMeasure) { super("w:spacing"); this.root.push( new Attributes({ diff --git a/src/file/paragraph/run/properties.ts b/src/file/paragraph/run/properties.ts index bb774cbc3d..41ca650995 100644 --- a/src/file/paragraph/run/properties.ts +++ b/src/file/paragraph/run/properties.ts @@ -9,6 +9,7 @@ import { StringValueElement, XmlComponent, } from "@file/xml-components"; +import { PositiveUniversalMeasure, UniversalMeasure } from "@util/values"; import { EmphasisMark, EmphasisMarkType } from "./emphasis-mark"; import { CharacterSpacing, Color, Highlight, HighlightComplexScript } from "./formatting"; @@ -22,6 +23,16 @@ interface IFontOptions { readonly hint?: string; } +export enum TextEffect { + BLINK_BACKGROUND = "blinkBackground", + LIGHTS = "lights", + ANTS_BLACK = "antsBlack", + ANTS_RED = "antsRed", + SHIMMER = "shimmer", + SPARKLE = "sparkle", + NONE = "none", +} + export interface IRunStylePropertiesOptions { readonly bold?: boolean; readonly boldComplexScript?: boolean; @@ -31,12 +42,15 @@ export interface IRunStylePropertiesOptions { readonly color?: string; readonly type?: UnderlineType; }; + readonly effect?: TextEffect; readonly emphasisMark?: { readonly type?: EmphasisMarkType; }; readonly color?: string; - readonly size?: number | string; - readonly sizeComplexScript?: boolean | number | string; + readonly kern?: number | PositiveUniversalMeasure; + readonly position?: UniversalMeasure; + readonly size?: number | PositiveUniversalMeasure; + readonly sizeComplexScript?: boolean | number | PositiveUniversalMeasure; readonly rightToLeft?: boolean; readonly smallCaps?: boolean; readonly allCaps?: boolean; @@ -54,9 +68,11 @@ export interface IRunStylePropertiesOptions { readonly revision?: IRunPropertiesChangeOptions; readonly language?: ILanguageOptions; readonly border?: IBorderOptions; + readonly snapToGrid?: boolean; readonly vanish?: boolean; readonly specVanish?: boolean; readonly scale?: number; + readonly math?: boolean; } export interface IRunPropertiesOptions extends IRunStylePropertiesOptions { @@ -135,6 +151,10 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { this.push(new Underline(options.underline.type, options.underline.color)); } + if (options.effect) { + this.push(new StringValueElement("w:effect", options.effect)); + } + if (options.emphasisMark) { this.push(new EmphasisMark(options.emphasisMark.type)); } @@ -143,6 +163,14 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { this.push(new Color(options.color)); } + if (options.kern) { + this.push(new HpsMeasureElement("w:kern", options.kern)); + } + + if (options.position) { + this.push(new StringValueElement("w:position", options.position)); + } + if (options.size !== undefined) { this.push(new HpsMeasureElement("w:sz", options.size)); } @@ -228,6 +256,10 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { this.push(new BorderElement("w:bdr", options.border)); } + if (options.snapToGrid) { + this.push(new OnOffElement("w:snapToGrid", options.snapToGrid)); + } + if (options.vanish) { // https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_vanish_topic_ID0E6W3O.html // http://www.datypic.com/sc/ooxml/e-w_vanish-1.html @@ -246,6 +278,10 @@ export class RunProperties extends IgnoreIfEmptyXmlComponent { if (options.language) { this.push(createLanguageComponent(options.language)); } + + if (options.math) { + this.push(new OnOffElement("w:oMath", options.math)); + } } public push(item: XmlComponent): void { diff --git a/src/file/paragraph/run/run.spec.ts b/src/file/paragraph/run/run.spec.ts index 2a207a7b87..b639e08e49 100644 --- a/src/file/paragraph/run/run.spec.ts +++ b/src/file/paragraph/run/run.spec.ts @@ -7,6 +7,7 @@ import { ShadingType } from "@file/shading"; import { EmphasisMarkType } from "./emphasis-mark"; import { PageNumber, Run } from "./run"; import { UnderlineType } from "./underline"; +import { TextEffect } from "./properties"; describe("Run", () => { describe("#bold()", () => { @@ -610,5 +611,117 @@ describe("Run", () => { }); }); }); + + describe("#position", () => { + it("should correctly set the position", () => { + const run = new Run({ + position: "2mm", + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + { + "w:rPr": [ + { + "w:position": { + _attr: { + "w:val": "2mm", + }, + }, + }, + ], + }, + ], + }); + }); + }); + + describe("#effect", () => { + it("should correctly set the effect", () => { + const run = new Run({ + effect: TextEffect.ANTS_BLACK, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + { + "w:rPr": [ + { + "w:effect": { + _attr: { + "w:val": "antsBlack", + }, + }, + }, + ], + }, + ], + }); + }); + }); + + describe("#math", () => { + it("should correctly set the math", () => { + const run = new Run({ + math: true, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + { + "w:rPr": [ + { + "w:oMath": {}, + }, + ], + }, + ], + }); + }); + }); + + describe("#kern", () => { + it("should correctly set the kern", () => { + const run = new Run({ + kern: "2mm", + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + { + "w:rPr": [ + { + "w:kern": { + _attr: { + "w:val": "2mm", + }, + }, + }, + ], + }, + ], + }); + }); + }); + + describe("#snapToGrid", () => { + it("should correctly set the snapToGrid", () => { + const run = new Run({ + snapToGrid: true, + }); + const tree = new Formatter().format(run); + expect(tree).to.deep.equal({ + "w:r": [ + { + "w:rPr": [ + { + "w:snapToGrid": {}, + }, + ], + }, + ], + }); + }); + }); }); }); diff --git a/src/file/table/grid.ts b/src/file/table/grid.ts index 719253b8b2..14eb60c886 100644 --- a/src/file/table/grid.ts +++ b/src/file/table/grid.ts @@ -9,11 +9,11 @@ // // -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { twipsMeasureValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { PositiveUniversalMeasure, twipsMeasureValue } from "@util/values"; export class TableGrid extends XmlComponent { - public constructor(widths: readonly number[] | readonly string[]) { + public constructor(widths: readonly number[] | readonly PositiveUniversalMeasure[]) { super("w:tblGrid"); for (const width of widths) { this.root.push(new GridCol(width)); @@ -21,15 +21,15 @@ export class TableGrid extends XmlComponent { } } -class GridColAttributes extends XmlAttributeComponent<{ readonly w: number | string }> { - protected readonly xmlKeys = { w: "w:w" }; -} - export class GridCol extends XmlComponent { - public constructor(width?: number | string) { + public constructor(width?: number | PositiveUniversalMeasure) { super("w:gridCol"); if (width !== undefined) { - this.root.push(new GridColAttributes({ w: twipsMeasureValue(width) })); + this.root.push( + new NextAttributeComponent<{ readonly width: number | PositiveUniversalMeasure }>({ + width: { key: "w:w", value: twipsMeasureValue(width) }, + }), + ); } } } diff --git a/src/file/table/table-width.ts b/src/file/table/table-width.ts index 38c941b2f1..1c2e29a0c5 100644 --- a/src/file/table/table-width.ts +++ b/src/file/table/table-width.ts @@ -1,6 +1,6 @@ // http://officeopenxml.com/WPtableWidth.php -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { measurementOrPercentValue } from "@util/values"; +import { NextAttributeComponent, XmlComponent } from "@file/xml-components"; +import { measurementOrPercentValue, Percentage, UniversalMeasure } from "@util/values"; // // @@ -25,14 +25,10 @@ export enum WidthType { // // // -export interface ITableWidthProperties { - readonly size: string | number; +export type ITableWidthProperties = { + readonly size: number | Percentage | UniversalMeasure; readonly type?: WidthType; -} - -class TableWidthAttributes extends XmlAttributeComponent { - protected readonly xmlKeys = { type: "w:type", size: "w:w" }; -} +}; export class TableWidthElement extends XmlComponent { public constructor(name: string, { type = WidthType.AUTO, size }: ITableWidthProperties) { @@ -42,6 +38,12 @@ export class TableWidthElement extends XmlComponent { if (type === WidthType.PERCENTAGE && typeof size === "number") { tableWidthValue = `${size}%`; } - this.root.push(new TableWidthAttributes({ type: type, size: measurementOrPercentValue(tableWidthValue) })); + + this.root.push( + new NextAttributeComponent({ + type: { key: "w:type", value: type }, + size: { key: "w:w", value: measurementOrPercentValue(tableWidthValue) }, + }), + ); } } diff --git a/src/file/xml-components/simple-elements.ts b/src/file/xml-components/simple-elements.ts index b81901232c..6ed117b7ba 100644 --- a/src/file/xml-components/simple-elements.ts +++ b/src/file/xml-components/simple-elements.ts @@ -1,6 +1,6 @@ import { AttributeData, AttributePayload, Attributes, NextAttributeComponent, XmlComponent } from "@file/xml-components"; -import { hpsMeasureValue } from "@util/values"; +import { hpsMeasureValue, PositiveUniversalMeasure } from "@util/values"; // This represents element type CT_OnOff, which indicate a boolean value. // @@ -26,8 +26,13 @@ export class OnOffElement extends XmlComponent { // // // + +// +// +// + export class HpsMeasureElement extends XmlComponent { - public constructor(name: string, val: number | string) { + public constructor(name: string, val: number | PositiveUniversalMeasure) { super(name); this.root.push(new Attributes({ val: hpsMeasureValue(val) })); } diff --git a/src/util/values.spec.ts b/src/util/values.spec.ts index b4b36dbe44..7331a2e1d7 100644 --- a/src/util/values.spec.ts +++ b/src/util/values.spec.ts @@ -116,7 +116,6 @@ describe("values", () => { }); it("should throw on invalid values", () => { expect(() => signedTwipsMeasureValue(NaN)).to.throw(); - expect(() => signedTwipsMeasureValue("foo")).to.throw(); }); }); diff --git a/src/util/values.ts b/src/util/values.ts index f44e2eb1d9..2f531ccba8 100644 --- a/src/util/values.ts +++ b/src/util/values.ts @@ -4,6 +4,23 @@ // Most of the rest of the types not defined here are either aliases of existing types or enumerations. // Enumerations should probably just be implemented as enums, with instructions to end-users, without a runtime check. +// -?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi) +export type UniversalMeasure = `${"-" | ""}${number}${"mm" | "cm" | "in" | "pt" | "pc" | "pi"}`; + +// +// +// +// +// +export type PositiveUniversalMeasure = `${number}${"mm" | "cm" | "in" | "pt" | "pc" | "pi"}`; + +// +// +// +// +// +export type Percentage = `${"-" | ""}${number}%`; + // // // @@ -70,7 +87,7 @@ export const uCharHexNumber = (val: string): string => hexBinary(val, 1); // // // -export const universalMeasureValue = (val: string): string => { +export const universalMeasureValue = (val: UniversalMeasure): string => { const unit = val.slice(-2); if (!universalMeasureUnits.includes(unit)) { throw new Error(`Invalid unit '${unit}' specified. Valid units are ${universalMeasureUnits.join(", ")}`); @@ -88,7 +105,7 @@ const universalMeasureUnits = ["mm", "cm", "in", "pt", "pc", "pi"]; // // // -export const positiveUniversalMeasureValue = (val: string): string => { +export const positiveUniversalMeasureValue = (val: PositiveUniversalMeasure): string => { const value = universalMeasureValue(val); if (parseFloat(value) < 0) { throw new Error(`Invalid value '${value}' specified. Expected a positive number.`); @@ -123,33 +140,33 @@ export const hexColorValue = (val: string): string => { // // // -export const signedTwipsMeasureValue = (val: string | number): string | number => - typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val); +export const signedTwipsMeasureValue = (val: UniversalMeasure | number): UniversalMeasure | number => + typeof val === "string" ? val : decimalNumber(val); // // // -export const hpsMeasureValue = (val: string | number): string | number => +export const hpsMeasureValue = (val: PositiveUniversalMeasure | number): string | number => typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); // // // -export const signedHpsMeasureValue = (val: string | number): string | number => +export const signedHpsMeasureValue = (val: UniversalMeasure | number): string | number => typeof val === "string" ? universalMeasureValue(val) : decimalNumber(val); // // // -export const twipsMeasureValue = (val: string | number): string | number => - typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); +export const twipsMeasureValue = (val: PositiveUniversalMeasure | number): PositiveUniversalMeasure | number => + typeof val === "string" ? val : unsignedDecimalNumber(val); // // // // // -export const percentageValue = (val: string): string => { +export const percentageValue = (val: Percentage): Percentage => { if (val.slice(-1) !== "%") { throw new Error(`Invalid value '${val}'. Expected percentage value (eg '55%')`); } @@ -172,14 +189,14 @@ export const percentageValue = (val: string): string => { // // -export const measurementOrPercentValue = (val: number | string): number | string => { +export const measurementOrPercentValue = (val: number | Percentage | UniversalMeasure): number | UniversalMeasure | Percentage => { if (typeof val === "number") { return decimalNumber(val); } if (val.slice(-1) === "%") { - return percentageValue(val); + return percentageValue(val as Percentage); } - return universalMeasureValue(val); + return val as UniversalMeasure; }; //