Compare commits

...

15 Commits
4.4.1 ... 4.7.1

Author SHA1 Message Date
c9fb9a827d Version bump 2019-02-26 21:54:33 +00:00
3a9fa49fbb Fix test 2019-02-26 21:46:19 +00:00
218a08d793 Fix tests 2019-02-26 21:45:55 +00:00
416a239708 Add higher width for grid col to fix merging 2019-02-26 21:38:54 +00:00
8dc590746b Version bump 2019-01-16 00:15:27 +00:00
de5f5c9a77 Add documentation 2019-01-16 00:13:18 +00:00
ca5f6a56a5 Write tests 2019-01-15 21:40:19 +00:00
8f6984580a Add Number of pages element 2019-01-15 02:09:38 +00:00
da0fa86345 Version bump 2019-01-10 02:37:40 +00:00
78b310e1dd Add Add createFirstPageFooter and createEvenPageFooter 2019-01-10 02:36:42 +00:00
4541d7c977 Text wrapping and borders for images 2019-01-10 02:10:20 +00:00
a37ff90bd7 Version bump 2019-01-09 01:53:12 +00:00
9998ddfcc9 Add createEvenPageHeader method 2019-01-09 01:52:20 +00:00
12ed54c9fd Add floating image support and documentation 2019-01-09 01:16:47 +00:00
3a1a21e498 Version bump 2018-12-10 19:27:51 +00:00
29 changed files with 742 additions and 265 deletions

44
demo/demo38.ts Normal file
View File

@ -0,0 +1,44 @@
// Example of how to add images to the document - You can use Buffers, UInt8Arrays or Base64 strings
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
// import { Document, Packer, Paragraph } from "../build";
import { Document, Packer, TextWrappingSide, TextWrappingType } from "../build";
const doc = new Document();
doc.createParagraph(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vehicula nec nulla vitae efficitur. Ut interdum mauris eu ipsum rhoncus, nec pharetra velit placerat. Sed vehicula libero ac urna molestie, id pharetra est pellentesque. Praesent iaculis vehicula fringilla. Duis pretium gravida orci eu vestibulum. Mauris tincidunt ipsum dolor, ut ornare dolor pellentesque id. Integer in nulla gravida, lacinia ante non, commodo ex. Vivamus vulputate nisl id lectus finibus vulputate. Ut et nisl mi. Cras fermentum augue arcu, ac accumsan elit euismod id. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed ac posuere nisi. Pellentesque tincidunt vehicula bibendum. Phasellus eleifend viverra nisl.",
);
doc.createParagraph(
"Proin ac purus faucibus, porttitor magna ut, cursus nisl. Vivamus ante purus, porta accumsan nibh eget, eleifend dignissim odio. Integer sed dictum est, aliquam lacinia justo. Donec ultrices auctor venenatis. Etiam interdum et elit nec elementum. Pellentesque nec viverra mauris. Etiam suscipit leo nec velit fringilla mattis. Pellentesque justo lacus, sodales eu condimentum in, dapibus finibus lacus. Morbi vitae nibh sit amet sem molestie feugiat. In non porttitor enim.",
);
doc.createParagraph(
"Ut eget diam cursus quam accumsan interdum at id ante. Ut mollis mollis arcu, eu scelerisque dui tempus in. Quisque aliquam, augue quis ornare aliquam, ex purus ultrices mauris, ut porta dolor dolor nec justo. Nunc a tempus odio, eu viverra arcu. Suspendisse vitae nibh nec mi pharetra tempus. Mauris ut ullamcorper sapien, et sagittis sapien. Vestibulum in urna metus. In scelerisque, massa id bibendum tempus, quam orci rutrum turpis, a feugiat nisi ligula id metus. Praesent id dictum purus. Proin interdum ipsum nulla.",
);
doc.createImage(fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
floating: {
horizontalPosition: {
offset: 2014400,
},
verticalPosition: {
offset: 2014400,
},
wrap: {
type: TextWrappingType.SQUARE,
side: TextWrappingSide.BOTH_SIDES,
},
margins: {
top: 201440,
bottom: 201440,
},
},
});
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

33
demo/demo39.ts Normal file
View File

@ -0,0 +1,33 @@
// Scaling images
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, PageNumberFormat, TextRun } from "../build";
const doc = new Document(
{},
{
pageNumberStart: 1,
pageNumberFormatType: PageNumberFormat.DECIMAL,
},
);
doc.Header.createParagraph("Foo Bar corp. ")
.addRun(new TextRun("Page Number ").pageNumber())
.addRun(new TextRun(" to ").numberOfTotalPages());
doc.Footer.createParagraph("Foo Bar corp. ")
.center()
.addRun(new TextRun("Page Number: ").pageNumber())
.addRun(new TextRun(" to ").numberOfTotalPages());
doc.createParagraph("Hello World 1").pageBreak();
doc.createParagraph("Hello World 2").pageBreak();
doc.createParagraph("Hello World 3").pageBreak();
doc.createParagraph("Hello World 4").pageBreak();
doc.createParagraph("Hello World 5").pageBreak();
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

48
demo/demo41.ts Normal file
View File

@ -0,0 +1,48 @@
// Multiple cells merging in the same table
// Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs";
import { Document, Packer, Paragraph } from "../build";
const doc = new Document();
const table = doc.createTable(13, 6);
let row = 0;
table.getCell(row, 0).addContent(new Paragraph("0,0"));
table.getCell(row, 1).addContent(new Paragraph("0,1"));
table.getCell(row, 3).addContent(new Paragraph("0,3"));
table.getCell(row, 4).addContent(new Paragraph("0,4"));
table.getRow(row).mergeCells(4, 5);
table.getRow(row).mergeCells(1, 2);
row = 1;
table.getCell(row, 0).addContent(new Paragraph("1,0"));
table.getCell(row, 2).addContent(new Paragraph("1,2"));
table.getCell(row, 4).addContent(new Paragraph("1,4"));
table.getRow(row).mergeCells(4, 5);
table.getRow(row).mergeCells(2, 3);
table.getRow(row).mergeCells(0, 1);
row = 2;
table.getCell(row, 0).addContent(new Paragraph("2,0"));
table.getCell(row, 1).addContent(new Paragraph("2,1"));
table.getCell(row, 2).addContent(new Paragraph("2,2"));
table.getCell(row, 3).addContent(new Paragraph("2,3"));
table.getCell(row, 4).addContent(new Paragraph("2,4"));
table.getRow(row).mergeCells(4, 5);
table.getRow(row).mergeCells(1, 2);
row = 3;
table.getCell(row, 0).addContent(new Paragraph("3,0"));
table.getCell(row, 1).addContent(new Paragraph("3,1"));
table.getCell(row, 2).addContent(new Paragraph("3,2"));
table.getCell(row, 3).addContent(new Paragraph("3,3"));
table.getCell(row, 4).addContent(new Paragraph("3,4"));
table.getCell(row, 5).addContent(new Paragraph("3,5"));
row = 4;
table.getCell(row, 0).addContent(new Paragraph("4,0"));
table.getCell(row, 5).addContent(new Paragraph("4,5"));
table.getRow(row).mergeCells(0, 4);
const packer = new Packer();
packer.toBuffer(doc).then((buffer) => {
fs.writeFileSync("My Document.docx", buffer);
});

