diff --git a/.nycrc b/.nycrc index 63dce4516c..c521820b91 100644 --- a/.nycrc +++ b/.nycrc @@ -1,9 +1,9 @@ { "check-coverage": true, - "lines": 98.66, - "functions": 97.06, - "branches": 95.51, - "statements": 98.67, + "lines": 98.67, + "functions": 97.08, + "branches": 95.6, + "statements": 98.68, "include": [ "src/**/*.ts" ], diff --git a/demo/60-text-frame.ts b/demo/60-text-frame.ts new file mode 100644 index 0000000000..0759a4e6c7 --- /dev/null +++ b/demo/60-text-frame.ts @@ -0,0 +1,71 @@ +// Text Frame (Text Box) example +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { Document, FrameAnchorType, HorizontalPositionAlign, Packer, Paragraph, TextRun, VerticalPositionAlign } from "../build"; + +const doc = new Document(); + +doc.addSection({ + properties: {}, + children: [ + new Paragraph({ + frame: { + position: { + x: 1000, + y: 3000, + }, + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + alignment: { + x: HorizontalPositionAlign.CENTER, + y: VerticalPositionAlign.TOP, + }, + }, + border: { + top: { + color: "auto", + space: 1, + value: "single", + size: 6, + }, + bottom: { + color: "auto", + space: 1, + value: "single", + size: 6, + }, + left: { + color: "auto", + space: 1, + value: "single", + size: 6, + }, + right: { + color: "auto", + space: 1, + value: "single", + size: 6, + }, + }, + children: [ + new TextRun("Hello World"), + new TextRun({ + text: "Foo Bar", + bold: true, + }), + new TextRun({ + text: "\tGithub is the best", + bold: true, + }), + ], + }), + ], +}); + +Packer.toBuffer(doc).then((buffer) => { + fs.writeFileSync("My Document.docx", buffer); +}); diff --git a/docs/_coverpage.md b/docs/_coverpage.md index c3db65386d..a2db37dc5f 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -4,7 +4,7 @@ - Simple, declarative API - 50+ usage examples -- Battle tested, mature, 97%+ coverage +- Battle tested, mature, 98%+ coverage [GitHub](https://github.com/dolanmiu/docx) [Get Started](#Welcome) diff --git a/src/file/drawing/floating/align.spec.ts b/src/file/drawing/floating/align.spec.ts index 906aa8732f..b181725709 100644 --- a/src/file/drawing/floating/align.spec.ts +++ b/src/file/drawing/floating/align.spec.ts @@ -1,9 +1,9 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { VerticalPositionAlign } from "file/shared/alignment"; import { Align } from "./align"; -import { VerticalPositionAlign } from "./floating-position"; describe("Align", () => { describe("#constructor()", () => { diff --git a/src/file/drawing/floating/align.ts b/src/file/drawing/floating/align.ts index 2ffa4ac52b..5eba22c6a9 100644 --- a/src/file/drawing/floating/align.ts +++ b/src/file/drawing/floating/align.ts @@ -1,6 +1,6 @@ // http://officeopenxml.com/drwPicFloating-position.php +import { HorizontalPositionAlign, VerticalPositionAlign } from "file/shared/alignment"; import { XmlComponent } from "file/xml-components"; -import { HorizontalPositionAlign, VerticalPositionAlign } from "./floating-position"; export class Align extends XmlComponent { constructor(value: HorizontalPositionAlign | VerticalPositionAlign) { diff --git a/src/file/drawing/floating/floating-position.ts b/src/file/drawing/floating/floating-position.ts index 6c5ad7f332..82652bbf91 100644 --- a/src/file/drawing/floating/floating-position.ts +++ b/src/file/drawing/floating/floating-position.ts @@ -1,5 +1,7 @@ // http://officeopenxml.com/drwPicFloating-position.php // http://officeopenxml.com/drwPicFloating.php +import { HorizontalPositionAlign, VerticalPositionAlign } from "file/shared/alignment"; + import { ITextWrapping } from "../text-wrap"; export enum HorizontalPositionRelativeFrom { @@ -24,22 +26,6 @@ export enum VerticalPositionRelativeFrom { TOP_MARGIN = "topMargin", } -export enum HorizontalPositionAlign { - CENTER = "center", - INSIDE = "inside", - LEFT = "left", - OUTSIDE = "outside", - RIGHT = "right", -} - -export enum VerticalPositionAlign { - BOTTOM = "bottom", - CENTER = "center", - INSIDE = "inside", - OUTSIDE = "outside", - TOP = "top", -} - export interface IHorizontalPositionOptions { readonly relative?: HorizontalPositionRelativeFrom; readonly align?: HorizontalPositionAlign; diff --git a/src/file/drawing/floating/horizontal-position.spec.ts b/src/file/drawing/floating/horizontal-position.spec.ts index 6faafb4f7e..705e637dd5 100644 --- a/src/file/drawing/floating/horizontal-position.spec.ts +++ b/src/file/drawing/floating/horizontal-position.spec.ts @@ -1,8 +1,9 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { HorizontalPositionAlign } from "file/shared/alignment"; -import { HorizontalPositionAlign, HorizontalPositionRelativeFrom } from "./floating-position"; +import { HorizontalPositionRelativeFrom } from "./floating-position"; import { HorizontalPosition } from "./horizontal-position"; describe("HorizontalPosition", () => { diff --git a/src/file/drawing/floating/vertical-position.spec.ts b/src/file/drawing/floating/vertical-position.spec.ts index ff34f762ef..20f4cbe62a 100644 --- a/src/file/drawing/floating/vertical-position.spec.ts +++ b/src/file/drawing/floating/vertical-position.spec.ts @@ -1,8 +1,9 @@ import { expect } from "chai"; import { Formatter } from "export/formatter"; +import { VerticalPositionAlign } from "file/shared/alignment"; -import { VerticalPositionAlign, VerticalPositionRelativeFrom } from "./floating-position"; +import { VerticalPositionRelativeFrom } from "./floating-position"; import { VerticalPosition } from "./vertical-position"; describe("VerticalPosition", () => { diff --git a/src/file/index.ts b/src/file/index.ts index 18f0a595e4..63761d4555 100644 --- a/src/file/index.ts +++ b/src/file/index.ts @@ -14,3 +14,4 @@ export * from "./footer-wrapper"; export * from "./header"; export * from "./footnotes"; export * from "./track-revision"; +export * from "./shared"; diff --git a/src/file/paragraph/frame/frame-properties.spec.ts b/src/file/paragraph/frame/frame-properties.spec.ts new file mode 100644 index 0000000000..d0cccc8c7a --- /dev/null +++ b/src/file/paragraph/frame/frame-properties.spec.ts @@ -0,0 +1,86 @@ +import { expect } from "chai"; + +import { Formatter } from "export/formatter"; +import { HorizontalPositionAlign, VerticalPositionAlign } from "file/shared"; + +import { FrameAnchorType, FrameProperties } 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, + }, + }); + + 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", + }, + }, + }); + }); + + 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, + }, + }); + + 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, + }, + }, + }); + }); + }); +}); diff --git a/src/file/paragraph/frame/frame-properties.ts b/src/file/paragraph/frame/frame-properties.ts new file mode 100644 index 0000000000..13a7e72745 --- /dev/null +++ b/src/file/paragraph/frame/frame-properties.ts @@ -0,0 +1,111 @@ +// http://officeopenxml.com/WPparagraph-textFrames.php +import { HorizontalPositionAlign, VerticalPositionAlign } from "file/shared/alignment"; +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +export enum DropCapType { + NONE = "none", + DROP = "drop", + MARGIN = "margin", +} + +export enum FrameAnchorType { + MARGIN = "margin", + PAGE = "page", + TEXT = "text", +} + +export enum FrameWrap { + AROUND = "around", + AUTO = "auto", + NONE = "none", + NOT_BESIDE = "notBeside", + THROUGH = "through", + TIGHT = "tight", +} + +export interface IFrameOptions { + readonly anchorLock?: boolean; + readonly dropCap?: DropCapType; + readonly width: number; + readonly height: number; + readonly position: { + readonly x: number; + readonly y: number; + }; + readonly wrap?: FrameWrap; + readonly lines?: number; + readonly anchor: { + readonly horizontal: FrameAnchorType; + readonly vertical: FrameAnchorType; + }; + readonly space?: { + readonly horizontal: number; + readonly vertical: number; + }; + readonly rule?: number; + readonly alignment: { + readonly x: HorizontalPositionAlign; + readonly y: VerticalPositionAlign; + }; +} + +export class FramePropertiesAttributes extends XmlAttributeComponent<{ + readonly anchorLock?: boolean; + readonly dropCap?: DropCapType; + readonly width: number; + readonly height: number; + readonly x: number; + readonly y: number; + readonly wrap?: FrameWrap; + readonly lines?: number; + readonly anchorHorizontal?: FrameAnchorType; + readonly anchorVertical?: FrameAnchorType; + readonly spaceHorizontal?: number; + readonly spaceVertical?: number; + readonly rule?: number; + readonly alignmentX?: HorizontalPositionAlign; + readonly alignmentY?: 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 { + 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.position.x, + y: options.position.y, + anchorHorizontal: options.anchor.horizontal, + anchorVertical: options.anchor.vertical, + spaceHorizontal: options.space?.horizontal, + spaceVertical: options.space?.vertical, + rule: options.rule, + alignmentX: options.alignment.x, + alignmentY: options.alignment.y, + lines: options.lines, + wrap: options.wrap, + }), + ); + } +} diff --git a/src/file/paragraph/frame/index.ts b/src/file/paragraph/frame/index.ts new file mode 100644 index 0000000000..92a61d95ef --- /dev/null +++ b/src/file/paragraph/frame/index.ts @@ -0,0 +1 @@ +export * from "./frame-properties"; diff --git a/src/file/paragraph/index.ts b/src/file/paragraph/index.ts index d68c1d7f03..c1b59e4deb 100644 --- a/src/file/paragraph/index.ts +++ b/src/file/paragraph/index.ts @@ -4,3 +4,4 @@ export * from "./properties"; export * from "./run"; export * from "./links"; export * from "./math"; +export * from "./frame"; diff --git a/src/file/paragraph/paragraph.spec.ts b/src/file/paragraph/paragraph.spec.ts index 6b1f3c0fe0..5b2f95ed58 100644 --- a/src/file/paragraph/paragraph.spec.ts +++ b/src/file/paragraph/paragraph.spec.ts @@ -7,8 +7,10 @@ import { EMPTY_OBJECT } from "file/xml-components"; import { IViewWrapper } from "../document-wrapper"; import { File } from "../file"; +import { HorizontalPositionAlign, VerticalPositionAlign } from "../shared"; import { ShadingType } from "../table/shading"; import { AlignmentType, HeadingLevel, LeaderType, PageBreak, TabStopPosition, TabStopType } from "./formatting"; +import { FrameAnchorType } from "./frame"; import { Bookmark, ExternalHyperlink } from "./links"; import { Paragraph } from "./paragraph"; import { TextRun } from "./run"; @@ -859,6 +861,52 @@ describe("Paragraph", () => { }); }); + describe("#frame", () => { + it("should set frame attribute", () => { + const paragraph = new Paragraph({ + frame: { + position: { + x: 1000, + y: 3000, + }, + width: 4000, + height: 1000, + anchor: { + horizontal: FrameAnchorType.MARGIN, + vertical: FrameAnchorType.MARGIN, + }, + alignment: { + x: HorizontalPositionAlign.CENTER, + y: VerticalPositionAlign.TOP, + }, + }, + }); + const tree = new Formatter().format(paragraph); + expect(tree).to.deep.equal({ + "w:p": [ + { + "w:pPr": [ + { + "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", + }, + }, + }, + ], + }, + ], + }); + }); + }); + describe("#prepForXml", () => { it("should set Internal Hyperlink", () => { const paragraph = new Paragraph({ diff --git a/src/file/paragraph/properties.ts b/src/file/paragraph/properties.ts index e8fc29a713..29bcc0613e 100644 --- a/src/file/paragraph/properties.ts +++ b/src/file/paragraph/properties.ts @@ -13,6 +13,7 @@ import { HeadingLevel, Style } from "./formatting/style"; import { LeaderType, TabStop, TabStopPosition, TabStopType } from "./formatting/tab-stop"; import { NumberProperties } from "./formatting/unordered-list"; import { WidowControl } from "./formatting/widow-control"; +import { FrameProperties, IFrameOptions } from "./frame/frame-properties"; import { OutlineLevel } from "./links"; import { Shading } from "./run/formatting"; @@ -55,6 +56,7 @@ export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOp readonly color: string; }; readonly widowControl?: boolean; + readonly frame?: IFrameOptions; } export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { @@ -159,6 +161,10 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent { if (options.widowControl) { this.push(new WidowControl(options.widowControl)); } + + if (options.frame) { + this.push(new FrameProperties(options.frame)); + } } public push(item: XmlComponent): void { diff --git a/src/file/shared/alignment.ts b/src/file/shared/alignment.ts new file mode 100644 index 0000000000..03e395b5fd --- /dev/null +++ b/src/file/shared/alignment.ts @@ -0,0 +1,15 @@ +export enum HorizontalPositionAlign { + CENTER = "center", + INSIDE = "inside", + LEFT = "left", + OUTSIDE = "outside", + RIGHT = "right", +} + +export enum VerticalPositionAlign { + BOTTOM = "bottom", + CENTER = "center", + INSIDE = "inside", + OUTSIDE = "outside", + TOP = "top", +} \ No newline at end of file diff --git a/src/file/shared/index.ts b/src/file/shared/index.ts new file mode 100644 index 0000000000..bc61bbe9ca --- /dev/null +++ b/src/file/shared/index.ts @@ -0,0 +1 @@ +export * from "./alignment";