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..fbcdcf9f88 100644 --- a/src/file/document/body/section-properties/section-properties.ts +++ b/src/file/document/body/section-properties/section-properties.ts @@ -5,9 +5,9 @@ import { FooterWrapper } from "@file/footer-wrapper"; import { HeaderWrapper } from "@file/header-wrapper"; import { VerticalAlign, VerticalAlignElement } from "@file/vertical-align"; import { OnOffElement, XmlComponent } from "@file/xml-components"; +import { PositiveUniversalMeasure, UniversalMeasure } from "@util/values"; import { HeaderFooterReference, HeaderFooterReferenceType, HeaderFooterType } from "./properties/header-footer-reference"; - import { Columns, IColumnsAttributes } from "./properties/columns"; import { DocumentGrid, IDocGridAttributesProperties } from "./properties/doc-grid"; import { ILineNumberAttributes, LineNumberType } from "./properties/line-number"; @@ -76,10 +76,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-properties/table-float-properties.ts b/src/file/table/table-properties/table-float-properties.ts index fdcf696d07..e7f32dc572 100644 --- a/src/file/table/table-properties/table-float-properties.ts +++ b/src/file/table/table-properties/table-float-properties.ts @@ -1,5 +1,5 @@ import { StringEnumValueElement, XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { signedTwipsMeasureValue, twipsMeasureValue } from "@util/values"; +import { PositiveUniversalMeasure, signedTwipsMeasureValue, twipsMeasureValue, UniversalMeasure } from "@util/values"; export enum TableAnchorType { MARGIN = "margin", @@ -55,7 +55,7 @@ export interface ITableFloatOptions { * If relativeHorizontalPosition is also specified, then the absoluteHorizontalPosition attribute is ignored. * If the attribute is omitted, the value is assumed to be zero. */ - readonly absoluteHorizontalPosition?: number | string; + readonly absoluteHorizontalPosition?: number | UniversalMeasure; /** * Specifies a relative horizontal position for the table, relative to the horizontalAnchor attribute. @@ -86,7 +86,7 @@ export interface ITableFloatOptions { * If relativeVerticalPosition is also specified, then the absoluteVerticalPosition attribute is ignored. * If the attribute is omitted, the value is assumed to be zero. */ - readonly absoluteVerticalPosition?: number | string; + readonly absoluteVerticalPosition?: number | UniversalMeasure; /** * Specifies a relative vertical position for the table, relative to the verticalAnchor attribute. @@ -104,25 +104,25 @@ export interface ITableFloatOptions { * Specifies the minimum distance to be maintained between the table and the top of text in the paragraph * below the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly bottomFromText?: number | string; + readonly bottomFromText?: number | PositiveUniversalMeasure; /** * Specifies the minimum distance to be maintained between the table and the bottom edge of text in the paragraph * above the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly topFromText?: number | string; + readonly topFromText?: number | PositiveUniversalMeasure; /** * Specifies the minimum distance to be maintained between the table and the edge of text in the paragraph * to the left of the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly leftFromText?: number | string; + readonly leftFromText?: number | PositiveUniversalMeasure; /** * Specifies the minimum distance to be maintained between the table and the edge of text in the paragraph * to the right of the table. The value is in twentieths of a point. If omitted, the value is assumed to be zero. */ - readonly rightFromText?: number | string; + readonly rightFromText?: number | PositiveUniversalMeasure; readonly overlap?: OverlapType; } diff --git a/src/file/table/table-row/table-row-height.ts b/src/file/table/table-row/table-row-height.ts index 12294b7f4e..4c6043bb47 100644 --- a/src/file/table/table-row/table-row-height.ts +++ b/src/file/table/table-row/table-row-height.ts @@ -1,5 +1,5 @@ import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; -import { twipsMeasureValue } from "@util/values"; +import { PositiveUniversalMeasure, twipsMeasureValue } from "@util/values"; // // @@ -30,7 +30,7 @@ export class TableRowHeightAttributes extends XmlAttributeComponent<{ } export class TableRowHeight extends XmlComponent { - public constructor(value: number | string, rule: HeightRule) { + public constructor(value: number | PositiveUniversalMeasure, rule: HeightRule) { super("w:trHeight"); this.root.push( diff --git a/src/file/table/table-row/table-row-properties.ts b/src/file/table/table-row/table-row-properties.ts index c4fa01ea6d..d388e917eb 100644 --- a/src/file/table/table-row/table-row-properties.ts +++ b/src/file/table/table-row/table-row-properties.ts @@ -28,6 +28,7 @@ // // import { IgnoreIfEmptyXmlComponent, OnOffElement } from "@file/xml-components"; +import { PositiveUniversalMeasure } from "@util/values"; import { HeightRule, TableRowHeight } from "./table-row-height"; @@ -35,7 +36,7 @@ export interface ITableRowPropertiesOptions { readonly cantSplit?: boolean; readonly tableHeader?: boolean; readonly height?: { - readonly value: number | string; + readonly value: number | PositiveUniversalMeasure; readonly rule: HeightRule; }; } 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..32df1a74b2 100644 --- a/src/util/values.spec.ts +++ b/src/util/values.spec.ts @@ -25,13 +25,6 @@ describe("values", () => { expect(universalMeasureValue("5.22pc")).to.eq("5.22pc"); expect(universalMeasureValue("100 pi")).to.eq("100pi"); }); - it("should throw on invalid values", () => { - expect(() => universalMeasureValue("100pp")).to.throw(); - expect(() => universalMeasureValue("foo")).to.throw(); - expect(() => universalMeasureValue("--in")).to.throw(); - expect(() => universalMeasureValue("NaNpc")).to.throw(); - expect(() => universalMeasureValue("50")).to.throw(); - }); }); describe("positiveUniversalMeasureValue", () => { @@ -46,11 +39,6 @@ describe("values", () => { it("should throw on invalid values", () => { expect(() => positiveUniversalMeasureValue("-9mm")).to.throw(); expect(() => positiveUniversalMeasureValue("-0.5in")).to.throw(); - expect(() => positiveUniversalMeasureValue("100pp")).to.throw(); - expect(() => positiveUniversalMeasureValue("foo")).to.throw(); - expect(() => positiveUniversalMeasureValue("--in")).to.throw(); - expect(() => positiveUniversalMeasureValue("NaNpc")).to.throw(); - expect(() => positiveUniversalMeasureValue("50")).to.throw(); }); }); @@ -116,7 +104,6 @@ describe("values", () => { }); it("should throw on invalid values", () => { expect(() => signedTwipsMeasureValue(NaN)).to.throw(); - expect(() => signedTwipsMeasureValue("foo")).to.throw(); }); }); @@ -129,7 +116,6 @@ describe("values", () => { it("should throw on invalid values", () => { expect(() => twipsMeasureValue(-12)).to.throw(); expect(() => twipsMeasureValue(NaN)).to.throw(); - expect(() => twipsMeasureValue("foo")).to.throw(); expect(() => twipsMeasureValue("-5mm")).to.throw(); }); }); @@ -154,7 +140,6 @@ describe("values", () => { }); it("should throw on invalid values", () => { expect(() => hpsMeasureValue(NaN)).to.throw(); - expect(() => hpsMeasureValue("5FF")).to.throw(); }); }); @@ -165,11 +150,6 @@ describe("values", () => { expect(percentageValue("100%")).to.eq("100%"); expect(percentageValue("1000%")).to.eq("1000%"); }); - it("should throw on invalid values", () => { - expect(() => percentageValue("0%%")).to.throw(); - expect(() => percentageValue("20")).to.throw(); - expect(() => percentageValue("FF%")).to.throw(); - }); }); describe("measurementOrPercentValue", () => { @@ -181,8 +161,6 @@ describe("values", () => { }); it("should throw on invalid values", () => { expect(() => measurementOrPercentValue(NaN)).to.throw(); - expect(() => measurementOrPercentValue("10%%")).to.throw(); - expect(() => measurementOrPercentValue("10F")).to.throw(); }); }); diff --git a/src/util/values.ts b/src/util/values.ts index f44e2eb1d9..4776210e69 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,30 +87,23 @@ export const uCharHexNumber = (val: string): string => hexBinary(val, 1); // // // -export const universalMeasureValue = (val: string): string => { +export const universalMeasureValue = (val: UniversalMeasure): UniversalMeasure => { const unit = val.slice(-2); - if (!universalMeasureUnits.includes(unit)) { - throw new Error(`Invalid unit '${unit}' specified. Valid units are ${universalMeasureUnits.join(", ")}`); - } const amount = val.substring(0, val.length - 2); - if (isNaN(Number(amount))) { - throw new Error(`Invalid value '${amount}' specified. Expected a valid number.`); - } - return `${Number(amount)}${unit}`; + return `${Number(amount)}${unit}` as UniversalMeasure; }; -const universalMeasureUnits = ["mm", "cm", "in", "pt", "pc", "pi"]; // // // // // -export const positiveUniversalMeasureValue = (val: string): string => { +export const positiveUniversalMeasureValue = (val: PositiveUniversalMeasure): PositiveUniversalMeasure => { const value = universalMeasureValue(val); if (parseFloat(value) < 0) { throw new Error(`Invalid value '${value}' specified. Expected a positive number.`); } - return value; + return value as PositiveUniversalMeasure; }; // @@ -123,25 +133,25 @@ export const hexColorValue = (val: string): string => { // // // -export const signedTwipsMeasureValue = (val: string | number): string | number => +export const signedTwipsMeasureValue = (val: UniversalMeasure | number): UniversalMeasure | number => typeof val === "string" ? universalMeasureValue(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 => +export const twipsMeasureValue = (val: PositiveUniversalMeasure | number): PositiveUniversalMeasure | number => typeof val === "string" ? positiveUniversalMeasureValue(val) : unsignedDecimalNumber(val); // @@ -149,14 +159,8 @@ export const twipsMeasureValue = (val: string | number): string | number => // // // -export const percentageValue = (val: string): string => { - if (val.slice(-1) !== "%") { - throw new Error(`Invalid value '${val}'. Expected percentage value (eg '55%')`); - } +export const percentageValue = (val: Percentage): Percentage => { const percent = val.substring(0, val.length - 1); - if (isNaN(Number(percent))) { - throw new Error(`Invalid value '${percent}' specified. Expected a valid number.`); - } return `${Number(percent)}%`; }; @@ -172,14 +176,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 universalMeasureValue(val as UniversalMeasure); }; //