View File

@ -1,7 +1,8 @@
// Example of how to add images to the document - You can use Buffers, UInt8Arrays or Base64 strings // Example of how to add images to the document - You can use Buffers, UInt8Arrays or Base64 strings
// Import from 'docx' rather than '../build' if you install from npm // Import from 'docx' rather than '../build' if you install from npm
import * as fs from "fs"; import * as fs from "fs";
import { Document, Packer, Paragraph } from "../build"; // import { Document, Packer, Paragraph } from "../build";
import { Document, HorizontalPositionAlign, HorizontalPositionRelativeFrom, Packer, Paragraph, VerticalPositionAlign, VerticalPositionRelativeFrom} from "../build";
const doc = new Document(); const doc = new Document();
@ -13,6 +14,29 @@ doc.createImage(fs.readFileSync("./demo/images/dog.png").toString("base64"));
doc.createImage(fs.readFileSync("./demo/images/cat.jpg")); doc.createImage(fs.readFileSync("./demo/images/cat.jpg"));
doc.createImage(fs.readFileSync("./demo/images/parrots.bmp")); doc.createImage(fs.readFileSync("./demo/images/parrots.bmp"));
doc.createImage(fs.readFileSync("./demo/images/pizza.gif")); doc.createImage(fs.readFileSync("./demo/images/pizza.gif"));
doc.createImage(fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
floating: {
horizontalPosition: {
offset: 1014400,
},
verticalPosition: {
offset: 1014400,
},
},
});
doc.createImage(fs.readFileSync("./demo/images/cat.jpg"), 200, 200, {
floating: {
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.PAGE,
align: HorizontalPositionAlign.RIGHT,
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.PAGE,
align: VerticalPositionAlign.BOTTOM,
},
},
});
const packer = new Packer(); const packer = new Packer();

View File

@ -17,6 +17,7 @@
* [Numbering](usage/numbering.md) * [Numbering](usage/numbering.md)
* [Tab Stops](usage/tab-stops.md) * [Tab Stops](usage/tab-stops.md)
* [Table of Contents](usage/table-of-contents.md) * [Table of Contents](usage/table-of-contents.md)
* [Page Numbers](usage/page-numbers.md)
* Styling * Styling
* [Styling with JS](usage/styling-with-js.md) * [Styling with JS](usage/styling-with-js.md)
* [Styling with XML](usage/styling-with-xml.md) * [Styling with XML](usage/styling-with-xml.md)

View File

