From e3582167c6154fc69ce9005d0ebf93fcdd611b38 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Mon, 25 Dec 2023 03:47:52 +0000 Subject: [PATCH] #933 Discriminated union for frame alignment --- demo/61-text-frame.ts | 103 +++++++ .../paragraph/frame/frame-properties.spec.ts | 264 +++++++++--------- src/file/paragraph/frame/frame-properties.ts | 133 ++++++--- src/file/paragraph/paragraph.spec.ts | 7 +- src/file/paragraph/properties.ts | 4 +- 5 files changed, 321 insertions(+), 190 deletions(-) diff --git a/demo/61-text-frame.ts b/demo/61-text-frame.ts index a9460bae878..cd8196e84e9 100644 --- a/demo/61-text-frame.ts +++ b/demo/61-text-frame.ts @@ -2,6 +2,7 @@ import * as fs from "fs"; import { + AlignmentType, BorderStyle, Document, FrameAnchorType, @@ -20,6 +21,7 @@ const doc = new Document({ children: [ new Paragraph({ frame: { + type: "absolute", position: { x: 1000, y: 3000, @@ -30,6 +32,54 @@ const doc = new Document({ horizontal: FrameAnchorType.MARGIN, vertical: FrameAnchorType.MARGIN, }, + }, + border: { + top: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + bottom: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + left: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + right: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + }, + children: [ + new TextRun("Hello World"), + new TextRun({ + text: "Foo Bar", + bold: true, + }), + new TextRun({ + children: [new Tab(), "Github is the best"], + bold: true, + }), + ], + }), + new Paragraph({ + frame: { + type: "alignment", + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, alignment: { x: HorizontalPositionAlign.CENTER, y: VerticalPositionAlign.TOP, @@ -73,6 +123,59 @@ const doc = new Document({ }), ], }), + new Paragraph({ + frame: { + type: "alignment", + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + alignment: { + x: HorizontalPositionAlign.CENTER, + y: VerticalPositionAlign.BOTTOM, + }, + }, + border: { + top: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + bottom: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + left: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + right: { + color: "auto", + space: 1, + style: BorderStyle.SINGLE, + size: 6, + }, + }, + alignment: AlignmentType.RIGHT, + children: [ + new TextRun("Hello World"), + new TextRun({ + text: "Foo Bar", + bold: true, + }), + new TextRun({ + children: [new Tab(), "Github is the best"], + bold: true, + }), + ], + }), ], }, ], diff --git a/src/file/paragraph/frame/frame-properties.spec.ts b/src/file/paragraph/frame/frame-properties.spec.ts index 5ff7afaef3a..fb6d90b9c6e 100644 --- a/src/file/paragraph/frame/frame-properties.spec.ts +++ b/src/file/paragraph/frame/frame-properties.spec.ts @@ -3,154 +3,144 @@ import { describe, expect, it } from "vitest"; import { Formatter } from "@export/formatter"; import { HorizontalPositionAlign, VerticalPositionAlign } from "@file/shared"; -import { FrameAnchorType, FrameProperties } from "./frame-properties"; +import { FrameAnchorType, createFrameProperties } from "./frame-properties"; -describe("FrameProperties", () => { - describe("#constructor()", () => { - it("should create", () => { - const currentFrameProperties = new FrameProperties({ - position: { - x: 1000, - y: 3000, - }, - width: 4000, - height: 1000, - anchor: { - horizontal: FrameAnchorType.MARGIN, - vertical: FrameAnchorType.MARGIN, - }, - alignment: { - x: HorizontalPositionAlign.CENTER, - y: VerticalPositionAlign.TOP, - }, - }); +describe("createFrameProperties", () => { + it("should create", () => { + const currentFrameProperties = createFrameProperties({ + type: "absolute", + position: { + x: 1000, + y: 3000, + }, + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + }); - const tree = new Formatter().format(currentFrameProperties); - expect(tree).to.deep.equal({ - "w:framePr": { - _attr: { - "w:h": 1000, - "w:hAnchor": "margin", - "w:vAnchor": "margin", - "w:w": 4000, - "w:x": 1000, - "w:xAlign": "center", - "w:y": 3000, - "w:yAlign": "top", - }, - }, - }); + const tree = new Formatter().format(currentFrameProperties); + expect(tree).to.deep.equal({ + "w:framePr": { + _attr: { + "w:h": 1000, + "w:hAnchor": "margin", + "w:vAnchor": "margin", + "w:w": 4000, + "w:x": 1000, + "w:y": 3000, + }, + }, }); + }); - it("should create with the space attribute", () => { - const currentFrameProperties = new FrameProperties({ - position: { - x: 1000, - y: 3000, - }, - width: 4000, - height: 1000, - anchor: { - horizontal: FrameAnchorType.MARGIN, - vertical: FrameAnchorType.MARGIN, - }, - alignment: { - x: HorizontalPositionAlign.CENTER, - y: VerticalPositionAlign.TOP, - }, - space: { - horizontal: 100, - vertical: 200, - }, - }); + it("should create with the space attribute", () => { + const currentFrameProperties = createFrameProperties({ + type: "absolute", + position: { + x: 1000, + y: 3000, + }, + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + space: { + horizontal: 100, + vertical: 200, + }, + }); - const tree = new Formatter().format(currentFrameProperties); - expect(tree).to.deep.equal({ - "w:framePr": { - _attr: { - "w:h": 1000, - "w:hAnchor": "margin", - "w:vAnchor": "margin", - "w:w": 4000, - "w:x": 1000, - "w:xAlign": "center", - "w:y": 3000, - "w:yAlign": "top", - "w:hSpace": 100, - "w:vSpace": 200, - }, - }, - }); + const tree = new Formatter().format(currentFrameProperties); + expect(tree).to.deep.equal({ + "w:framePr": { + _attr: { + "w:h": 1000, + "w:hAnchor": "margin", + "w:vAnchor": "margin", + "w:w": 4000, + "w:x": 1000, + "w:y": 3000, + "w:hSpace": 100, + "w:vSpace": 200, + }, + }, }); + }); - it("should create without x and y", () => { - const currentFrameProperties = new FrameProperties({ - width: 4000, - height: 1000, - anchor: { - horizontal: FrameAnchorType.MARGIN, - vertical: FrameAnchorType.MARGIN, - }, - alignment: { - x: HorizontalPositionAlign.CENTER, - y: VerticalPositionAlign.TOP, - }, - space: { - horizontal: 100, - vertical: 200, - }, - }); + it("should create without x and y", () => { + const currentFrameProperties = createFrameProperties({ + type: "alignment", + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + alignment: { + x: HorizontalPositionAlign.CENTER, + y: VerticalPositionAlign.TOP, + }, + space: { + horizontal: 100, + vertical: 200, + }, + }); - const tree = new Formatter().format(currentFrameProperties); - expect(tree).to.deep.equal({ - "w:framePr": { - _attr: { - "w:h": 1000, - "w:hAnchor": "margin", - "w:vAnchor": "margin", - "w:w": 4000, - "w:xAlign": "center", - "w:yAlign": "top", - "w:hSpace": 100, - "w:vSpace": 200, - }, - }, - }); + const tree = new Formatter().format(currentFrameProperties); + expect(tree).to.deep.equal({ + "w:framePr": { + _attr: { + "w:h": 1000, + "w:hAnchor": "margin", + "w:vAnchor": "margin", + "w:w": 4000, + "w:xAlign": "center", + "w:yAlign": "top", + "w:hSpace": 100, + "w:vSpace": 200, + }, + }, }); + }); - it("should create without alignments", () => { - const currentFrameProperties = new FrameProperties({ - position: { - x: 1000, - y: 3000, - }, - width: 4000, - height: 1000, - anchor: { - horizontal: FrameAnchorType.MARGIN, - vertical: FrameAnchorType.MARGIN, - }, - space: { - horizontal: 100, - vertical: 200, - }, - }); + it("should create without alignments", () => { + const currentFrameProperties = createFrameProperties({ + type: "absolute", + position: { + x: 1000, + y: 3000, + }, + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + space: { + horizontal: 100, + vertical: 200, + }, + }); - const tree = new Formatter().format(currentFrameProperties); - expect(tree).to.deep.equal({ - "w:framePr": { - _attr: { - "w:h": 1000, - "w:hAnchor": "margin", - "w:vAnchor": "margin", - "w:w": 4000, - "w:x": 1000, - "w:y": 3000, - "w:hSpace": 100, - "w:vSpace": 200, - }, - }, - }); + const tree = new Formatter().format(currentFrameProperties); + expect(tree).to.deep.equal({ + "w:framePr": { + _attr: { + "w:h": 1000, + "w:hAnchor": "margin", + "w:vAnchor": "margin", + "w:w": 4000, + "w:x": 1000, + "w:y": 3000, + "w:hSpace": 100, + "w:vSpace": 200, + }, + }, }); }); }); diff --git a/src/file/paragraph/frame/frame-properties.ts b/src/file/paragraph/frame/frame-properties.ts index 28ce7c28611..b72ced9f5fd 100644 --- a/src/file/paragraph/frame/frame-properties.ts +++ b/src/file/paragraph/frame/frame-properties.ts @@ -1,7 +1,7 @@ // http://officeopenxml.com/WPparagraph-textFrames.php import { HorizontalPositionAlign, VerticalPositionAlign } from "@file/shared/alignment"; import { HeightRule } from "@file/table"; -import { XmlAttributeComponent, XmlComponent } from "@file/xml-components"; +import { BuilderElement, XmlComponent } from "@file/xml-components"; export const DropCapType = { NONE: "none", @@ -44,6 +44,7 @@ interface IBaseFrameOptions { } export interface IXYFrameOptions extends IBaseFrameOptions { + readonly type: "absolute"; readonly position: { readonly x: number; readonly y: number; @@ -51,6 +52,7 @@ export interface IXYFrameOptions extends IBaseFrameOptions { } export interface IAlignmentFrameOptions extends IBaseFrameOptions { + readonly type: "alignment"; readonly alignment: { readonly x: (typeof HorizontalPositionAlign)[keyof typeof HorizontalPositionAlign]; readonly y: (typeof VerticalPositionAlign)[keyof typeof VerticalPositionAlign]; @@ -61,7 +63,24 @@ export interface IAlignmentFrameOptions extends IBaseFrameOptions { // https://stackoverflow.com/q/46370222/3481582 export type IFrameOptions = IXYFrameOptions | IAlignmentFrameOptions; -export class FramePropertiesAttributes extends XmlAttributeComponent<{ +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +type FramePropertiesAttributes = { readonly anchorLock?: boolean; readonly dropCap?: (typeof DropCapType)[keyof typeof DropCapType]; readonly width: number; @@ -77,47 +96,71 @@ export class FramePropertiesAttributes extends XmlAttributeComponent<{ readonly rule?: (typeof HeightRule)[keyof typeof HeightRule]; readonly alignmentX?: (typeof HorizontalPositionAlign)[keyof typeof HorizontalPositionAlign]; readonly alignmentY?: (typeof VerticalPositionAlign)[keyof typeof VerticalPositionAlign]; -}> { - protected readonly xmlKeys = { - anchorLock: "w:anchorLock", - dropCap: "w:dropCap", - width: "w:w", - height: "w:h", - x: "w:x", - y: "w:y", - anchorHorizontal: "w:hAnchor", - anchorVertical: "w:vAnchor", - spaceHorizontal: "w:hSpace", - spaceVertical: "w:vSpace", - rule: "w:hRule", - alignmentX: "w:xAlign", - alignmentY: "w:yAlign", - lines: "w:lines", - wrap: "w:wrap", - }; -} +}; -export class FrameProperties extends XmlComponent { - public constructor(options: IFrameOptions) { - super("w:framePr"); - this.root.push( - new FramePropertiesAttributes({ - anchorLock: options.anchorLock, - dropCap: options.dropCap, - width: options.width, - height: options.height, - x: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.x : undefined, - y: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.y : undefined, - anchorHorizontal: options.anchor.horizontal, - anchorVertical: options.anchor.vertical, - spaceHorizontal: options.space?.horizontal, - spaceVertical: options.space?.vertical, - rule: options.rule, - alignmentX: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.x : undefined, - alignmentY: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.y : undefined, - lines: options.lines, - wrap: options.wrap, - }), - ); - } -} +export const createFrameProperties = (options: IFrameOptions): XmlComponent => + new BuilderElement({ + name: "w:framePr", + attributes: { + anchorLock: { + key: "w:anchorLock", + value: options.anchorLock, + }, + dropCap: { + key: "w:dropCap", + value: options.dropCap, + }, + width: { + key: "w:w", + value: options.width, + }, + height: { + key: "w:h", + value: options.height, + }, + x: { + key: "w:x", + value: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.x : undefined, + }, + y: { + key: "w:y", + value: (options as IXYFrameOptions).position ? (options as IXYFrameOptions).position.y : undefined, + }, + anchorHorizontal: { + key: "w:hAnchor", + value: options.anchor.horizontal, + }, + anchorVertical: { + key: "w:vAnchor", + value: options.anchor.vertical, + }, + spaceHorizontal: { + key: "w:hSpace", + value: options.space?.horizontal, + }, + spaceVertical: { + key: "w:vSpace", + value: options.space?.vertical, + }, + rule: { + key: "w:hRule", + value: options.rule, + }, + alignmentX: { + key: "w:xAlign", + value: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.x : undefined, + }, + alignmentY: { + key: "w:yAlign", + value: (options as IAlignmentFrameOptions).alignment ? (options as IAlignmentFrameOptions).alignment.y : undefined, + }, + lines: { + key: "w:lines", + value: options.lines, + }, + wrap: { + key: "w:wrap", + value: options.wrap, + }, + }, + }); diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 6e911cd1451..61ac8f78d0a 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -890,10 +890,7 @@ describe("Paragraph", () => { it("should set frame attribute", () => { const paragraph = new Paragraph({ frame: { - position: { - x: 1000, - y: 3000, - }, + type: "alignment", width: 4000, height: 1000, anchor: { @@ -918,9 +915,7 @@ describe("Paragraph", () => { "w:hAnchor": "margin", "w:vAnchor": "margin", "w:w": 4000, - "w:x": 1000, "w:xAlign": "center", - "w:y": 3000, "w:yAlign": "top", }, }, diff --git a/src/file/paragraph/properties.ts b/src/file/paragraph/properties.ts index c427717115c..a160de93d50 100644 --- a/src/file/paragraph/properties.ts +++ b/src/file/paragraph/properties.ts @@ -13,7 +13,7 @@ import { HeadingLevel, Style } from "./formatting/style"; import { TabStop, TabStopDefinition, TabStopType } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; import { WordWrap } from "./formatting/word-wrap"; -import { FrameProperties, IFrameOptions } from "./frame/frame-properties"; +import { createFrameProperties, IFrameOptions } from "./frame/frame-properties"; import { OutlineLevel } from "./links"; import { IRunOptions, RunProperties } from "."; @@ -116,7 +116,7 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { } if (options.frame) { - this.push(new FrameProperties(options.frame)); + this.push(createFrameProperties(options.frame)); } if (options.widowControl !== undefined) {