@ -1,156 +1,231 @@
# Images # Images
## Intro To create a `floating` image on top of text:
Adding images is very simple ```ts
doc.createImage(fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
Simply call the `createImage` method:
```js
const image = doc.createImage([BUFFER_OF_YOUR_IMAGE]);
```
`docx` supports `jpeg`, `jpg`, `bmp`, `gif` and `png`
Check `demo5.js` for an example
## Positioning
Images can be:
* floating position of images
* Wrapped around the text
* Inline
By default, picture are exported as `INLINE` elements.
In Word this is found in:
![Word Image Positiong](https://user-images.githubusercontent.com/34742290/41765548-b0946302-7604-11e8-96f9-166a9f0b8f39.png)
### Usage
The `PictureRun` element support various options to define the positioning of the element in the document.
```js
interface DrawingOptions {
position?: PlacementPosition;
textWrapping?: TextWrapping;
floating?: Floating;
}
```
can be passed when creating `PictureRun()` for example:
```js
const imageData = document.createImage(buffer, 903, 1149);
new docx.PictureRun(imageData, {
position: docx.PlacementPosition.FLOATING,
floating: { floating: {
horizontalPosition: { horizontalPosition: {
relative: HorizontalPositionRelativeFrom.PAGE, offset: 1014400,
align: HorizontalPositionAlign.LEFT,
}, },
verticalPosition: { verticalPosition: {
relative: VerticalPositionRelativeFrom.PAGE, offset: 1014400,
align: VerticalPositionAlign.TOP,
}, },
}, },
}); });
``` ```
So, the first thing is to define the placement position: `INLINE` or `FLOATING`. Inline is the default one so there is no need to pass drawing options for inline. By default with no arguments, its an `inline` image:
When placement position is FLOATING then we can use two options: ```ts
doc.createImage(fs.readFileSync("./demo/images/parrots.bmp"));
### Wrap text
for `drawingOptions.textWrapping` we can define various options. `textWrapping` has the following properties:
```js
interface TextWrapping {
textWrapStyle: TextWrapStyle;
wrapTextOption?: WrapTextOption;
distanceFromText?: Distance;
}
enum TextWrapStyle {
NONE,
SQUARE,
TIGHT,
TOP_AND_BOTTOM,
}
enum WrapTextOption {
BOTH_SIDES = "bothSides",
LEFT = "left",
RIGHT = "right",
LARGEST = "largest",
}
``` ```
### Floating position You can also create images manually and add them later:
When we want to position the image relative or absolute then we need to use option `drawingOptions.floating`: ```ts
const image = Media.addImage(doc, fs.readFileSync("./demo/images/pizza.gif"));
```js doc.addImage(image);
export interface Floating {
horizontalPosition: HorizontalPositionOptions;
verticalPosition: VerticalPositionOptions;
allowOverlap?: boolean;
lockAnchor?: boolean;
behindDocument?: boolean;
layoutInCell?: boolean;
}
export interface HorizontalPositionOptions {
relative: HorizontalPositionRelativeFrom;
align?: HorizontalPositionAlign;
offset?: number;
}
export interface VerticalPositionOptions {
relative: VerticalPositionRelativeFrom;
align?: VerticalPositionAlign;
offset?: number;
}
export enum HorizontalPositionRelativeFrom {
CHARACTER = "character",
COLUMN = "column",
INSIDE_MARGIN = "insideMargin",
LEFT_MARGIN = "leftMargin",
MARGIN = "margin",
OUTSIDE_MARGIN = "outsideMargin",
PAGE = "page",
RIGHT_MARGIN = "rightMargin",
}
export enum VerticalPositionRelativeFrom {
BOTTOM_MARGIN = "bottomMargin",
INSIDE_MARGIN = "insideMargin",
LINE = "line",
MARGIN = "margin",
OUTSIDE_MARGIN = "outsideMargin",
PAGE = "page",
PARAGRAPH = "paragraph",
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",
}
``` ```
## Intro
Adding images can be done in two ways:
1. Call the `createImage` method to add the image directly into the `document`:
```js
doc.createImage([IMAGE_BUFFER], [WIDTH], [HEIGHT], [POSITION_OPTIONS]);
```
2. Create an `image` first, then add it into the `document`:
```ts
const image = Media.addImage(doc, [IMAGE_BUFFER]);
doc.addImage(image);
```
`docx` supports `jpeg`, `jpg`, `bmp`, `gif` and `png`
## Positioning
> Positioning is the method on how to place the image on the document
![Word Image Positiong](https://user-images.githubusercontent.com/34742290/41765548-b0946302-7604-11e8-96f9-166a9f0b8f39.png)
Three types of image positioning is supported:
- Floating
- Inline
By default, picture are exported as `Inline` elements.
### Usage
Pass `options` into the `[POSITION_OPTIONS]` metioned in the [Intro above](#Intro).
## Floating
To change the position the image to be on top of the text, simply add the `floating` property to the last argument. By default, the offsets are relative to the top left corner of the `page`. Offset units are in [emus](https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/):
```ts
const imageData = document.createImage(buffer, 903, 1149, {
floating: {
horizontalPosition: {
offset: 1014400, // relative: HorizontalPositionRelativeFrom.PAGE by default
},
verticalPosition: {
offset: 1014400, // relative: VerticalPositionRelativeFrom.PAGE by default
},
},
});
```
```ts
const imageData = document.createImage(buffer, 903, 1149, {
floating: {
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.RIGHT_MARGIN,
offset: 1014400,
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.BOTTOM_MARGIN,
offset: 1014400,
},
},
});
```
### Options
Full options you can pass into `floating` are:
| Property | Type | Notes |
| ------------------ | --------------------------- | -------- |
| horizontalPosition | `HorizontalPositionOptions` | Required |
| verticalPosition | `VerticalPositionOptions` | Required |
| allowOverlap | `boolean` | Optional |
| lockAnchor | `boolean` | Optional |
| behindDocument | `boolean` | Optional |
| layoutInCell | `boolean` | Optional |
`HorizontalPositionOptions` are:
| Property | Type | Notes | Possible Values |
| -------- | -------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| relative | `HorizontalPositionRelativeFrom` | Required | `CHARACTER`, `COLUMN`, `INSIDE_MARGIN`, `LEFT_MARGIN`, `MARGIN`, `OUTSIDE_MARGIN`, `PAGE`, `RIGHT_MARGIN` |
| align | `HorizontalPositionAlign` | You can either have `align` or `offset`, not both | `CENTER`, `INSIDE`, `LEFT`, `OUTSIDE`, `RIGHT` |
| offset | `number` | You can either have `align` or `offset`, not both | `0` to `Infinity` |
`VerticalPositionOptions` are:
| Property | Type | Notes | Possible Values |
| -------- | ------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| relative | `VerticalPositionRelativeFrom` | Required | `BOTTOM_MARGIN`, `INSIDE_MARGIN`, `LINE`, `MARGIN`, `OUTSIDE_MARGIN`, `PAGE`, `PARAGRAPH`, `TOP_MARGIN` |
| align | `VerticalPositionAlign` | You can either have `align` or `offset`, not both | `BOTTOM`, `CENTER`, `INSIDE`, `OUTSIDE`, `TOP` |
| offset | `number` | You can either have `align` or `offset`, not both | `0` to `Infinity` |
## Wrap text
Wrapping only works for floating elements. Text will "wrap" around the floating `image`.
Add `wrap` options inside the `floating` options:
```ts
wrap: {
type: [TextWrappingType],
side: [TextWrappingSide],
},
```
For example:
```ts
doc.createImage(fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
floating: {
horizontalPosition: {
offset: 2014400,
},
verticalPosition: {
offset: 2014400,
},
wrap: {
type: TextWrappingType.SQUARE,
side: TextWrappingSide.BOTH_SIDES,
},
},
});
```
Wrap options have the following properties are:
| Property | Type | Notes | Possible Values |
| -------- | ------------------ | -------- | ------------------------------------------- |
| type | `TextWrappingType` | Optional | `NONE`, `SQUARE`, `TIGHT`, `TOP_AND_BOTTOM` |
| side | `TextWrappingSide` | Optional | `BOTH_SIDES`, `LEFT`, `RIGHT`, `LARGEST` |
## Margins
Margins give some space between the text and the image. Margins [only work for floating elements](http://officeopenxml.com/drwPicInline.php). Additionally, the image must also be in wrap mode (see above).
?> Be sure to also set `wrap` in your options!
To use, add the `margins` options inside the `floating` options:
```ts
margins: {
top: number,
bottom: number,
left: number,
right: number
},
```
For example:
```ts
doc.createImage(fs.readFileSync("./demo/images/pizza.gif"), 200, 200, {
floating: {
horizontalPosition: {
offset: 2014400,
},
verticalPosition: {
offset: 2014400,
},
wrap: {
type: TextWrappingType.SQUARE,
side: TextWrappingSide.BOTH_SIDES,
},
margins: {
top: 201440,
bottom: 201440,
},
},
});
```
## Examples
### Add image to the document
Importing Images from file system path
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo5.ts ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo5.ts_
### Add images to header and footer
Example showing how to add image to headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo9.ts ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo9.ts_
### Floating images
Example showing how to float images on top of text and optimally give a `margin`
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo38.ts ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo38.ts_

View File

@ -0,0 +1,66 @@
# Page Numbers
> This feature allows you to set page numbers on each page
?> **Note:** This feature only works on Headers and Footers
```ts
doc.Header.createParagraph().addRun(new TextRun("Page Number: ").pageNumber()).addRun(new TextRun("to ").numberOfTotalPages());
```
## Current page number
To get the current page number, call the `.pageNumber()` method on a `TextRun`. Then add the newly created `TextRun` into a paragraph
```ts
pageNumber();
```
For example:
```ts
const currentPageRun = new TextRun("Current Page Number: ").pageNumber();
paragraph.addRun(currentPageRun);
```
## Total number of pages
```ts
numberOfTotalPages();
```
For example:
```ts
const lastPage = new TextRun("Total Page Number: ").numberOfTotalPages();
paragraph.addRun(lastPage);
```
## Both
You can combine the two to get "Page 2 of 10" effect:
```ts
const currentPageRun = new TextRun("Page ").pageNumber();
const lastPage = new TextRun("of ").numberOfTotalPages();
paragraph.addRun(currentPageRun);
paragraph.addRun(lastPage);
```
Or:
```ts
doc.Header.createParagraph().addRun(new TextRun("Page ").pageNumber()).addRun(new TextRun("of ").numberOfTotalPages());
```
## Examples
### Simple Example
Adding page numbers to Header and Footer
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo39.ts ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo39.ts_

View File

@ -1,6 +1,6 @@
{ {
"name": "docx", "name": "docx",
"version": "4.4.0", "version": "4.7.1",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)", "description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
"main": "build/index.js", "main": "build/index.js",
"scripts": { "scripts": {

View File

@ -78,9 +78,9 @@ describe("Document", () => {
.to.have.property("w:tbl") .to.have.property("w:tbl")
.which.includes({ .which.includes({
"w:tblGrid": [ "w:tblGrid": [
{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 100 } }] },
{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 100 } }] },
{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 100 } }] },
], ],
}); });
expect(body[0]["w:tbl"].filter((x) => x["w:tr"])).to.have.length(2); expect(body[0]["w:tbl"].filter((x) => x["w:tr"])).to.have.length(2);

View File

@ -3,10 +3,10 @@ import { assert } from "chai";
import { Utility } from "tests/utility"; import { Utility } from "tests/utility";
import { IDrawingOptions } from "../drawing"; import { IDrawingOptions } from "../drawing";
import { TextWrapStyle } from "../text-wrap"; import { TextWrappingType } from "../text-wrap";
import { Anchor } from "./anchor"; import { Anchor } from "./anchor";
function createDrawing(drawingOptions: IDrawingOptions): Anchor { function createAnchor(drawingOptions: IDrawingOptions): Anchor {
return new Anchor( return new Anchor(
1, 1,
{ {
@ -28,14 +28,32 @@ describe("Anchor", () => {
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create a Drawing with correct root key", () => { it("should create a Drawing with correct root key", () => {
anchor = createDrawing({}); anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
},
});
const newJson = Utility.jsonify(anchor); const newJson = Utility.jsonify(anchor);
assert.equal(newJson.rootKey, "wp:anchor"); assert.equal(newJson.rootKey, "wp:anchor");
assert.equal(newJson.root.length, 10); assert.equal(newJson.root.length, 10);
}); });
it("should create a Drawing with all default options", () => { it("should create a Drawing with all default options", () => {
anchor = createDrawing({}); anchor = createAnchor({
floating: {
verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
},
});
const newJson = Utility.jsonify(anchor); const newJson = Utility.jsonify(anchor);
assert.equal(newJson.root.length, 10); assert.equal(newJson.root.length, 10);
@ -60,7 +78,7 @@ describe("Anchor", () => {
const horizontalPosition = newJson.root[2]; const horizontalPosition = newJson.root[2];
assert.equal(horizontalPosition.rootKey, "wp:positionH"); assert.equal(horizontalPosition.rootKey, "wp:positionH");
assert.include(horizontalPosition.root[0].root, { assert.include(horizontalPosition.root[0].root, {
relativeFrom: "column", relativeFrom: "page",
}); });
assert.equal(horizontalPosition.root[1].rootKey, "wp:posOffset"); assert.equal(horizontalPosition.root[1].rootKey, "wp:posOffset");
assert.include(horizontalPosition.root[1].root[0], 0); assert.include(horizontalPosition.root[1].root[0], 0);
@ -69,7 +87,7 @@ describe("Anchor", () => {
const verticalPosition = newJson.root[3]; const verticalPosition = newJson.root[3];
assert.equal(verticalPosition.rootKey, "wp:positionV"); assert.equal(verticalPosition.rootKey, "wp:positionV");
assert.include(verticalPosition.root[0].root, { assert.include(verticalPosition.root[0].root, {
relativeFrom: "paragraph", relativeFrom: "page",
}); });
assert.equal(verticalPosition.root[1].rootKey, "wp:posOffset"); assert.equal(verticalPosition.root[1].rootKey, "wp:posOffset");
assert.include(verticalPosition.root[1].root[0], 0); assert.include(verticalPosition.root[1].root[0], 0);
@ -104,9 +122,17 @@ describe("Anchor", () => {
}); });
it("should create a Drawing with square text wrapping", () => { it("should create a Drawing with square text wrapping", () => {
anchor = createDrawing({ anchor = createAnchor({
textWrapping: { floating: {
textWrapStyle: TextWrapStyle.SQUARE, verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
wrap: {
type: TextWrappingType.SQUARE,
},
}, },
}); });
const newJson = Utility.jsonify(anchor); const newJson = Utility.jsonify(anchor);
@ -118,9 +144,17 @@ describe("Anchor", () => {
}); });
it("should create a Drawing with no text wrapping", () => { it("should create a Drawing with no text wrapping", () => {
anchor = createDrawing({ anchor = createAnchor({
textWrapping: { floating: {
textWrapStyle: TextWrapStyle.NONE, verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
wrap: {
type: TextWrappingType.NONE,
},
}, },
}); });
const newJson = Utility.jsonify(anchor); const newJson = Utility.jsonify(anchor);
@ -131,9 +165,17 @@ describe("Anchor", () => {
}); });
it("should create a Drawing with tight text wrapping", () => { it("should create a Drawing with tight text wrapping", () => {
anchor = createDrawing({ anchor = createAnchor({
textWrapping: { floating: {
textWrapStyle: TextWrapStyle.TIGHT, horizontalPosition: {
offset: 0,
},
verticalPosition: {
offset: 0,
},
wrap: {
type: TextWrappingType.TIGHT,
},
}, },
}); });
const newJson = Utility.jsonify(anchor); const newJson = Utility.jsonify(anchor);
@ -144,9 +186,17 @@ describe("Anchor", () => {
}); });
it("should create a Drawing with tight text wrapping", () => { it("should create a Drawing with tight text wrapping", () => {
anchor = createDrawing({ anchor = createAnchor({
textWrapping: { floating: {
textWrapStyle: TextWrapStyle.TOP_AND_BOTTOM, verticalPosition: {
offset: 0,
},
horizontalPosition: {
offset: 0,
},
wrap: {
type: TextWrappingType.TOP_AND_BOTTOM,
},
}, },
}); });
const newJson = Utility.jsonify(anchor); const newJson = Utility.jsonify(anchor);

View File

@ -2,16 +2,9 @@
import { IMediaDataDimensions } from "file/media"; import { IMediaDataDimensions } from "file/media";
import { XmlComponent } from "file/xml-components"; import { XmlComponent } from "file/xml-components";
import { IDrawingOptions } from "../drawing"; import { IDrawingOptions } from "../drawing";
import { import { HorizontalPosition, IFloating, SimplePos, VerticalPosition } from "../floating";
HorizontalPosition,
HorizontalPositionRelativeFrom,
IFloating,
SimplePos,
VerticalPosition,
VerticalPositionRelativeFrom,
} from "../floating";
import { Graphic } from "../inline/graphic"; import { Graphic } from "../inline/graphic";
import { TextWrapStyle, WrapNone, WrapSquare, WrapTight, WrapTopAndBottom } from "../text-wrap"; import { TextWrappingType, WrapNone, WrapSquare, WrapTight, WrapTopAndBottom } from "../text-wrap";
import { DocProperties } from "./../doc-properties/doc-properties"; import { DocProperties } from "./../doc-properties/doc-properties";
import { EffectExtent } from "./../effect-extent/effect-extent"; import { EffectExtent } from "./../effect-extent/effect-extent";
import { Extent } from "./../extent/extent"; import { Extent } from "./../extent/extent";
@ -23,14 +16,8 @@ const defaultOptions: IFloating = {
behindDocument: false, behindDocument: false,
lockAnchor: false, lockAnchor: false,
layoutInCell: true, layoutInCell: true,
verticalPosition: { verticalPosition: {},
relative: VerticalPositionRelativeFrom.PARAGRAPH, horizontalPosition: {},
offset: 0,
},
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.COLUMN,
offset: 0,
},
}; };
export class Anchor extends XmlComponent { export class Anchor extends XmlComponent {
@ -38,15 +25,22 @@ export class Anchor extends XmlComponent {
super("wp:anchor"); super("wp:anchor");
const floating = { const floating = {
margins: {
top: 0,
bottom: 0,
left: 0,
right: 0,
},
...defaultOptions, ...defaultOptions,
...drawingOptions.floating, ...drawingOptions.floating,
}; };
this.root.push( this.root.push(
new AnchorAttributes({ new AnchorAttributes({
distT: 0, distT: floating.margins.top || 0,
distB: 0, distB: floating.margins.bottom || 0,
distL: 0, distL: floating.margins.left || 0,
distR: 0, distR: floating.margins.right || 0,
simplePos: "0", // note: word doesn't fully support - so we use 0 simplePos: "0", // note: word doesn't fully support - so we use 0
allowOverlap: floating.allowOverlap === true ? "1" : "0", allowOverlap: floating.allowOverlap === true ? "1" : "0",
behindDoc: floating.behindDocument === true ? "1" : "0", behindDoc: floating.behindDocument === true ? "1" : "0",
@ -62,18 +56,18 @@ export class Anchor extends XmlComponent {
this.root.push(new Extent(dimensions.emus.x, dimensions.emus.y)); this.root.push(new Extent(dimensions.emus.x, dimensions.emus.y));
this.root.push(new EffectExtent()); this.root.push(new EffectExtent());
if (drawingOptions.textWrapping !== undefined) { if (drawingOptions.floating !== undefined && drawingOptions.floating.wrap !== undefined) {
switch (drawingOptions.textWrapping.textWrapStyle) { switch (drawingOptions.floating.wrap.type) {
case TextWrapStyle.SQUARE: case TextWrappingType.SQUARE:
this.root.push(new WrapSquare(drawingOptions.textWrapping)); this.root.push(new WrapSquare(drawingOptions.floating.wrap, drawingOptions.floating.margins));
break; break;
case TextWrapStyle.TIGHT: case TextWrappingType.TIGHT:
this.root.push(new WrapTight(drawingOptions.textWrapping.distanceFromText)); this.root.push(new WrapTight(drawingOptions.floating.margins));
break; break;
case TextWrapStyle.TOP_AND_BOTTOM: case TextWrappingType.TOP_AND_BOTTOM:
this.root.push(new WrapTopAndBottom(drawingOptions.textWrapping.distanceFromText)); this.root.push(new WrapTopAndBottom(drawingOptions.floating.margins));
break; break;
case TextWrapStyle.NONE: case TextWrappingType.NONE:
default: default:
this.root.push(new WrapNone()); this.root.push(new WrapNone());
} }

View File

@ -2,7 +2,7 @@ import { assert } from "chai";
import { Utility } from "tests/utility"; import { Utility } from "tests/utility";
import { Drawing, IDrawingOptions, PlacementPosition } from "./drawing"; import { Drawing, IDrawingOptions } from "./drawing";
const imageBase64Data = `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`; const imageBase64Data = `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`;
@ -47,7 +47,14 @@ describe("Drawing", () => {
it("should create a drawing with anchor element when there options are passed", () => { it("should create a drawing with anchor element when there options are passed", () => {
currentBreak = createDrawing({ currentBreak = createDrawing({
position: PlacementPosition.FLOATING, floating: {
horizontalPosition: {
offset: 0,
},
verticalPosition: {
offset: 0,
},
},
}); });
const newJson = Utility.jsonify(currentBreak); const newJson = Utility.jsonify(currentBreak);
assert.equal(newJson.root[0].rootKey, "wp:anchor"); assert.equal(newJson.root[0].rootKey, "wp:anchor");

View File

@ -3,12 +3,6 @@ import { XmlComponent } from "file/xml-components";
import { Anchor } from "./anchor"; import { Anchor } from "./anchor";
import { IFloating } from "./floating"; import { IFloating } from "./floating";
import { Inline } from "./inline"; import { Inline } from "./inline";
import { ITextWrapping } from "./text-wrap";
export enum PlacementPosition {
INLINE,
FLOATING,
}
export interface IDistance { export interface IDistance {
readonly distT?: number; readonly distT?: number;
@ -18,35 +12,24 @@ export interface IDistance {
} }
export interface IDrawingOptions { export interface IDrawingOptions {
readonly position?: PlacementPosition;
readonly textWrapping?: ITextWrapping;
readonly floating?: IFloating; readonly floating?: IFloating;
} }
const defaultDrawingOptions: IDrawingOptions = {
position: PlacementPosition.INLINE,
};
export class Drawing extends XmlComponent { export class Drawing extends XmlComponent {
private readonly inline: Inline; private readonly inline: Inline;
constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) { constructor(imageData: IMediaData, drawingOptions: IDrawingOptions = {}) {
super("w:drawing"); super("w:drawing");
if (imageData === undefined) { if (imageData === undefined) {
throw new Error("imageData cannot be undefined"); throw new Error("imageData cannot be undefined");
} }
const mergedOptions = { if (!drawingOptions.floating) {
...defaultDrawingOptions,
...drawingOptions,
};
if (mergedOptions.position === PlacementPosition.INLINE) {
this.inline = new Inline(imageData.referenceId, imageData.dimensions); this.inline = new Inline(imageData.referenceId, imageData.dimensions);
this.root.push(this.inline); this.root.push(this.inline);
} else if (mergedOptions.position === PlacementPosition.FLOATING) { } else {
this.root.push(new Anchor(imageData.referenceId, imageData.dimensions, mergedOptions)); this.root.push(new Anchor(imageData.referenceId, imageData.dimensions, drawingOptions));
} }
} }

View File

@ -1,4 +1,5 @@
// http://officeopenxml.com/drwPicFloating-position.php // http://officeopenxml.com/drwPicFloating-position.php
import { ITextWrapping } from "../text-wrap";
export enum HorizontalPositionRelativeFrom { export enum HorizontalPositionRelativeFrom {
CHARACTER = "character", CHARACTER = "character",
@ -39,17 +40,24 @@ export enum VerticalPositionAlign {
} }
export interface IHorizontalPositionOptions { export interface IHorizontalPositionOptions {
readonly relative: HorizontalPositionRelativeFrom; readonly relative?: HorizontalPositionRelativeFrom;
readonly align?: HorizontalPositionAlign; readonly align?: HorizontalPositionAlign;
readonly offset?: number; readonly offset?: number;
} }
export interface IVerticalPositionOptions { export interface IVerticalPositionOptions {
readonly relative: VerticalPositionRelativeFrom; readonly relative?: VerticalPositionRelativeFrom;
readonly align?: VerticalPositionAlign; readonly align?: VerticalPositionAlign;
readonly offset?: number; readonly offset?: number;
} }
export interface IMargins {
readonly left?: number;
readonly bottom?: number;
readonly top?: number;
readonly right?: number;
}
export interface IFloating { export interface IFloating {
readonly horizontalPosition: IHorizontalPositionOptions; readonly horizontalPosition: IHorizontalPositionOptions;
readonly verticalPosition: IVerticalPositionOptions; readonly verticalPosition: IVerticalPositionOptions;
@ -57,4 +65,6 @@ export interface IFloating {
readonly lockAnchor?: boolean; readonly lockAnchor?: boolean;
readonly behindDocument?: boolean; readonly behindDocument?: boolean;
readonly layoutInCell?: boolean; readonly layoutInCell?: boolean;
readonly margins?: IMargins;
readonly wrap?: ITextWrapping;
} }

View File

@ -20,7 +20,7 @@ export class HorizontalPosition extends XmlComponent {
this.root.push( this.root.push(
new HorizontalPositionAttributes({ new HorizontalPositionAttributes({
relativeFrom: horizontalPosition.relative, relativeFrom: horizontalPosition.relative || HorizontalPositionRelativeFrom.PAGE,
}), }),
); );

View File

@ -20,7 +20,7 @@ export class VerticalPosition extends XmlComponent {
this.root.push( this.root.push(
new VerticalPositionAttributes({ new VerticalPositionAttributes({
relativeFrom: verticalPosition.relative, relativeFrom: verticalPosition.relative || VerticalPositionRelativeFrom.PAGE,
}), }),
); );

View File

@ -1,8 +1,13 @@
import { XmlAttributeComponent } from "file/xml-components"; import { XmlAttributeComponent } from "file/xml-components";
import { IDistance } from "../drawing"; import { IDistance } from "../drawing";
// tslint:disable-next-line:no-empty-interface // distT, distB etc have no effect on inline images, only floating
export interface IInlineAttributes extends IDistance {} export interface IInlineAttributes extends IDistance {
readonly distT?: number;
readonly distB?: number;
readonly distL?: number;
readonly distR?: number;
}
export class InlineAttributes extends XmlAttributeComponent<IInlineAttributes> { export class InlineAttributes extends XmlAttributeComponent<IInlineAttributes> {
protected readonly xmlKeys = { protected readonly xmlKeys = {

View File

@ -1,14 +1,14 @@
// http://officeopenxml.com/drwPicFloating-textWrap.php // http://officeopenxml.com/drwPicFloating-textWrap.php
import { IDistance } from "../drawing"; import { IDistance } from "../drawing";
export enum TextWrapStyle { export enum TextWrappingType {
NONE, NONE,
SQUARE, SQUARE,
TIGHT, TIGHT,
TOP_AND_BOTTOM, TOP_AND_BOTTOM,
} }
export enum WrapTextOption { export enum TextWrappingSide {
BOTH_SIDES = "bothSides", BOTH_SIDES = "bothSides",
LEFT = "left", LEFT = "left",
RIGHT = "right", RIGHT = "right",
@ -16,7 +16,7 @@ export enum WrapTextOption {
} }
export interface ITextWrapping { export interface ITextWrapping {
readonly textWrapStyle: TextWrapStyle; readonly type: TextWrappingType;
readonly wrapTextOption?: WrapTextOption; readonly side?: TextWrappingSide;
readonly distanceFromText?: IDistance; readonly margins?: IDistance;
} }

View File

@ -1,10 +1,15 @@
// http://officeopenxml.com/drwPicFloating-textWrap.php // http://officeopenxml.com/drwPicFloating-textWrap.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { ITextWrapping, WrapTextOption } from "."; import { ITextWrapping, TextWrappingSide } from ".";
import { IDistance } from "../drawing"; import { IDistance } from "../drawing";
import { IMargins } from "../floating";
interface IWrapSquareAttributes extends IDistance { interface IWrapSquareAttributes extends IDistance {
readonly wrapText?: WrapTextOption; readonly wrapText?: TextWrappingSide;
readonly distT?: number;
readonly distB?: number;
readonly distL?: number;
readonly distR?: number;
} }
class WrapSquareAttributes extends XmlAttributeComponent<IWrapSquareAttributes> { class WrapSquareAttributes extends XmlAttributeComponent<IWrapSquareAttributes> {
@ -18,13 +23,24 @@ class WrapSquareAttributes extends XmlAttributeComponent<IWrapSquareAttributes>
} }
export class WrapSquare extends XmlComponent { export class WrapSquare extends XmlComponent {
constructor(textWrapping: ITextWrapping) { constructor(
textWrapping: ITextWrapping,
margins: IMargins = {
top: 0,
bottom: 0,
left: 0,
right: 0,
},
) {
super("wp:wrapSquare"); super("wp:wrapSquare");
this.root.push( this.root.push(
new WrapSquareAttributes({ new WrapSquareAttributes({
wrapText: textWrapping.wrapTextOption || WrapTextOption.BOTH_SIDES, wrapText: textWrapping.side || TextWrappingSide.BOTH_SIDES,
...textWrapping.distanceFromText, distT: margins.top,
distB: margins.bottom,
distL: margins.left,
distR: margins.right,
}), }),
); );
} }

View File

@ -1,6 +1,7 @@
// http://officeopenxml.com/drwPicFloating-textWrap.php // http://officeopenxml.com/drwPicFloating-textWrap.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { IDistance } from "../drawing";
import { IMargins } from "../floating";
interface IWrapTightAttributes { interface IWrapTightAttributes {
readonly distT?: number; readonly distT?: number;
@ -16,17 +17,17 @@ class WrapTightAttributes extends XmlAttributeComponent<IWrapTightAttributes> {
export class WrapTight extends XmlComponent { export class WrapTight extends XmlComponent {
constructor( constructor(
distanceFromText: IDistance = { margins: IMargins = {
distT: 0, top: 0,
distB: 0, bottom: 0,
}, },
) { ) {
super("wp:wrapTight"); super("wp:wrapTight");
this.root.push( this.root.push(
new WrapTightAttributes({ new WrapTightAttributes({
distT: distanceFromText.distT, distT: margins.top,
distB: distanceFromText.distB, distB: margins.bottom,
}), }),
); );
} }

View File

@ -1,6 +1,7 @@
// http://officeopenxml.com/drwPicFloating-textWrap.php // http://officeopenxml.com/drwPicFloating-textWrap.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { IDistance } from "../drawing";
import { IMargins } from "../floating";
interface IWrapTopAndBottomAttributes { interface IWrapTopAndBottomAttributes {
readonly distT?: number; readonly distT?: number;
@ -16,17 +17,17 @@ class WrapTopAndBottomAttributes extends XmlAttributeComponent<IWrapTopAndBottom
export class WrapTopAndBottom extends XmlComponent { export class WrapTopAndBottom extends XmlComponent {
constructor( constructor(
distanceFromText: IDistance = { margins: IMargins = {
distT: 0, top: 0,
distB: 0, bottom: 0,
}, },
) { ) {
super("wp:wrapTopAndBottom"); super("wp:wrapTopAndBottom");
this.root.push( this.root.push(
new WrapTopAndBottomAttributes({ new WrapTopAndBottomAttributes({
distT: distanceFromText.distT, distT: margins.top,
distB: distanceFromText.distB, distB: margins.bottom,
}), }),
); );
} }

View File

@ -3,12 +3,14 @@ import { ContentTypes } from "./content-types/content-types";
import { CoreProperties, IPropertiesOptions } from "./core-properties"; import { CoreProperties, IPropertiesOptions } from "./core-properties";
import { Document } from "./document"; import { Document } from "./document";
import { import {
FooterReference,
FooterReferenceType, FooterReferenceType,
HeaderReference, HeaderReference,
HeaderReferenceType, HeaderReferenceType,
IHeaderFooterGroup, IHeaderFooterGroup,
SectionPropertiesOptions, SectionPropertiesOptions,
} from "./document/body/section-properties"; } from "./document/body/section-properties";
import { IDrawingOptions } from "./drawing";
import { IFileProperties } from "./file-properties"; import { IFileProperties } from "./file-properties";
import { FooterWrapper, IDocumentFooter } from "./footer-wrapper"; import { FooterWrapper, IDocumentFooter } from "./footer-wrapper";
import { FootNotes } from "./footnotes"; import { FootNotes } from "./footnotes";
@ -133,8 +135,13 @@ export class File {
return this; return this;
} }
public createImage(buffer: Buffer | string | Uint8Array | ArrayBuffer, width?: number, height?: number): Image { public createImage(
const image = Media.addImage(this, buffer, width, height); buffer: Buffer | string | Uint8Array | ArrayBuffer,
width?: number,
height?: number,
drawingOptions?: IDrawingOptions,
): Image {
const image = Media.addImage(this, buffer, width, height, drawingOptions);
this.document.addParagraph(image.Paragraph); this.document.addParagraph(image.Paragraph);
return image; return image;
@ -199,6 +206,45 @@ export class File {
return headerWrapper; return headerWrapper;
} }
public createEvenPageHeader(): HeaderWrapper {
const headerWrapper = this.createHeader();
this.document.Body.DefaultSection.addChildElement(
new HeaderReference({
headerType: HeaderReferenceType.EVEN,
headerId: headerWrapper.Header.ReferenceId,
}),
);
return headerWrapper;
}
public createFirstPageFooter(): FooterWrapper {
const footerWrapper = this.createFooter();
this.document.Body.DefaultSection.addChildElement(
new FooterReference({
footerType: FooterReferenceType.FIRST,
footerId: footerWrapper.Footer.ReferenceId,
}),
);
return footerWrapper;
}
public createEvenPageFooter(): FooterWrapper {
const footerWrapper = this.createFooter();
this.document.Body.DefaultSection.addChildElement(
new FooterReference({
footerType: FooterReferenceType.EVEN,
footerId: footerWrapper.Footer.ReferenceId,
}),
);
return footerWrapper;
}
public getFooterByReferenceNumber(refId: number): FooterWrapper { public getFooterByReferenceNumber(refId: number): FooterWrapper {
const entry = this.footers.map((item) => item.footer).find((h) => h.Footer.ReferenceId === refId); const entry = this.footers.map((item) => item.footer).find((h) => h.Footer.ReferenceId === refId);
if (entry) { if (entry) {

View File

@ -25,21 +25,23 @@ describe("Numbering", () => {
{ "w:multiLevelType": [{ _attr: { "w:val": "hybridMultilevel" } }] }, { "w:multiLevelType": [{ _attr: { "w:val": "hybridMultilevel" } }] },
]); ]);
abstractNums.filter((el) => el["w:lvl"]).forEach((el, ix) => { abstractNums
expect(Object.keys(el)).to.have.lengthOf(1); .filter((el) => el["w:lvl"])
expect(Object.keys(el["w:lvl"]).sort()).to.deep.equal(["_attr", "w:start", "w:lvlJc", "w:numFmt", "w:pPr", "w:rPr"]); .forEach((el, ix) => {
expect(el["w:lvl"]).to.have.deep.members([ expect(Object.keys(el)).to.have.lengthOf(1);
{ _attr: { "w:ilvl": ix, "w15:tentative": 1 } }, expect(Object.keys(el["w:lvl"]).sort()).to.deep.equal(["_attr", "w:start", "w:lvlJc", "w:numFmt", "w:pPr", "w:rPr"]);
{ "w:start": [{ _attr: { "w:val": 1 } }] }, expect(el["w:lvl"]).to.have.deep.members([
{ "w:lvlJc": [{ _attr: { "w:val": "left" } }] }, { _attr: { "w:ilvl": ix, "w15:tentative": 1 } },
{ "w:numFmt": [{ _attr: { "w:val": "bullet" } }] }, { "w:start": [{ _attr: { "w:val": 1 } }] },
]); { "w:lvlJc": [{ _attr: { "w:val": "left" } }] },
// Once chai 4.0.0 lands and #644 is resolved, we can add the following to the test: { "w:numFmt": [{ _attr: { "w:val": "bullet" } }] },
// {"w:lvlText": [{"_attr": {"w:val": "•"}}]}, ]);
// {"w:rPr": [{"w:rFonts": [{"_attr": {"w:ascii": "Symbol", "w:cs": "Symbol", "w:eastAsia": "Symbol", "w:hAnsi": "Symbol", "w:hint": "default"}}]}]}, // Once chai 4.0.0 lands and #644 is resolved, we can add the following to the test:
// {"w:pPr": [{"_attr": {}}, // {"w:lvlText": [{"_attr": {"w:val": "•"}}]},
// {"w:ind": [{"_attr": {"w:left": 720, "w:hanging": 360}}]}]}, // {"w:rPr": [{"w:rFonts": [{"_attr": {"w:ascii": "Symbol", "w:cs": "Symbol", "w:eastAsia": "Symbol", "w:hAnsi": "Symbol", "w:hint": "default"}}]}]},
}); // {"w:pPr": [{"_attr": {}},
// {"w:ind": [{"_attr": {"w:left": 720, "w:hanging": 360}}]}]},
});
}); });
}); });

View File

@ -0,0 +1,23 @@
import { expect } from "chai";
import { Formatter } from "export/formatter";
import { NumberOfPages, Page } from "./page-number";
describe("Page", () => {
describe("#constructor()", () => {
it("uses the font name for both ascii and hAnsi", () => {
const tree = new Formatter().format(new Page());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "PAGE"] });
});
});
});
describe("NumberOfPages", () => {
describe("#constructor()", () => {
it("uses the font name for both ascii and hAnsi", () => {
const tree = new Formatter().format(new NumberOfPages());
expect(tree).to.deep.equal({ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "NUMPAGES"] });
});
});
});

View File

@ -12,3 +12,11 @@ export class Page extends XmlComponent {
this.root.push("PAGE"); this.root.push("PAGE");
} }
} }
export class NumberOfPages extends XmlComponent {
constructor() {
super("w:instrText");
this.root.push(new TextAttributes({ space: SpaceType.PRESERVE }));
this.root.push("NUMPAGES");
}
}

View File

@ -156,6 +156,38 @@ describe("Run", () => {
}); });
}); });
describe("#numberOfTotalPages", () => {
it("should set the run to the RTL mode", () => {
run.numberOfTotalPages();
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{ "w:rPr": [] },
{ "w:fldChar": [{ _attr: { "w:fldCharType": "begin" } }] },
{ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "NUMPAGES"] },
{ "w:fldChar": [{ _attr: { "w:fldCharType": "separate" } }] },
{ "w:fldChar": [{ _attr: { "w:fldCharType": "end" } }] },
],
});
});
});
describe("#pageNumber", () => {
it("should set the run to the RTL mode", () => {
run.pageNumber();
const tree = new Formatter().format(run);
expect(tree).to.deep.equal({
"w:r": [
{ "w:rPr": [] },
{ "w:fldChar": [{ _attr: { "w:fldCharType": "begin" } }] },
{ "w:instrText": [{ _attr: { "xml:space": "preserve" } }, "PAGE"] },
{ "w:fldChar": [{ _attr: { "w:fldCharType": "separate" } }] },
{ "w:fldChar": [{ _attr: { "w:fldCharType": "end" } }] },
],
});
});
});
describe("#style", () => { describe("#style", () => {
it("should set the style to the given styleId", () => { it("should set the style to the given styleId", () => {
run.style("myRunStyle"); run.style("myRunStyle");

View File

@ -14,7 +14,7 @@ import {
SizeComplexScript, SizeComplexScript,
Strike, Strike,
} from "./formatting"; } from "./formatting";
import { Page } from "./page-number"; import { NumberOfPages, Page } from "./page-number";
import { RunProperties } from "./properties"; import { RunProperties } from "./properties";
import { RunFonts } from "./run-fonts"; import { RunFonts } from "./run-fonts";
import { SubScript, SuperScript } from "./script"; import { SubScript, SuperScript } from "./script";
@ -84,6 +84,14 @@ export class Run extends XmlComponent {
return this; return this;
} }
public numberOfTotalPages(): Run {
this.root.push(new Begin());
this.root.push(new NumberOfPages());
this.root.push(new Separate());
this.root.push(new End());
return this;
}
public smallCaps(): Run { public smallCaps(): Run {
this.properties.push(new SmallCaps()); this.properties.push(new SmallCaps());
return this; return this;

View File

@ -95,7 +95,7 @@ describe("Table", () => {
"w:tbl": [ "w:tbl": [
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES] }, { "w:tblPr": [DEFAULT_TABLE_PROPERTIES] },
{ {
"w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 1 } }] }], "w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 100 } }] }, { "w:gridCol": [{ _attr: { "w:w": 100 } }] }],
}, },
{ "w:tr": [{ "w:trPr": [] }, cell, cell] }, { "w:tr": [{ "w:trPr": [] }, cell, cell] },
{ "w:tr": [{ "w:trPr": [] }, cell, cell] }, { "w:tr": [{ "w:trPr": [] }, cell, cell] },
@ -137,7 +137,7 @@ describe("Table", () => {
"w:tbl": [ "w:tbl": [
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES] }, { "w:tblPr": [DEFAULT_TABLE_PROPERTIES] },
{ {
"w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 1 } }] }], "w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 100 } }] }, { "w:gridCol": [{ _attr: { "w:w": 100 } }] }],
}, },
{ "w:tr": [{ "w:trPr": [] }, cell("A1"), cell("B1")] }, { "w:tr": [{ "w:trPr": [] }, cell("A1"), cell("B1")] },
{ "w:tr": [{ "w:trPr": [] }, cell("A2"), cell("B2")] }, { "w:tr": [{ "w:trPr": [] }, cell("A2"), cell("B2")] },
@ -166,7 +166,7 @@ describe("Table", () => {
"w:tbl": [ "w:tbl": [
{ "w:tblPr": [DEFAULT_TABLE_PROPERTIES] }, { "w:tblPr": [DEFAULT_TABLE_PROPERTIES] },
{ {
"w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 1 } }] }, { "w:gridCol": [{ _attr: { "w:w": 1 } }] }], "w:tblGrid": [{ "w:gridCol": [{ _attr: { "w:w": 100 } }] }, { "w:gridCol": [{ _attr: { "w:w": 100 } }] }],
}, },
{ "w:tr": [{ "w:trPr": [] }, cell("A1"), cell("B1")] }, { "w:tr": [{ "w:trPr": [] }, cell("A1"), cell("B1")] },
{ "w:tr": [{ "w:trPr": [] }, cell("A2"), cell("B2")] }, { "w:tr": [{ "w:trPr": [] }, cell("A2"), cell("B2")] },

View File

@ -32,7 +32,7 @@ export class Table extends XmlComponent {
table will make it look reasonable, as the layout table will make it look reasonable, as the layout
algorithm will expand columns to fit its content algorithm will expand columns to fit its content
*/ */
gridCols.push(1); gridCols.push(100);
} }
this.grid = new TableGrid(gridCols); this.grid = new TableGrid(gridCols);
} }