Merge branch 'master' into feat/browser-packer

# Conflicts:
#	demo/demo13.js
#	package.json
#	src/export/packer/compiler.ts
#	src/file/media/data.ts
#	src/file/media/media.ts
This commit is contained in:
Dolan
2018-08-10 01:40:29 +01:00
196 changed files with 5665 additions and 407 deletions

4
.gitignore vendored
View File

@ -36,6 +36,10 @@ node_modules
build
build-tests
# Documentation
docs/api/
docs/.nojekyll
# VSCode
.vscode/*
!.vscode/settings.json

View File

@ -1,6 +1,6 @@
language: node_js
node_js:
- "stable"
- 9
install:
- npm install
script:
@ -24,7 +24,8 @@ after_failure:
- "cat /home/travis/builds/dolanmiu/docx/npm-debug.log"
after_success:
- npm run typedoc
- echo "janchi.co.uk" > docs/.nojekyll
- echo "docx.js.org" > docs/.nojekyll
- echo "docx.js.org" > docs/CNAME
deploy:
provider: pages
skip-cleanup: true

View File

@ -3,106 +3,59 @@
</p>
<p align="center">
Generate .docx files with JS/TS very easily, written in TS.
Easily generate .docx files with JS/TS.
</p>
---
[![NPM version][npm-image]][npm-url]
[![Downloads per month][downloads-image]][downloads-url]
[![Build Status][travis-image]][travis-url]
[![Dependency Status][gemnasium-image]][gemnasium-url]
[![Dependency Status][daviddm-image]][daviddm-url]
[![Known Vulnerabilities][snky-image]][snky-url]
[![Chat on Gitter][gitter-image]][gitter-url]
[![code style: prettier][prettier-image]][prettier-url]
[![PRs Welcome][pr-image]][pr-url]
[![NPM](https://nodei.co/npm/docx.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/docx/)
<p align="center">
<img src="https://thumbs.gfycat.com/ComplexEminentEquine-size_restricted.gif" alt="drawing" width="800"/>
</p>
# docx
## Install
```sh
$ npm install --save docx
```
## Demo
# Demo
Press `endpoint` on the `RunKit` website:
![RunKit Instructions](https://user-images.githubusercontent.com/2917613/38582539-f84311b6-3d07-11e8-90db-5885ae02c3c4.png)
* https://runkit.com/dolanmiu/docx-demo1 - Simple paragraph and text
* https://runkit.com/dolanmiu/docx-demo2 - Advanced Paragraphs and text
* https://runkit.com/dolanmiu/docx-demo3 - Bullet points
* https://runkit.com/dolanmiu/docx-demo4 - Simple table
* https://runkit.com/dolanmiu/docx-demo5 - Images
* https://runkit.com/dolanmiu/docx-demo6 - Margins
* https://runkit.com/dolanmiu/docx-demo7 - Landscape
* https://runkit.com/dolanmiu/docx-demo8/1.0.1 - Header and Footer
* https://runkit.com/dolanmiu/docx-demo10 - **My CV generated with docx**
* https://runkit.com/dolanmiu/docx-demo1 - Simple paragraph and text
* https://runkit.com/dolanmiu/docx-demo2 - Advanced Paragraphs and text
* https://runkit.com/dolanmiu/docx-demo3 - Bullet points
* https://runkit.com/dolanmiu/docx-demo4 - Simple table
* https://runkit.com/dolanmiu/docx-demo5 - Images
* https://runkit.com/dolanmiu/docx-demo6 - Margins
* https://runkit.com/dolanmiu/docx-demo7 - Landscape
* https://runkit.com/dolanmiu/docx-demo8/1.0.1 - Header and Footer
* https://runkit.com/dolanmiu/docx-demo10 - **My CV generated with docx**
#### Run demos locally:
# How to use & Documentation
```sh
$ npm run demo
```
Please refer to the [documentation at https://docx.js.org/](https://docx.js.org/) for details on how to use this library, examples and much more!
This command will run the demo selector app in the `demo` folder. It will prompt you to select a demo number, which will run a demo from that folder.
# Examples
## Guide
Please refer to [the Wiki](https://github.com/dolanmiu/docx/wiki) for details on how to use this library, examples and much more!
Full documentation can be found here: [http://dolanmiu.github.io/docx/index.html](http://dolanmiu.github.io/docx/index.html)
## Simple Usage
```js
// Used to create docx files
var docx = require("docx");
// Create document
var doc = new docx.Document();
// Add some content in the document
var paragraph = new docx.Paragraph("Some cool text here.");
// Add more text into the paragraph if you wish
paragraph.addRun(new docx.TextRun("Lorem Ipsum Foo Bar"));
doc.addParagraph(paragraph);
// Used to export the file into a .docx file
var exporter = new docx.LocalPacker(doc);
// Or use the express packer to make the file downloadable.
// res is express' Response object
var exporter = new docx.ExpressPacker(doc, res);
exporter.pack("My First Document");
// If you want to export it as a .pdf file instead
exporter.packPdf("My First Document");
// done! A file called 'My First Document.docx'
// will be in your file system if you used LocalPacker
// Or it will start downloading if you are using Express
```
## Examples
Check [the Wiki](https://github.com/dolanmiu/docx/wiki/Examples) for examples.
Check the `examples` section in the [documentation](https://docx.js.org/#/examples) and the [demo folder](https://github.com/dolanmiu/docx/tree/master/demo) for examples.
# Contributing
Read the contribution guidelines [here](https://github.com/dolanmiu/docx/wiki/Contributing-Guidelines).
Read the contribution guidelines [here](https://docx.js.org/#/contribution-guidelines).
---
Made with 💖
Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contributions to this project
[npm-image]: https://badge.fury.io/js/docx.svg
[npm-url]: https://npmjs.org/package/docx
[downloads-image]: https://img.shields.io/npm/dm/docx.svg
[downloads-url]: https://npmjs.org/package/docx
[travis-image]: https://travis-ci.org/dolanmiu/docx.svg?branch=master
[travis-url]: https://travis-ci.org/dolanmiu/docx
[daviddm-image]: https://david-dm.org/dolanmiu/docx.svg?theme=shields.io
@ -111,9 +64,5 @@ Huge thanks to [@felipeochoa](https://github.com/felipeochoa) for awesome contri
[snky-url]: https://snyk.io/test/github/dolanmiu/docx
[gitter-image]: https://badges.gitter.im/dolanmiu/docx.svg
[gitter-url]: https://gitter.im/docx-lib/Lobby
[gemnasium-image]: https://gemnasium.com/badges/github.com/dolanmiu/docx.svg
[gemnasium-url]: https://gemnasium.com/github.com/dolanmiu/docx
[prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg
[prettier-url]: https://github.com/prettier/prettier
[pr-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
[pr-url]: http://makeapullrequest.com

File diff suppressed because one or more lines are too long

View File

@ -1,17 +1,26 @@
// This example shows 3 styles
const fs = require('fs');
const docx = require('../build');
var doc = new docx.Document();
const styles = fs.readFileSync('./demo/assets/custom-styles.xml', 'utf-8');
const doc = new docx.Document({
title: 'Title',
externalStyles: styles
});
var paragraph = new docx.Paragraph("Hello World");
var institutionText = new docx.TextRun("University College London").bold();
var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
paragraph.addRun(institutionText);
paragraph.addRun(dateText);
doc.createParagraph('Cool Heading Text').heading1();
let paragraph = new docx.Paragraph('This is a custom named style from the template "MyFancyStyle"');
paragraph.style('MyFancyStyle');
doc.addParagraph(paragraph);
doc.createParagraph('Some normal text')
doc.createParagraph('MyFancyStyle again').style('MyFancyStyle');
paragraph.style('MyFancyStyle');
doc.addParagraph(paragraph);
var exporter = new docx.LocalPacker(doc);
exporter.packPdf('My Document').then(() => {
console.log('Document created successfully at project root!');
});
exporter.pack('My Document');
console.log('Document created successfully at project root!');

24
demo/demo14.js Normal file
View File

@ -0,0 +1,24 @@
const docx = require('../build');
var doc = new docx.Document();
doc.createParagraph("First Page").pageBreak()
doc.createParagraph("Second Page");
var pageNumber = new docx.TextRun().pageNumber()
var pageoneheader = new docx.Paragraph("First Page Header ").right();
pageoneheader.addRun(pageNumber);
var firstPageHeader = doc.createFirstPageHeader();
firstPageHeader.addParagraph(pageoneheader);
var pagetwoheader = new docx.Paragraph("My Title ").right();
pagetwoheader.addRun(pageNumber)
doc.Header.addParagraph(pagetwoheader)
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

14
demo/demo15.js Normal file
View File

@ -0,0 +1,14 @@
const docx = require('../build');
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
var paragraph2 = new docx.Paragraph("Hello World on another page").pageBreakBefore();
doc.addParagraph(paragraph);
doc.addParagraph(paragraph2);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

36
demo/demo16.js Normal file
View File

@ -0,0 +1,36 @@
const docx = require("../build");
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World").pageBreak();
doc.addParagraph(paragraph);
var header = doc.createHeader();
header.createParagraph("Header on another page");
var footer = doc.createFooter();
footer.createParagraph("Footer on another page");
doc.addSection({
headerId: header.Header.ReferenceId,
footerId: footer.Footer.ReferenceId,
pageNumberStart: 1,
pageNumberFormatType: docx.PageNumberFormat.DECIMAL,
});
doc.createParagraph("hello");
doc.addSection({
headerId: header.Header.ReferenceId,
footerId: footer.Footer.ReferenceId,
pageNumberStart: 1,
pageNumberFormatType: docx.PageNumberFormat.DECIMAL,
orientation: docx.PageOrientation.LANDSCAPE,
});
doc.createParagraph("hello in landscape");
var exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");
console.log("Document created successfully at project root!");

17
demo/demo17.js Normal file
View File

@ -0,0 +1,17 @@
const docx = require('../build');
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World").referenceFootnote(1);
var paragraph2 = new docx.Paragraph("Hello World").referenceFootnote(2);
doc.addParagraph(paragraph);
doc.addParagraph(paragraph2);
doc.createFootnote(new docx.Paragraph("Test"));
doc.createFootnote(new docx.Paragraph("My amazing reference"));
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

15
demo/demo18.js Normal file
View File

@ -0,0 +1,15 @@
// Insert image from a buffer
const docx = require('../build');
var doc = new docx.Document();
const imageBase64Data = `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`
// doc.createImageFromBuffer(Buffer.from(imageBase64Data, 'base64'));
doc.createImageFromBuffer(Buffer.from(imageBase64Data, 'base64'), 100, 100);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

20
demo/demo19.js Normal file
View File

@ -0,0 +1,20 @@
const fs = require("fs");
const docx = require("../build");
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
var institutionText = new docx.TextRun("Foo").bold();
var dateText = new docx.TextRun("Bar").tab().bold();
paragraph.addRun(institutionText);
paragraph.addRun(dateText);
doc.addParagraph(paragraph);
var exporter = new docx.BufferPacker(doc);
exporter.pack("My Document").then((buffer) => {
// At this point, you can do anything with the buffer, including casting it to a string etc.
console.log(buffer);
fs.writeFileSync('My Document.docx', buffer);
console.log("Document created successfully at project root!");
});

17
demo/demo20.js Normal file
View File

@ -0,0 +1,17 @@
const docx = require("../build");
var doc = new docx.Document();
const table = doc.createTable(4, 4);
table
.getCell(2, 2)
.addContent(new docx.Paragraph("Hello"))
.CellProperties.Borders.addTopBorder(docx.BorderStyle.DASH_DOT_STROKED, 3, "red")
.addBottomBorder(docx.BorderStyle.DOUBLE, 3, "blue")
.addStartBorder(docx.BorderStyle.DOT_DOT_DASH, 3, "green")
.addEndBorder(docx.BorderStyle.DOT_DOT_DASH, 3, "#ff8000");
var exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");
console.log("Document created successfully at project root!");

31
demo/demo21.js Normal file
View File

@ -0,0 +1,31 @@
/** This demo shows how to create bookmarks then link to them with internal hyperlinks */
const docx = require("../build");
const loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam mi velit, convallis convallis scelerisque nec, faucibus nec leo. Phasellus at posuere mauris, tempus dignissim velit. Integer et tortor dolor. Duis auctor efficitur mattis. Vivamus ut metus accumsan tellus auctor sollicitudin venenatis et nibh. Cras quis massa ac metus fringilla venenatis. Proin rutrum mauris purus, ut suscipit magna consectetur id. Integer consectetur sollicitudin ante, vitae faucibus neque efficitur in. Praesent ultricies nibh lectus. Mauris pharetra id odio eget iaculis. Duis dictum, risus id pellentesque rutrum, lorem quam malesuada massa, quis ullamcorper turpis urna a diam. Cras vulputate metus vel massa porta ullamcorper. Etiam porta condimentum nulla nec tristique. Sed nulla urna, pharetra non tortor sed, sollicitudin molestie diam. Maecenas enim leo, feugiat eget vehicula id, sollicitudin vitae ante.";
const doc = new docx.Document({
creator: 'Clippy',
title: 'Sample Document',
description: 'A brief example of using docx with bookmarks and internal hyperlinks',
});
const anchorId = "anchorID";
// First create the bookmark
const bookmark = doc.createBookmark(anchorId, "Lorem Ipsum");
// That has header styling
doc.createParagraph().addBookmark(bookmark).heading1();
doc.createParagraph("\n");
doc.createParagraph(loremIpsum);
doc.createParagraph().pageBreak();
// Now the link back up to the bookmark
const hyperlink = doc.createInternalHyperLink(anchorId, `Click me!`);
doc.createParagraph().addHyperLink(hyperlink);
var exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");
console.log("Document created successfully at project root!");

26
demo/demo22.js Normal file
View File

@ -0,0 +1,26 @@
const docx = require('../build');
var doc = new docx.Document();
var paragraph1 = new docx.Paragraph().bidirectional();
var textRun1 = new docx.TextRun("שלום עולם").rightToLeft();
paragraph1.addRun(textRun1);
doc.addParagraph(paragraph1);
var paragraph2 = new docx.Paragraph().bidirectional();
var textRun2 = new docx.TextRun("שלום עולם").bold().rightToLeft();
paragraph2.addRun(textRun2);
doc.addParagraph(paragraph2);
var paragraph3 = new docx.Paragraph().bidirectional();
var textRun3 = new docx.TextRun("שלום עולם").italic().rightToLeft();
paragraph3.addRun(textRun3);
doc.addParagraph(paragraph3);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

31
demo/demo23.js Normal file
View File

@ -0,0 +1,31 @@
// This demo adds an image to the Media cache, and then insert to the document afterwards
const docx = require("../build");
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
doc.addParagraph(paragraph);
const image = docx.Media.addImage(doc, "./demo/images/image1.jpeg");
const image2 = docx.Media.addImage(doc, "./demo/images/dog.png");
const image3 = docx.Media.addImage(doc, "./demo/images/cat.jpg");
const image4 = docx.Media.addImage(doc, "./demo/images/parrots.bmp");
const image5 = docx.Media.addImage(doc, "./demo/images/pizza.gif");
const imageBase64Data = `iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAACzVBMVEUAAAAAAAAAAAAAAAA/AD8zMzMqKiokJCQfHx8cHBwZGRkuFxcqFSonJyckJCQiIiIfHx8eHh4cHBwoGhomGSYkJCQhISEfHx8eHh4nHR0lHBwkGyQjIyMiIiIgICAfHx8mHh4lHh4kHR0jHCMiGyIhISEgICAfHx8lHx8kHh4jHR0hHCEhISEgICAlHx8kHx8jHh4jHh4iHSIhHCEhISElICAkHx8jHx8jHh4iHh4iHSIhHSElICAkICAjHx8jHx8iHh4iHh4hHiEhHSEkICAjHx8iHx8iHx8hHh4hHiEkHSEjHSAjHx8iHx8iHx8hHh4kHiEkHiEjHSAiHx8hHx8hHh4kHiEjHiAjHSAiHx8iHx8hHx8kHh4jHiEjHiAjHiAiICAiHx8kHx8jHh4jHiEjHiAiHiAiHSAiHx8jHx8jHx8jHiAiHiAiHiAiHSAiHx8jHx8jHx8iHiAiHiAiHiAjHx8jHx8jHx8jHx8iHiAiHiAiHiAjHx8jHx8jHx8iHx8iHSAiHiAjHiAjHx8jHx8hHx8iHx8iHyAiHiAjHiAjHiAjHh4hHx8iHx8iHx8iHyAjHSAjHiAjHiAjHh4hHx8iHx8iHx8jHyAjHiAhHh4iHx8iHx8jHyAjHSAjHSAhHiAhHh4iHx8iHx8jHx8jHyAjHSAjHSAiHh4iHh4jHx8jHx8jHyAjHyAhHSAhHSAiHh4iHh4jHx8jHx8jHyAhHyAhHSAiHSAiHh4jHh4jHx8jHx8jHyAhHyAhHSAiHSAjHR4jHh4jHx8jHx8hHyAhHyAiHSAjHSAjHR4jHh4jHx8hHx8hHyAhHyAiHyAjHSAjHR4jHR4hHh4hHx8hHyAiHyAjHyAjHSAjHR4jHR4hHh4hHx8hHyAjHyAjHyAjHSAjHR4hHR4hHR4hHx8iHyAjHyAjHyAjHSAhHR4hHR4hHR4hHx8jHyAjHyAjHyAjHyC9S2xeAAAA7nRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFxgZGhscHR4fICEiIyQlJicoKSorLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZISUpLTE1OUFFSU1RVVllaW1xdXmBhYmNkZWZnaGprbG1ub3Byc3R1dnd4eXp8fn+AgYKDhIWGiImKi4yNj5CRkpOUlZaXmJmam5ydnp+goaKjpKaoqqusra6vsLGys7S1tri5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+fkZpVQAABcBJREFUGBntwftjlQMcBvDnnLNL22qzJjWlKLHFVogyty3SiFq6EZliqZGyhnSxsLlMRahYoZKRFcul5dKFCatYqWZaNKvWtrPz/A2+7/b27qRzec/lPfvl/XxgMplMJpPJZDKZAtA9HJ3ppnIez0KnSdtC0RCNznHdJrbrh85wdSlVVRaEXuoGamYi5K5430HNiTiEWHKJg05eRWgNfKeV7RxbqUhGKPV/207VupQ8is0IoX5vtFC18SqEHaK4GyHTZ2kzVR8PBTCO4oANIZL4ShNVZcOhKKeYg9DoWdhI1ec3os2VFI0JCIUez5+i6st0qJZRrEAIJCw+QdW223BG/EmKwTBc/IJ/qfp2FDrkUnwFo8U9dZyqnaPhxLqfYjyM1S3vb6p+GGOBszsojoTDSDFz6qj66R4LzvYJxVMwUNRjf1H1ywQr/megg2RzLximy8waqvbda8M5iijegVEiHjlM1W/3h+FcXesphsMY4dMOUnUgOxyuPEzxPQwRNvV3qg5Nj4BreyimwADWe/dRVTMjEm6MoGLzGwtystL6RyOY3qSqdlYU3FpLZw1VW0sK5943MvUCKwJ1noNtjs6Ohge76Zq9ZkfpigU5WWkDYuCfbs1U5HWFR8/Qq4a9W0uK5k4ZmdrTCl8spGIePLPlbqqsc1Afe83O0hULc8alDYiBd7ZyitYMeBfR55rR2fOKP6ioPk2dGvZ+UVI0d8rtqT2tcCexlqK2F3wRn5Q+YVbBqrLKOupkr9lZujAOrmS0UpTb4JeIPkNHZ+cXr6uoPk2vyuBSPhWLEKj45PQJuQWryyqP0Z14uGLdROHIRNBEXDR09EP5r62rOHCazhrD4VKPwxTH+sIA3ZPTJ+YuWV22n+IruHFDC8X2CBjnPoolcGc2FYUwzmsUWXDHsoGKLBhmN0VvuBVfTVE/AAbpaid5CB4MbaLY1QXGuIViLTyZQcVyGGMuxWPwaA0Vk2GI9RRp8Ci2iuLkIBjhT5LNUfAspZFiTwyC72KK7+DNg1SsRvCNp3gZXq2k4iEEXSHFJHgVXUlxejCCbTvFAHiXdIJiXxyCK7KJ5FHoMZGK9xBcwyg2QpdlVMxEUM2iyIMuXXZQNF+HswxMsSAAJRQjoE//eoqDCXBSTO6f1xd+O0iyNRY6jaWi1ALNYCocZROj4JdEikroVkjFk9DcStXxpdfCD2MoXodu4RUU9ptxxmXssOfxnvDVcxRTod9FxyhqLoAqis5aPhwTDp9spRgEH2Q6KLbYoKqlaKTm6Isp0C/sJMnjFvhiERXPQvUNRe9p29lhR04CdBpC8Sl8YiuncIxEuzUUg4Dkgj+paVozygY9plPMh28SaymO9kabAopREGF3vt9MzeFFl8G7lRSZ8FFGK8XX4VA8QjEd7XrM3M0OXz8YCy+qKBLgq3wqnofiTorF0Ax56Rg1J1elW+BBAsVe+My6iYq7IK6keBdOIseV2qn5Pb8f3MqkWAXf9ThM8c8lAOIotuFsF875lRrH5klRcG0+xcPwQ1oLxfeRAP4heQTnGL78X2rqlw2DK59SXAV/zKaiGMAuko5InCt68mcOan5+ohf+z1pP8lQY/GHZQMV4YD3FpXDp4qerqbF/lBWBswyi+AL+ia+maLgcRRQj4IYlY/UpauqKBsPJAxQF8NM1TRQ/RudSPAD34rK3scOuR8/HGcspxsJfOVS8NZbiGXiUtPgINU3v3WFDmx8pEuG3EiqKKVbCC1vm2iZqap5LAtCtleQf8F9sFYWDohzeJczYyQ4V2bEZFGsQgJRGqqqhS2phHTWn9lDkIhBTqWqxQZ+IsRvtdHY9AvI2VX2hW68nfqGmuQsCEl3JdjfCF8OW1bPdtwhQ0gm2mQzfRE3a7KCYj0BNZJs8+Kxf/r6WtTEI2FIqlsMfFgRB5A6KUnSe/vUkX0AnuvUIt8SjM1m6wWQymUwmk8lkMgXRf5vi8rLQxtUhAAAAAElFTkSuQmCC`
const image6 = docx.Media.addImageFromBuffer(doc, Buffer.from(imageBase64Data, 'base64'), 100, 100);
// I am adding an image to the paragraph rather than the document to make the image inline
paragraph.addImage(image5);
doc.addImage(image);
doc.addImage(image2);
doc.addImage(image3);
doc.addImage(image4);
doc.addImage(image5);
doc.addImage(image6);
var exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");
console.log("Document created successfully at project root!");

15
demo/demo24.js Normal file
View File

@ -0,0 +1,15 @@
// Add image to table cell
const docx = require('../build');
var doc = new docx.Document();
const table = doc.createTable(4, 4);
table.getCell(2, 2).addContent(new docx.Paragraph('Hello'));
const image = docx.Media.addImage(doc, "./demo/images/image1.jpeg");
table.getCell(1, 1).addContent(image);
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');

15
demo/demo25.js Normal file
View File

@ -0,0 +1,15 @@
const fs = require("fs");
const docx = require("../build");
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
var institutionText = new docx.TextRun("Foo").bold();
var dateText = new docx.TextRun("Bar").tab().bold();
paragraph.addRun(institutionText);
paragraph.addRun(dateText);
doc.addParagraph(paragraph);
var exporter = new docx.LocalPacker(doc);
exporter.packPdf("My Document");

View File

@ -1,17 +1,17 @@
const docx = require('../build');
const docx = require("../build");
var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World");
doc.addParagraph(paragraph);
const image = doc.createImage("./demo/images/image1.jpeg");
const image2 = doc.createImage("./demo/images/dog.png");
const image3 = doc.createImage("./demo/images/cat.jpg");
const image4 = doc.createImage("./demo/images/parrots.bmp");
const image5 = doc.createImage("./demo/images/pizza.gif");
doc.createImage("./demo/images/image1.jpeg");
doc.createImage("./demo/images/dog.png");
doc.createImage("./demo/images/cat.jpg");
doc.createImage("./demo/images/parrots.bmp");
doc.createImage("./demo/images/pizza.gif");
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
exporter.pack("My Document");
console.log('Document created successfully at project root!');
console.log("Document created successfully at project root!");

View File

@ -10,4 +10,4 @@ doc.Footer.createParagraph("Footer text");
var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document');
console.log('Document created successfully at project root!');
console.log('Document created successfully at project root!');

59
docs/README.md Normal file
View File

@ -0,0 +1,59 @@
<p align="center">
<img alt="clippy the assistant" src="http://i60.tinypic.com/339pvtt.png">
</p>
<p align="center">
Easily generate .docx files with JS/TS. :100:
</p>
---
# Welcome
## Installation
```sh
npm install --save docx
```
Then you can `require` or `import` as usual:
```js
let docx = require("docx");
```
```js
import * as docx from "docx";
```
## Basic Usage
```js
var docx = require("docx");
// Create document
var doc = new docx.Document();
// Add some content in the document
var paragraph = new docx.Paragraph("Some cool text here.");
// Add more text into the paragraph if you wish
paragraph.addRun(new docx.TextRun("Lorem Ipsum Foo Bar"));
doc.addParagraph(paragraph);
// Used to export the file into a .docx file
var exporter = new docx.LocalPacker(doc);
exporter.pack("My First Document");
// Done! A file called 'My First Document.docx' will be in your file system if you used LocalPacker
```
## Honoured Mentions
[@felipeochoa](https://github.com/felipeochoa)
[@h4buli](https://github.com/h4buli)
---
Made with 💖

27
docs/_sidebar.md Normal file
View File

@ -0,0 +1,27 @@
* [Getting Started](/)
* [Examples](examples.md)
* API
* [Documentation](https://docx.js.org/api/)
* Usage
* [Document](usage/document.md)
* [Paragraph](usage/paragraph.md)
* [Text](usage/text.md)
* [Image](usage/images.md)
* [Headers & Footers](usage/headers-and-footers.md)
* [Bullet Points](usage/bullet-points.md)
* [Numbering](usage/numbering.md)
* [Tab Stops](usage/tab-stops.md)
* Styling
* [Styling with JS](usage/styling-with-js.md)
* [Styling with XML](usage/styling-with-xml.md)
* Exporting
* [Packers](usage/packers.md)
* [Contribution Guidelines](contribution-guidelines.md)

View File

@ -0,0 +1,80 @@
# Contribution Guidelines
## Writing Code
* Include documentation reference(s) at the top of each file:
```js
// http://officeopenxml.com/WPdocument.php
```
* Follow Prettier standards, and consider using the [Prettier VSCode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) plugin.
* Follow the `TSLint` rules
## Add vs Create
This is just a guideline, and the rules can sometimes be broken.
* Use `create` if the method `new`'s up an element inside:
```js
public createParagraph() {
const paragraph = new Paragraph();
this.root.push(paragraph);
}
```
* Use `add` if you add the element into the method as a parameter:
```js
public addParagraph(paragraph: Paragraph) {
this.root.push(paragraph);
}
```
## Getters and Setters
Getters and Setters are done with a capital letter like so:
```js
public get Level() {
}
```
There is no performance advantage by doing this. It means we don't need to prefix all private variables with the ugly `_`:
**Do not:**
```js
private get _level: string;
```
**Do**
```js
private get level: string;
```
## Testing
Please write a test of every file you make and suffix it with `.spec.ts`.
Here is a template of a test:
```js
import { assert } from "chai";
describe("ClassName", () => {
beforeEach(() => {
// TODO
});
describe("#methodName()", () => {
it("should ", () => {
// TODO
});
});
});
```

225
docs/examples.md Normal file
View File

@ -0,0 +1,225 @@
# Examples
> All examples can run independently and can be found in the `/demo` folder of the project
All the examples below can be ran locally, to do so, run the following command:
```sh
npm run demo
```
This command will run the `demo selector app` in the `/demo` folder. It will prompt you to select a demo number, which will run a demo from that folder.
## Simple
A simple hello world of the `docx` library:
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo1.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo1.js_
## Styles
### Styling with JS
This example shows how to customise the look and feel of a document using JS configuration
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo2.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo2.js_
### Styling with XML
This example shows how to customise the look and feel of a document using XML configuration
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo13.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo13.js_
## Numbering
This example shows many levels of numbering
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo3.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo3.js_
## Table
Example of simple table
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo4.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo4.js_
### Styling table borders
Styling the borders of a table
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo20.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo20.js_
## Images
### Add image to the document
Importing Images from file system path
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo5.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo5.js_
### 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.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo9.js_
### Scaling images
Example showing how to scale images
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo12.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo12.js_
### Add Image to media before adding to document
This is the best way to add an image to a document because you can add the same image in two locations without increasing document size by re-using the same image
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo23.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo23.js_
### Add image to table
As before, to add an image to a table, you would need to add it to the `Media` object first
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo24.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo24.js_
### Images using Base64 URI
If you want to use a Base64 image instead
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo18.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo18.js_
## Margins
Example showing how to set custom margains
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo6.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo6.js_
## Orientation
Example showing how to set the document to `landscape` or `portrait`
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo7.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo7.js_
## Headers & Footers
Example showing how to add headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo8.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo8.js_
## Multiple headers and footers
Check out `Sections` for this feature
## Page Breaks
### Normal page breaks
Example showing how to page break
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo14.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo14.js_
### Page break before
Example showing how to page break before like in Word
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo15.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo15.js_
## Sections
Example of how sections work. Sections allow multiple headers and footers, and `landscape`/`portrait` inside the same document
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo16.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo16.js_
## Footnotes
Example of how to add footnotes. Good for references
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo17.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo17.js_
## Packers
## Buffer Packer
Example showing how to use the Buffer packer and then write that buffer to the file system
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo19.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo19.js_
## PDF Packing
Example of how to use the `LocalPacker` to create a PDF document
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo25.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo25.js_
## Bookmarks
Example showing how to make bookmarks to make internal hyperlinks within the document
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo21.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo21.js_
## Bidirectional text
Example showing how to use bidirectional text for certain languages such as Hebrew
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo22.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo22.js_
## Showcase
### My CV
Example showing how to add headers and footers
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo10.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo10.js_
### Style and Images
This example shows how to customise the look and feel of a document and add images
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/demo11.js ":include")
_Source: https://github.com/dolanmiu/docx/blob/master/demo/demo11.js_

30
docs/index.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>docx - Generate .docx documents with JavaScript</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Generate .docx documents with JavaScript">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'docx',
repo: 'https://github.com/dolanmiu/docx',
loadSidebar: true,
subMaxLevel: 2,
search: 'auto',
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/emoji.min.js"></script>
<script src="https://unpkg.com/docsify-copy-code@2"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,21 @@
# Bullet Points
## Example
To make a bullet point, simply make a paragraph into a bullet point:
```js
var text = new docx.TextRun("Bullet points");
var paragraph = new docx.Paragraph(text).bullet();
var text2 = new docx.TextRun("Are awesome");
var paragraph2 = new docx.Paragraph(text2).bullet();
doc.addParagraph(paragraph);
doc.addParagraph(paragraph2);
```
### This will produce:
* Bullet points
* Are awesome

35
docs/usage/document.md Normal file
View File

@ -0,0 +1,35 @@
# Document
> The `Document` object is the starting point of your `.docx` journey, this is the literal Word Document. You add all your content such as `Paragraphs` to this `Document`, and at the end export it however you like.
To create a new document, it is very easy:
```js
var doc = new docx.Document();
```
## Document properties
You can add properties to the Word document by specifying options, for example:
```js
var doc = new docx.Document({
creator: "Dolan Miu",
description: "My extremely interesting document",
title: "My Document",
});
```
### Full list of options:
```
creator
description
title
subject
keywords
lastModifiedBy
revision
```
You can mix and match whatever properties you want, or provide no properties.

View File

@ -0,0 +1,47 @@
# Headers and Footers
## Example
Creating Headers and footers is simple. Access the `Header` and `Footer` by doing so like this:
```js
doc.Header;
doc.Footer;
```
You can call the same methods as you would with a `File`:
```js
doc.Header.createParagraph("Header text");
doc.Footer.createParagraph("Footer text");
```
Even add images:
```js
doc.Header.createImage([PATH_TO_YOUR_IMAGE]);
doc.Footer.createImage([PATH_TO_YOUR_IMAGE]);
```
Refer to `demo8.js` for more information
## Multiple Headers and Footers
Also all the supported section properties are implemented according to: http://officeopenxml.com/WPsection.php
### Example
```js
const header = this.document.createHeader();
const footer = this.document.createFooter();
// Add new section with another header and footer
doc.addSection({
headerId: header.Header.ReferenceId,
footerId: footer.Footer.ReferenceId,
pageNumberStart: 1,
pageNumberFormatType: docx.PageNumberFormat.DECIMAL,
});
```

156
docs/usage/images.md Normal file
View File

@ -0,0 +1,156 @@
# Images
## Intro
Adding images is very simple
Simply call the `createImage` method:
```js
const image = doc.createImage([PATH_TO_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.createImageData(filename, buffer, 903, 1149);
new docx.PictureRun(imageData, {
position: docx.PlacementPosition.FLOATING,
floating: {
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.PAGE,
align: HorizontalPositionAlign.LEFT,
},
verticalPosition: {
relative: VerticalPositionRelativeFrom.PAGE,
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.
When placement position is FLOATING then we can use two options:
### 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
When we want to position the image relative or absolute then we need to use option `drawingOptions.floating`:
```js
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",
}
```

96
docs/usage/numbering.md Normal file
View File

@ -0,0 +1,96 @@
# Bullets and Numbering
`docx` is quite flexible in its bullets and numbering system, allowing
the user great freedom in how bullets and numbers are to be styled and
displayed. E.g., numbers can be shown using Arabic numerals, roman
numerals, or even ordinal words ("one", "two", "three", ...). The
format also supports re-using bullets/numbering styles throughout the
document, so that different lists using the same style need not
redefine them.
Because of this flexibility, bullets and numbering in DOCX involves a
couple of moving pieces:
1. Document-level bullets/numbering definitions (abstract)
2. Document-level bullets/numbering definitions (concrete)
3. Paragraph-level bullets/numbering selection
## Document-level bullets/numbering definitions (abstract)
Every document contains a set of abstract bullets/numbering
definitions which define the formatting and layout of paragraphs using
those bullets/numbering. An abstract numbering system defines how
bullets/numbers are to be shown for lists, including any sublists that
may be used. Thus each abstract definition includes a series of
_levels_ which form a sequence starting at 0 indicating the top-level
list look and increasing from there to descibe the sublists, then
sub-sublists, etc. Each level includes the following properties:
* **level**: This its 0-based index in the defintion stack
* **numberFormat**: This indicates how the bullet or number should be
generated. Options include `bullet` (meaning don't count), `decimal`
(arabic numerals), `upperRoman`, `lowerRoman`, `hex`, and many
more.
* **levelText**: This is a format string using the output of the
`numberFormat` function and generating a string to insert before
every item in the list. You may use `%1`, `%2`, ... to reference the
numbers from each numbering level before this one. Thus a level
text of `%d)` with a number format of `lowerLetter` would result in
the sequence "a)", "b)", ...
* and a few others, which you can see in the OXML spec section 17.9.6
## Document-level bullets/numbering defintions (concrete)
Concrete definitions are sort of like concrete subclasses of the
abstract defintions. They indicate their parent and are allowed to
override certain level definitions. Thus two lists that differ only in
how sub-sub-lists are to be displayed can share the same abstract
numbering definition and have slightly different concrete definitions.
## Paragraph-level bullets/numbering selection
In order to use a bullets/numbering definition (which must be
concrete), paragraphs need to select it, similar to applying a CSS
class to an element, using both the concrete numbering definition ID
and the level number that the paragraph should be at. Additionally, MS
Word and LibreOffice typically apply a "ListParagraph" style to
paragraphs that are being numbered.
## Using bullets/numbering in `docx`
`docx` includes a pre-defined bullet style which you can add to your
paragraphs using `para.bullets()`. If you require different bullet
styles or numbering of any kind, you'll have to use the
`docx.Numbering` class.
First you need to create a new numbering container class and use it to
create your abstract numbering style, define your levels, and creat
your concreate numbering style:
```js
const numbering = new docx.Numbering();
const abstractNum = numbering.createAbstractNumbering();
abstractNum.createLevel(0, "upperRoman", "%1", "start").addParagraphProperty(new Indent(720, 260));
abstractNum.createLevel(1, "decimal", "%2.", "start").addParagraphProperty(new Indent(1440, 980));
abstractNum.createLevel(2, "lowerLetter", "%3)", "start").addParagraphProperty(new Indent(2160, 1700));
const concrete = numbering.createConcreteNumbering(numberedAbstract);
```
You can then apply your concrete style to paragraphs using their
`#setNumbering` method:
```js
topLevelP.setNumbering(concrete, 0);
subP.setNumbering(concrete, 1);
subSubP.setNumbering(concrete, 2);
```
Finally, you need to let your exporter know about your numbering
styles when you're ready to render the document:
```js
const packer = new Packer(doc, undefined, undefined, numbering);
packer.pack(myOutput);
```

80
docs/usage/packers.md Normal file
View File

@ -0,0 +1,80 @@
# Packers
> Packers are the way in which `docx` turns your code into `.docx` format. It is completely decoupled from the `docx.Document`.
## File System Packer
```js
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.LocalPacker(doc);
exporter.pack("My Document");
// Word Document is in file system
```
## Buffer Packer
```js
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.BufferPacker(doc);
const buffer = exporter.pack();
```
## Stream Packer
Creates a `node` `Readable` stream
```js
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.StreamPacker(doc);
const stream = exporter.pack();
```
## Express Packer
The old express packer is now deprecated and may disappear soon, so you should upgrade.
The reason for this is because it means this project needs to know about and use `express`, which for a Word document generator, does not sound right. Seperation of concerns.
It will still be usable (for now), but it is ill advised.
I used the express exporter in my [website](http://www.dolan.bio).
The recommended way is to use the `StreamPacker` and handle the `express` magic outside of the library:
```js
const docx = require("docx");
const doc = new docx.Document();
const exporter = new docx.StreamPacker(doc);
const stream = exporter.pack();
// Express' response object
res.attachment('yourfile.xlsx');
stream.pipe(res);
```
where `res` is the response object obtained through the Express router. It is that simple. The file will begin downloading in the browser.
## PDF Exporting
You can export your word document as a PDF file like so:
```js
const exporter = new docx.LocalPacker(doc);
exporter.packPdf("My Document");
// Express
const exporter = new docx.ExpressPacker(doc, res);
exporter.packPdf("My Document");
```
## Browser based docx exporting
It is on the bucket list. It has been requested by a few, and work is already on it

113
docs/usage/paragraph.md Normal file
View File

@ -0,0 +1,113 @@
# Paragraph
> Everything (text, images, graphs etc) in OpenXML is organised in paragraphs.
## Example
You can add more text to the paragraph by doing this:
```js
var paragraph = new docx.Paragraph(),
```
```js
var text = new docx.TextRun("Lorem Ipsum Foo Bar");
var paragraph = new docx.Paragraph();
paragraph.addRun(text);
```
```js
var paragraph = new docx.Paragraph("Short hand notation for adding text.");
```
After you create the paragraph, you must add the paragraph into the `document`:
```js
doc.addParagraph(paragraph);
```
## Styles
To create styles, please refer to the styling Wiki: https://github.com/dolanmiu/docx/wiki/Styling
![Word 2013 Styles menu](http://content.gcflearnfree.org/topics/233/style_apply_choose.png "Word 2013 Styles menu")
### Heading1 - Heading5
```js
paragraph.heading1();
paragraph.heading2();
paragraph.heading3();
paragraph.heading4();
paragraph.heading5();
```
### Title
```js
paragraph.title();
```
## Text Alignment
To change the text alignment of a paragraph, for center, left, right or justified:
```js
paragraph.center();
```
```js
paragraph.left();
```
```js
paragraph.right();
```
```js
paragraph.justified();
```
### Example
```js
paragraph.heading1().center();
```
The above will create a `heading 1` which is `centered`.
## Thematic Break
To add a break in the page, simply add `.thematicBreak()` on a paragraph:
```js
var paragraph = new docx.Paragraph("Amazing Heading").heading1().thematicBreak();
```
The above example will create a heading with a page break directly under it.
## Page Break
To move to a new page (insert a page break), simply add `.pageBreak()` on a paragraph:
```js
var paragraph = new docx.Paragraph("Amazing Heading").heading1().pageBreak();
```
The above example will create a heading and start a new page immediately afterwards.
### Page break before:
This option (available in word) will make sure that the paragraph will start on a new page (if it's not already on a new page).
```js
var paragraph = new docx.Paragraph("Hello World on another page").pageBreakBefore();
```
![Page Break Before in Word](https://user-images.githubusercontent.com/34742290/40176503-df3a8398-59db-11e8-8b9c-d719f13aa8b4.png)
Example: https://github.com/dolanmiu/docx/blob/master/demo/demo15.js
## Page break control
Paragraphs have `.keepLines()` and `.keepNext()` methods that allow restricting page breaks within and between paragraphs. See [this Microsoft article](https://support.office.com/en-us/article/Keep-lines-and-paragraphs-together-d72af534-926f-4c4b-830a-abfc2daa3bfa) for more details)

View File

@ -0,0 +1,162 @@
# Styling with JS
## Example
```js
const para = new Paragraph("To whom it may concern:").heading2().center();
const name = new TextRun("Name:")
.bold()
.font("Calibri")
.allCaps();
```
## Available methods
* For run formatting:
* `.bold()`, `.italic()`, `.smallCaps()`, `.allCaps()`, `.strike()`, `.doubleStrike()`, `.subScript()`, `.superScript()`: Set the formatting property to true
* `.underline(style="single", color=null)`: Set the underline style and color
* `.color(color)`: Set the text color, using 6 hex characters for RRGGBB (no leading `#`)
* `.size(halfPts)`: Set the font size, measured in half-points
* `.font(name)`: Set the run's font
* `.style(name)`: Apply a named run style
* For paragraph formatting:
* `.heading1()`, `.heading2()`, `.heading3()`, `.heading4()`, `.heading5()`, `.title()`: apply the appropriate style to the paragraph
* `.left()`, `.center()`, `.right()`, `.justified()`: set the paragraph's alignment
* `.thematicBreak()`, `.pageBreak()`: Insert a thick rule or a page break beneath the paragraph
* `.leftTabStop(position)`: Add a left tab stop (measured in TWIPs from the left)
* `.maxRightTabStop()`: Add a right tab stop at the far right
* `.bullet()`: Use the default bullet style
* `.setNumbering(numbering, indentLevel)`: Use a custom numbering format for the paragraph
* `.style(name)`: Apply a named paragraph style
* `.indent(start, hanging=0)`: Set the paragraph's indent level (in TWIPs)
* `.spacing({before=0, after=0, line=0})`: Set the line and before/after on the paragraph. Before/after is measured in TWIPs, line is measured in 240ths of a line
Paragraph styles have all the run formatting methods, except `style()`, and `.left()`, `.center()`, `.right()`, `.justified()`, `.thematicBreak()`, `.leftTabStop(position)`, `.maxRightTabStop()`, `.indent(start, hanging=0)`, and `.spacing({before=0, after=0, line=0})` methods.
## Detailed guide
There are 4 items in DOCX that can be styled:
* Characters: Attributes that can change within a paragraph. e.g., bold, italics, etc.
* Paragraphs: Attributes like indent, text alignment, line spacing, etc.
* Tables: Border styles, table formats, etc.
* List items: These are the numbers and bullets that are automatically inserted
There are a few different ways of styling this content in DOCX, which somewhat resemble the HTML/CSS approach. In order of greatest to lowest priority:
1. Direct formatting (AKA inline formatting)
2. Centrally defined styles (similar to external CSS)
3. Document defaults (similar to a `*` rule in CSS)
Unlike CSS, less specific rules don't _necessarily_ override parent rules. The rules are a bit wonky, but if you're interested, see the [advanced formatting section](#Advanced formatting).
### Direct formatting (AKA inline formatting)
This is the type of formatting that your uncle uses when he types out documents: _N ... a ... m ... e ... :_ Then he grabs the mouse, highlights _Name:_ and moves over to the **B** for bold. This manner of formatting results in markup that is similar to writing `<span style="bold: true">Name:</span>` if you were typing out HTML. DOCX (the format) allows you to specify this for any of the four types of items. `docx` (the library) only supports this type of formatting for paragraphs and characters, using a _fluent_ api. Thus you could do:
```js
const name = new TextRun("Name:")
.bold()
.font("Calibri")
.allCaps();
```
Or for paragraph formatting:
```js
const para = new Paragraph("To whom it may concern:").heading2().center();
```
### Centrally defined styles (similar to external CSS)
DOCX files contain a styles section separate from the main content, much like how HTML includes CSS files. Unlike CSS, DOCX distinguishes between styles meant for tables (which show up in the table formatting toolbar), styles for lists (which show up under bullets and numbering), and styles for runs and paragraphs, which show up as dropdowns offering standard styles, like "Heading 1", "Caption", or any custom styles defined in that document. <!-- TODO: add pictures of the panes -->. `docx` allows you to define these styles using a fluent interface as well.
There are three parts to using custom styles with `docx`:
1. Create a container object for the style definitions:
```js
const myStyles = new docx.Styles();
```
2. Define your custom styles, similar to the way you would format a paragraph or run
```js
// The first argument is an ID you use to apply the style to paragraphs
// The second argument is a human-friendly name to show in the UI
myStyles
.createParagraphStyle("myWonkyStyle", "My Wonky Style")
.basedOn("Normal")
.next("Normal")
.color("999999")
.italics()
.indent(720) // 720 TWIP === 720 / 20 pt === .5 in
.spacing({ line: 276 }); // 276 / 240 = 1.15x line spacing
myStyles
.createParagraphStyle("Heading2", "Heading 2")
.basedOn("Normal")
.next("Normal")
.quickFormat()
.size(26) // 26 half-points === 13pt font
.bold()
.underline("double", "FF0000")
.spacing({ before: 240, after: 120 }); // TWIP for both
```
3. When you generate your document, make sure to pass the `styles` container to the `Packer`:
```js
const packer = new Packer(doc, myStyles);
packer.pack(myOutStream);
```
**Note**: If you are using the `.headingX` or `.title` methods of paragraphs, you must make sure to define `HeadingX` or `Title` styles for these. Otherwise they'll show up unstyled :(. If you are using the `.bullet` or `.setNumbering` methods, you need to define a `ListParagraph` style or the numbers may not show up.
### Document defaults
Setting document defaults acts like a `*` rule in CSS: it applies to every paragraph and run in the document, but at a low priority level. Other styles affecting this property will override these defaults.
## Advanced formatting
### Style inheritance
Styles may define a `basedOn` attribute that references another style of the same type. In this case, any unspecified properties are inherited from the parent style.
### Interactions between the 4 items
In addition to the 3-layer hierarchy spelled above, there is some interaction between the 4 items that you can style.
For instance numbering styles may also specify some styling for paragraphs (typically indentation and tab stops); paragraphs may specify character formatting (e.g., heading font sizes); etc.
The elements that each style may affect are summarized in the table below. So, e.g., table styles may specify table formatting, paragraph formatting, and character formatting.
| Style type | Table | Paragraph | List item | Characters |
| :---------------- | :---: | :-------: | :-------: | :--------: |
| Document defaults | | X | | X |
| Table | X | X | | X |
| Paragraph | | X | X | X |
| Numbering | | X | X | |
| Character | | | | X |
| Direct formatting | X | X | X | X |
To determine the value of a styling property, you must first identify whether it's a table, paragraph, list item, or character property. E.g., numbering definition is a list item property. Then you need to find the last row in the table for which that property has an "X" and the document has formatting of that type. So if a particular run was in a paragraph whose style specified color as `FF0000`, but it also had a character style specifying color as `00DD00`, then the character style (lower down on the table) would trump, and the character would have color `00DD00`.
### Toggle properties
The following properties are treated in a special manner; they're called toggle properties:
* Bold
* All caps
* Small caps
* Italics
* Single strike-through
* Hidden
* Imprint
* Emboss
* Character outline
* Character shadow
For these properties, the rules state the following conflict resolution in case the property is specified at multiple points for the same item:
* Direct formatting trumps all if specified (either true or false)
* Otherwise, if the property is true in document defaults, the property is set to true
* Otherwise, the property's value is an XOR of its effective table, paragraph, and character values. (So specifying bold `true` on a table style and a paragraph style would result in non-bold text if a paragraph inside the table had that style)

View File

@ -0,0 +1,47 @@
# Styling with XML
## Setup
1. Create a new word document in Microsoft Word
2. Customise the styles on the Ribbon Bar.
For example, modify the `Normal`, `Heading 1`, `Heading 2` like so:
![image](https://user-images.githubusercontent.com/2917613/41195113-65edebfa-6c1f-11e8-97b4-77de2d60044a.png)
![image](https://user-images.githubusercontent.com/2917613/41195126-ca99c36c-6c1f-11e8-9e58-19e5f69b3b87.png)
3. You can even create a totally new `Style`:
![image](https://user-images.githubusercontent.com/2917613/41195135-f0f7862a-6c1f-11e8-8be4-dd6d8fe5be03.png)
![image](https://user-images.githubusercontent.com/2917613/41195139-0ec52130-6c20-11e8-8fae-f6b44b43fdf8.png)
4. Save
5. Re-name the saved `.docx` file to `.zip` and un-zip
6. Find `styles.xml`
![image](https://user-images.githubusercontent.com/2917613/41195178-bb9ba9c4-6c20-11e8-850e-a7a6ada9a2f6.png)
## Usage
Read the styles using `fs`, and put it into the `Document` object in the constructor:
```js
const styles = fs.readFileSync("./styles.xml", "utf-8");
const doc = new docx.Document({
title: "Title",
externalStyles: styles,
});
```
You can use paragraphs, `heading1()`, `heading2()` etc and it will be styled according to your `styles.xml` created earlier. You can even use your new style you made by calling the `style` method:
```js
doc.createParagraph("Cool Heading Text").heading1();
let paragraph = new docx.Paragraph('This is a custom named style from the template "Cool New Style"');
paragraph.style("Cool New Style");
doc.addParagraph(paragraph);
doc.createParagraph("Some normal text");
```
Example: https://github.com/dolanmiu/docx/blob/master/demo/demo13.js

54
docs/usage/tab-stops.md Normal file
View File

@ -0,0 +1,54 @@
# Tab Stops
> Tab stops are useful, if you are unclear of what they are, [here is a link explaining](https://en.wikipedia.org/wiki/Tab_stop). It enables side by side text which is nicely laid out without the need for tables, or constantly pressing space bar.
**Note**: At the moment, the unit of measurement for a tab stop is counter intuitive for a human. It is using OpenXMLs own measuring system. For example, 2268 roughly translates to 3cm. Therefore in the future, I may consider changing it to percentages or even cm.
![Word 2013 Tabs](http://www.teachucomp.com/wp-content/uploads/blog-4-22-2015-UsingTabStopsInWord-1024x577.png "Word 2013 Tab Stops")
Simply call the relevant methods on the paragraph listed below. Then just add a `tab()` method call to a text object. Adding multiple `tabStops` will mean you would have to chain `tab()` until the desired `tabStop` is selected. Example is shown below.
## Example
```js
var paragraph = new docx.Paragraph().maxRightTabStop();
var leftText = new docx.TextRun("Hey everyone").bold();
var rightText = new docx.TextRun("11th November 2015").tab();
paragraph.addRun(leftText);
paragraph.addRun(rightText);
```
The example above will create a left aligned text, and a right aligned text on the same line. The laymans approach to this problem would be to either use text boxes or tables. YUK!
```js
var paragraph = new docx.Paragraph();
paragraph.maxRightTabStop();
paragraph.leftTabStop(1000);
var text = new docx.TextRun("Second tab stop here I come!").tab().tab();
paragraph.addRun(text);
```
The above shows the use of two tab stops, and how to select/use it.
## Left Tab Stop
```js
paragraph.leftTabStop(2268);
```
2268 is the distance from the left side.
## Center Tab Stop
```js
paragraph.centerTabStop(2268);
```
2268 is the distance from the left side.
## Right Tab Stop
```js
paragraph.rightTabStop(2268);
```
2268 is the distance from the left side.
## Max Right Tab Stop
```js
paragraph.maxRightTabStop();
```
This will create a tab stop on the very edge of the right hand side. Handy for right aligning and left aligning text on the same line.

84
docs/usage/text.md Normal file
View File

@ -0,0 +1,84 @@
# Text
Paragraphs need `text run` objects. To create text:
```js
var text = new docx.TextRun("My awesome text here for my university dissertation");
paragraph.addRun(text);
```
Text objects have methods inside which changes the way the text is displayed.
## Typographical Emphasis
More info [here](https://english.stackexchange.com/questions/97081/what-is-the-typography-term-which-refers-to-the-usage-of-bold-italics-and-unde)
### Bold
```js
text.bold();
```
### Italics
```js
text.italic();
```
### Underline
```js
text.underline();
```
### Strike through
```js
text.strike();
```
### Double strike through
```js
text.doubleStrike();
```
### Superscript
```js
text.superScript();
```
### Subscript
```js
text.subScript();
```
### All Capitals
```js
text.allCaps();
```
### Small Capitals
```js
text.smallCaps();
```
## Break
Sometimes you would want to put text underneath another line of text but inside the same paragraph.
```js
text.break();
```
## Chaining
What if you want to create a paragraph which is **_bold_** and **_italic_**?
```js
paragraph.bold().italic();
```

View File

@ -1,6 +1,6 @@
{
"name": "docx",
"version": "3.4.0",
"version": "3.6.0",
"description": "Generate .docx documents with JavaScript (formerly Office-Clippy)",
"main": "build/index.js",
"scripts": {
@ -14,10 +14,15 @@
"webpack": "rimraf ./build && webpack",
"build.web": "webpack --config webpack.web.config.js",
"demo": "npm run build && node ./demo",
"typedoc": "typedoc --out docs/ src/ --module commonjs --target ES6 --disableOutputCheck --excludePrivate --externalPattern \"**/*.spec.ts\"",
"typedoc": "typedoc src/index.ts",
"style": "prettier -l \"src/**/*.ts\"",
"style.fix": "prettier \"src/**/*.ts\" --write",
"fix-types": "node types-absolute-fixer.js"
},
"pre-commit": [
"style",
"lint"
],
"files": [
"src",
"build",
@ -42,11 +47,13 @@
"types": "./build/index.d.ts",
"dependencies": {
"@types/archiver": "^2.1.0",
"@types/bluebird": "3.5.20",
"@types/express": "^4.0.35",
"@types/image-size": "0.0.29",
"@types/jszip": "^3.1.3",
"@types/request-promise": "^4.1.41",
"@types/request-promise": "^4.1.42",
"archiver": "^2.1.1",
"fast-xml-parser": "^3.3.6",
"image-size": "^0.6.2",
"jszip": "^3.1.5",
"request": "^2.83.0",
@ -62,19 +69,23 @@
"devDependencies": {
"@types/chai": "^3.4.35",
"@types/mocha": "^2.2.39",
"@types/sinon": "^4.3.1",
"awesome-typescript-loader": "^3.4.1",
"chai": "^3.5.0",
"glob": "^7.1.2",
"jszip": "^3.1.5",
"mocha": "^3.2.0",
"mocha-webpack": "^1.0.1",
"prettier": "^1.10.2",
"pre-commit": "^1.2.2",
"prettier": "^1.12.1",
"prompt": "^1.0.0",
"replace-in-file": "^3.1.0",
"rimraf": "^2.5.2",
"shelljs": "^0.7.7",
"tslint": "^5.1.0",
"typedoc": "^0.9.0",
"typescript": "2.6.2",
"sinon": "^5.0.7",
"tslint": "^5.11.0",
"typedoc": "^0.11.1",
"typescript": "2.9.2",
"webpack": "^3.10.0"
}
}

View File

@ -2,3 +2,4 @@ export * from "./packer/local";
export * from "./packer/express";
export * from "./packer/packer";
export * from "./packer/stream";
export * from "./packer/buffer";

View File

@ -0,0 +1,28 @@
import { Writable } from "stream";
export class BufferStream extends Writable {
private readonly data: Buffer[];
constructor() {
super();
this.data = [];
}
// tslint:disable-next-line:no-any
public _write(chunk: any, _: string, next: (err?: Error) => void): void {
this.data.push(Buffer.from(chunk));
next();
}
// tslint:disable-next-line:ban-types
public end(cb?: Function): void {
super.end(cb);
this.emit("close");
}
public get Buffer(): Buffer {
return Buffer.concat(this.data);
}
}

View File

@ -0,0 +1,44 @@
/* tslint:disable:typedef space-before-function-paren */
import { assert } from "chai";
import { stub } from "sinon";
import { BufferPacker } from "../../export/packer/buffer";
import { File, Paragraph } from "../../file";
describe("BufferPacker", () => {
let packer: BufferPacker;
beforeEach(() => {
const file = new File({
creator: "Dolan Miu",
revision: "1",
lastModifiedBy: "Dolan Miu",
});
const paragraph = new Paragraph("test text");
const heading = new Paragraph("Hello world").heading1();
file.addParagraph(new Paragraph("title").title());
file.addParagraph(heading);
file.addParagraph(new Paragraph("heading 2").heading2());
file.addParagraph(paragraph);
packer = new BufferPacker(file);
});
describe("#pack()", () => {
it("should create a standard docx file", async function() {
this.timeout(99999999);
const buffer = await packer.pack();
assert.isDefined(buffer);
assert.isTrue(buffer.byteLength > 0);
});
it("should handle exception if it throws any", () => {
// tslint:disable-next-line:no-any
const compiler = stub((packer as any).packer, "compile");
compiler.throwsException();
return packer.pack().catch((error) => {
assert.isDefined(error);
});
});
});
});

View File

@ -0,0 +1,20 @@
import { File } from "../../file";
import { BufferStream } from "./buffer-stream";
import { Compiler } from "./compiler";
import { IPacker } from "./packer";
export class BufferPacker implements IPacker {
private readonly packer: Compiler;
constructor(file: File) {
this.packer = new Compiler(file);
}
public async pack(): Promise<Buffer> {
const stream = new BufferStream();
await this.packer.compile(stream);
return stream.Buffer;
}
}

View File

@ -0,0 +1,76 @@
/* tslint:disable:typedef space-before-function-paren */
import * as fs from "fs";
import * as JSZip from "jszip";
import { expect } from "chai";
import { File } from "../../file";
import { Compiler } from "./compiler";
describe("Compiler", () => {
let compiler: Compiler;
let file: File;
beforeEach(() => {
file = new File();
compiler = new Compiler(file);
});
describe("#compile()", () => {
it("should pack all the content", async function() {
this.timeout(99999999);
const fileName = "build/tests/test.docx";
await compiler.compile(fs.createWriteStream(fileName));
const docxFile = fs.readFileSync(fileName);
const zipFile: JSZip = await JSZip.loadAsync(docxFile);
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(13);
expect(fileNames).to.include("word/document.xml");
expect(fileNames).to.include("word/styles.xml");
expect(fileNames).to.include("docProps/core.xml");
expect(fileNames).to.include("docProps/app.xml");
expect(fileNames).to.include("word/numbering.xml");
expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels");
expect(fileNames).to.include("word/footer1.xml");
expect(fileNames).to.include("word/footnotes.xml");
expect(fileNames).to.include("word/_rels/footer1.xml.rels");
expect(fileNames).to.include("word/_rels/document.xml.rels");
expect(fileNames).to.include("[Content_Types].xml");
expect(fileNames).to.include("_rels/.rels");
});
it("should pack all additional headers and footers", async function() {
file.createFooter();
file.createFooter();
file.createHeader();
file.createHeader();
this.timeout(99999999);
const fileName = "build/tests/test2.docx";
await compiler.compile(fs.createWriteStream(fileName));
const docxFile = fs.readFileSync(fileName);
const zipFile: JSZip = await JSZip.loadAsync(docxFile);
const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(21);
expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels");
expect(fileNames).to.include("word/header2.xml");
expect(fileNames).to.include("word/_rels/header2.xml.rels");
expect(fileNames).to.include("word/header3.xml");
expect(fileNames).to.include("word/_rels/header3.xml.rels");
expect(fileNames).to.include("word/footer1.xml");
expect(fileNames).to.include("word/_rels/footer1.xml.rels");
expect(fileNames).to.include("word/footer2.xml");
expect(fileNames).to.include("word/_rels/footer2.xml.rels");
expect(fileNames).to.include("word/footer3.xml");
expect(fileNames).to.include("word/_rels/footer3.xml.rels");
});
});
});

View File

@ -1,6 +1,5 @@
import * as archiver from "archiver";
import * as express from "express";
import * as fs from "fs";
import { Writable } from "stream";
import * as xml from "xml";
@ -9,9 +8,9 @@ import { Formatter } from "../formatter";
export class Compiler {
protected archive: archiver.Archiver;
private formatter: Formatter;
private readonly formatter: Formatter;
constructor(private file: File) {
constructor(private readonly file: File) {
this.formatter = new Formatter();
this.archive = archiver.create("zip", {});
@ -23,7 +22,7 @@ export class Compiler {
public async compile(output: Writable | express.Response): Promise<void> {
this.archive.pipe(output);
const xmlDocument = xml(this.formatter.format(this.file.Document), true);
const xmlDocument = xml(this.formatter.format(this.file.Document));
const xmlStyles = xml(this.formatter.format(this.file.Styles));
const xmlProperties = xml(this.formatter.format(this.file.CoreProperties), {
declaration: {
@ -34,12 +33,9 @@ export class Compiler {
const xmlNumbering = xml(this.formatter.format(this.file.Numbering));
const xmlRelationships = xml(this.formatter.format(this.file.DocumentRelationships));
const xmlFileRelationships = xml(this.formatter.format(this.file.FileRelationships));
const xmlHeader = xml(this.formatter.format(this.file.Header.Header));
const xmlFooter = xml(this.formatter.format(this.file.Footer.Footer));
const xmlHeaderRelationships = xml(this.formatter.format(this.file.Header.Relationships));
const xmlFooterRelationships = xml(this.formatter.format(this.file.Footer.Relationships));
const xmlContentTypes = xml(this.formatter.format(this.file.ContentTypes));
const xmlAppProperties = xml(this.formatter.format(this.file.AppProperties));
const xmlFootnotes = xml(this.formatter.format(this.file.FootNotes));
this.archive.append(xmlDocument, {
name: "word/document.xml",
@ -61,26 +57,38 @@ export class Compiler {
name: "word/numbering.xml",
});
this.archive.append(xmlHeader, {
name: "word/header1.xml",
});
// headers
for (let i = 0; i < this.file.Headers.length; i++) {
const element = this.file.Headers[i];
this.archive.append(xml(this.formatter.format(element.Header)), {
name: `word/header${i + 1}.xml`,
});
this.archive.append(xmlFooter, {
name: "word/footer1.xml",
this.archive.append(xml(this.formatter.format(element.Relationships)), {
name: `word/_rels/header${i + 1}.xml.rels`,
});
}
// footers
for (let i = 0; i < this.file.Footers.length; i++) {
const element = this.file.Footers[i];
this.archive.append(xml(this.formatter.format(element.Footer)), {
name: `word/footer${i + 1}.xml`,
});
this.archive.append(xml(this.formatter.format(element.Relationships)), {
name: `word/_rels/footer${i + 1}.xml.rels`,
});
}
this.archive.append(xmlFootnotes, {
name: "word/footnotes.xml",
});
this.archive.append(xmlRelationships, {
name: "word/_rels/document.xml.rels",
});
this.archive.append(xmlHeaderRelationships, {
name: "word/_rels/header1.xml.rels",
});
this.archive.append(xmlFooterRelationships, {
name: "word/_rels/footer1.xml.rels",
});
this.archive.append(xmlContentTypes, {
name: "[Content_Types].xml",
});
@ -89,8 +97,8 @@ export class Compiler {
name: "_rels/.rels",
});
for (const data of this.file.Media.array) {
this.archive.append(fs.createReadStream(data.path), {
for (const data of this.file.Media.Array) {
this.archive.append(data.stream, {
name: `word/media/${data.fileName}`,
});
}

View File

@ -0,0 +1,43 @@
// tslint:disable:typedef space-before-function-paren
// tslint:disable:no-empty
// tslint:disable:no-any
import { assert } from "chai";
import { stub } from "sinon";
import { ExpressPacker } from "../../export/packer/express";
import { File, Paragraph } from "../../file";
describe("LocalPacker", () => {
let packer: ExpressPacker;
beforeEach(() => {
const file = new File({
creator: "Dolan Miu",
revision: "1",
lastModifiedBy: "Dolan Miu",
});
const paragraph = new Paragraph("test text");
const heading = new Paragraph("Hello world").heading1();
file.addParagraph(new Paragraph("title").title());
file.addParagraph(heading);
file.addParagraph(new Paragraph("heading 2").heading2());
file.addParagraph(paragraph);
const expressResMock = {
on: () => {},
attachment: () => {},
};
packer = new ExpressPacker(file, expressResMock as any);
});
describe("#pack()", () => {
it("should handle exception if it throws any", () => {
const compiler = stub((packer as any).packer, "compile");
compiler.throwsException();
return packer.pack("build/tests/test").catch((error) => {
assert.isDefined(error);
});
});
});
});

View File

@ -4,6 +4,9 @@ import { File } from "file";
import { Compiler } from "./compiler";
import { IPacker } from "./packer";
/**
* @deprecated ExpressPacker is now deprecated. Please use the StreamPacker instead and pipe that to `express`' `res` object
*/
export class ExpressPacker implements IPacker {
private readonly packer: Compiler;

View File

@ -1,5 +1,7 @@
/* tslint:disable:typedef space-before-function-paren */
import { assert } from "chai";
import * as fs from "fs";
import { stub } from "sinon";
import { LocalPacker } from "../../export/packer/local";
import { File, Paragraph } from "../../file";
@ -29,14 +31,36 @@ describe("LocalPacker", () => {
await packer.pack("build/tests/test");
fs.statSync("build/tests/test.docx");
});
it("should handle exception if it throws any", () => {
// tslint:disable-next-line:no-any
const compiler = stub((packer as any).packer, "compile");
compiler.throwsException();
return packer.pack("build/tests/test").catch((error) => {
assert.isDefined(error);
});
});
});
describe("#packPdf", () => {
it("should create a standard PDF file", async function() {
this.timeout(99999999);
// tslint:disable-next-line:no-any
const pdfConverterConvert = stub((packer as any).pdfConverter, "convert");
pdfConverterConvert.returns("Test PDF Contents");
await packer.packPdf("build/tests/pdf-test");
fs.statSync("build/tests/pdf-test.pdf");
});
it("should handle exception if it throws any", () => {
// tslint:disable-next-line:no-any
const compiler = stub((packer as any).packer, "compile");
compiler.throwsException();
return packer.packPdf("build/tests/pdf-test").catch((error) => {
assert.isDefined(error);
});
});
});
});

View File

@ -20,7 +20,7 @@ export class LocalPacker implements IPacker {
filePath = filePath.replace(/.docx$/, "");
const zip = await this.packer.compile();
const zipData = await zip.generateAsync({ type: "base64" }) as string;
const zipData = (await zip.generateAsync({ type: "base64" })) as string;
await this.writeToFile(`${filePath}.docx`, zipData);
}
@ -32,7 +32,7 @@ export class LocalPacker implements IPacker {
const tempPath = path.join(os.tmpdir(), `${fileName}.docx`);
const zip = await this.packer.compile();
const zipData = await zip.generateAsync({ type: "base64" }) as string;
const zipData = (await zip.generateAsync({ type: "base64" })) as string;
await this.writeToFile(tempPath, zipData);
const text = await this.pdfConverter.convert(tempPath);

View File

@ -1,4 +1,3 @@
import * as fs from "fs";
import * as JSZip from "jszip";
import * as xml from "xml";
@ -26,9 +25,9 @@ interface IXmlifyedFileMapping {
}
export class Compiler {
private formatter: Formatter;
private readonly formatter: Formatter;
constructor(private file: File) {
constructor(private readonly file: File) {
this.formatter = new Formatter();
}
@ -47,8 +46,8 @@ export class Compiler {
zip.file(xmlifiedFile.path, xmlifiedFile.data);
}
for (const data of this.file.Media.array) {
const mediaData = await this.readFile(data.path);
for (const data of this.file.Media.Array) {
const mediaData = data.stream;
zip.file(`word/media/${data.fileName}`, mediaData);
}
@ -112,17 +111,4 @@ export class Compiler {
},
};
}
private readFile(path: string): Promise<Buffer> {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) {
reject();
return;
}
resolve(data);
});
});
}
}

View File

@ -3,4 +3,7 @@ export interface IPacker {
}
// Needed because of: https://github.com/s-panferov/awesome-typescript-loader/issues/432
/**
* @ignore
*/
export const WORKAROUND = "";

View File

@ -1,5 +1,3 @@
/* tslint:disable:object-literal-key-quotes */
// This tslint disable is needed, or it simply won't work
import * as fs from "fs";
import * as request from "request-promise";
@ -11,6 +9,7 @@ export class PdfConvertWrapper {
public convert(filePath: string): request.RequestPromise {
return request.post({
url: "http://mirror1.convertonlinefree.com",
// tslint:disable-next-line:no-null-keyword
encoding: null,
headers: {
"User-Agent":

View File

@ -0,0 +1,139 @@
import { expect } from "chai";
import { Formatter } from "../../export/formatter";
import { ContentTypes } from "./content-types";
describe("ContentTypes", () => {
let contentTypes: ContentTypes;
beforeEach(() => {
contentTypes = new ContentTypes();
});
describe("#constructor()", () => {
it("should create default content types", () => {
const tree = new Formatter().format(contentTypes);
expect(tree["Types"]).to.be.an.instanceof(Array);
expect(tree["Types"][0]).to.deep.equal({ _attr: { xmlns: "http://schemas.openxmlformats.org/package/2006/content-types" } });
expect(tree["Types"][1]).to.deep.equal({ Default: [{ _attr: { ContentType: "image/png", Extension: "png" } }] });
expect(tree["Types"][2]).to.deep.equal({ Default: [{ _attr: { ContentType: "image/jpeg", Extension: "jpeg" } }] });
expect(tree["Types"][3]).to.deep.equal({ Default: [{ _attr: { ContentType: "image/jpeg", Extension: "jpg" } }] });
expect(tree["Types"][4]).to.deep.equal({ Default: [{ _attr: { ContentType: "image/bmp", Extension: "bmp" } }] });
expect(tree["Types"][5]).to.deep.equal({ Default: [{ _attr: { ContentType: "image/gif", Extension: "gif" } }] });
expect(tree["Types"][6]).to.deep.equal({
Default: [{ _attr: { ContentType: "application/vnd.openxmlformats-package.relationships+xml", Extension: "rels" } }],
});
expect(tree["Types"][7]).to.deep.equal({ Default: [{ _attr: { ContentType: "application/xml", Extension: "xml" } }] });
expect(tree["Types"][8]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
PartName: "/word/document.xml",
},
},
],
});
expect(tree["Types"][9]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
PartName: "/word/styles.xml",
},
},
],
});
expect(tree["Types"][10]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-package.core-properties+xml",
PartName: "/docProps/core.xml",
},
},
],
});
expect(tree["Types"][11]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.extended-properties+xml",
PartName: "/docProps/app.xml",
},
},
],
});
expect(tree["Types"][12]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
PartName: "/word/numbering.xml",
},
},
],
});
});
});
describe("#addFooter()", () => {
it("should add footer", () => {
contentTypes.addFooter(101);
contentTypes.addFooter(102);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][14]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
PartName: "/word/footer101.xml",
},
},
],
});
expect(tree["Types"][15]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
PartName: "/word/footer102.xml",
},
},
],
});
});
});
describe("#addHeader()", () => {
it("should add header", () => {
contentTypes.addHeader(201);
contentTypes.addHeader(202);
const tree = new Formatter().format(contentTypes);
expect(tree["Types"][14]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
PartName: "/word/header201.xml",
},
},
],
});
expect(tree["Types"][15]).to.deep.equal({
Override: [
{
_attr: {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
PartName: "/word/header202.xml",
},
},
],
});
});
});
});

View File

@ -24,11 +24,23 @@ export class ContentTypes extends XmlComponent {
this.root.push(
new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", "/word/document.xml"),
);
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", "/word/header1.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", "/word/footer1.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", "/word/styles.xml"));
this.root.push(new Override("application/vnd.openxmlformats-package.core-properties+xml", "/docProps/core.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.extended-properties+xml", "/docProps/app.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml", "/word/numbering.xml"));
this.root.push(new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml", "/word/footnotes.xml"));
}
public addFooter(index: number): void {
this.root.push(
new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml", `/word/footer${index}.xml`),
);
}
public addHeader(index: number): void {
this.root.push(
new Override("application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml", `/word/header${index}.xml`),
);
}
}

View File

@ -10,6 +10,7 @@ export interface IPropertiesOptions {
description?: string;
lastModifiedBy?: string;
revision?: string;
externalStyles?: string;
}
export class CoreProperties extends XmlComponent {

View File

@ -1,39 +1,42 @@
// import { assert } from "chai";
import { expect } from "chai";
// import { Utility } from "../../../tests/utility";
// import { Body } from "./";
import { Formatter } from "../../../export/formatter";
import { Body } from "./body";
describe("Body", () => {
// let body: Body;
let body: Body;
beforeEach(() => {
// body = new Body();
body = new Body();
});
// describe("#constructor()", () => {
// it("should create the Section Properties", () => {
// const newJson = Utility.jsonify(body);
// assert.equal(newJson.root[0].rootKey, "w:sectPr");
// });
describe("#constructor()", () => {
it("should create default section", () => {
const formatted = new Formatter().format(body)["w:body"][0];
expect(formatted)
.to.have.property("w:sectPr")
.and.to.be.an.instanceof(Array);
expect(formatted["w:sectPr"]).to.have.length(7);
});
});
// it("should create the Page Size", () => {
// const newJson = Utility.jsonify(body);
// assert.equal(newJson.root[1].rootKey, "w:pgSz");
// });
describe("addSection", () => {
it("should add section with options", () => {
body.addSection({
width: 10000,
height: 10000,
});
// it("should create the Page Margin", () => {
// const newJson = Utility.jsonify(body);
// assert.equal(newJson.root[2].rootKey, "w:pgMar");
// });
const formatted = new Formatter().format(body)["w:body"];
expect(formatted).to.be.an.instanceof(Array);
const defaultSectionPr = formatted[0]["w:p"][1]["w:pPr"][0]["w:sectPr"];
// it("should create the Columns", () => {
// const newJson = Utility.jsonify(body);
// assert.equal(newJson.root[3].rootKey, "w:cols");
// });
// check that this is the default section and added first in paragraph
expect(defaultSectionPr[0]).to.deep.equal({ "w:pgSz": [{ _attr: { "w:h": 16838, "w:w": 11906, "w:orient": "portrait" } }] });
// it("should create the Document Grid", () => {
// const newJson = Utility.jsonify(body);
// assert.equal(newJson.root[4].rootKey, "w:docGrid");
// });
// });
// check for new section (since it's the last one, it's direct child of body)
const newSection = formatted[1]["w:sectPr"];
expect(newSection[0]).to.deep.equal({ "w:pgSz": [{ _attr: { "w:h": 10000, "w:w": 10000, "w:orient": "portrait" } }] });
});
});
});

View File

@ -1,14 +1,63 @@
import { XmlComponent } from "file/xml-components";
import { SectionProperties, SectionPropertiesOptions } from "./section-properties/section-properties";
import { IXmlableObject, XmlComponent } from "file/xml-components";
import { Paragraph, ParagraphProperties } from "../..";
import { SectionProperties, SectionPropertiesOptions } from "./section-properties";
export class Body extends XmlComponent {
private readonly defaultSection: SectionProperties;
private readonly sections: SectionProperties[] = [];
constructor(sectionPropertiesOptions?: SectionPropertiesOptions) {
super("w:body");
this.root.push(new SectionProperties(sectionPropertiesOptions));
this.defaultSection = new SectionProperties(sectionPropertiesOptions);
this.sections.push(this.defaultSection);
}
/**
* Adds new section properties.
* Note: Previous section is created in paragraph after the current element, and then new section will be added.
* The spec says:
* - section element should be in the last paragraph of the section
* - last section should be direct child of body
* @param section new section
*/
public addSection(section: SectionPropertiesOptions | SectionProperties): void {
const currentSection = this.sections.pop() as SectionProperties;
this.root.push(this.createSectionParagraph(currentSection));
if (section instanceof SectionProperties) {
this.sections.push(section);
} else {
const params = {
...this.defaultSection.Options,
...section,
};
this.sections.push(new SectionProperties(params));
}
}
public prepForXml(): IXmlableObject {
if (this.sections.length === 1) {
this.root.push(this.sections[0]);
} else if (this.sections.length > 1) {
throw new Error("Invalid usage of sections. At the end of the body element there must be ONE section.");
}
return super.prepForXml();
}
public push(component: XmlComponent): void {
this.root.push(component);
}
public get DefaultSection(): SectionProperties {
return this.defaultSection;
}
private createSectionParagraph(section: SectionProperties): Paragraph {
const paragraph = new Paragraph();
const properties = new ParagraphProperties();
properties.addChildElement(section);
paragraph.addChildElement(properties);
return paragraph;
}
}

View File

@ -1 +1,2 @@
export * from "./body";
export * from "./section-properties";

View File

@ -1,5 +1,11 @@
import { XmlAttributeComponent } from "file/xml-components";
export enum FooterReferenceType {
DEFAULT = "default",
FIRST = "first",
EVEN = "even",
}
export interface IFooterReferenceAttributes {
type: string;
id: string;

View File

@ -1,13 +1,19 @@
import { XmlComponent } from "file/xml-components";
import { FooterReferenceAttributes } from "./footer-reference-attributes";
import { FooterReferenceAttributes, FooterReferenceType } from "./footer-reference-attributes";
export interface IFooterOptions {
footerType?: FooterReferenceType;
footerId?: number;
}
export class FooterReference extends XmlComponent {
constructor() {
constructor(options: IFooterOptions) {
super("w:footerReference");
this.root.push(
new FooterReferenceAttributes({
type: "default",
id: `rId${4}`,
type: options.footerType || FooterReferenceType.DEFAULT,
id: `rId${options.footerId}`,
}),
);
}

View File

@ -0,0 +1,2 @@
export * from "./footer-reference";
export * from "./footer-reference-attributes";

View File

@ -1,5 +1,11 @@
import { XmlAttributeComponent } from "file/xml-components";
export enum HeaderReferenceType {
DEFAULT = "default",
FIRST = "first",
EVEN = "even",
}
export interface IHeaderReferenceAttributes {
type: string;
id: string;

View File

@ -1,13 +1,18 @@
import { XmlComponent } from "file/xml-components";
import { HeaderReferenceAttributes } from "./header-reference-attributes";
import { HeaderReferenceAttributes, HeaderReferenceType } from "./header-reference-attributes";
export interface IHeaderOptions {
headerType?: HeaderReferenceType;
headerId?: number;
}
export class HeaderReference extends XmlComponent {
constructor() {
constructor(options: IHeaderOptions) {
super("w:headerReference");
this.root.push(
new HeaderReferenceAttributes({
type: "default",
id: `rId${3}`,
type: options.headerType || HeaderReferenceType.DEFAULT,
id: `rId${options.headerId}`,
}),
);
}

View File

@ -0,0 +1,2 @@
export * from "./header-reference";
export * from "./header-reference-attributes";

View File

@ -0,0 +1,5 @@
export * from "./section-properties";
export * from "./footer-reference";
export * from "./header-reference";
export * from "./page-size";
export * from "./page-number";

View File

@ -0,0 +1 @@
export * from "./page-number";

View File

@ -0,0 +1,41 @@
// http://officeopenxml.com/WPSectionPgNumType.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
export enum PageNumberFormat {
CARDINAL_TEXT = "cardinalText",
DECIMAL = "decimal",
DECIMAL_ENCLOSED_CIRCLE = "decimalEnclosedCircle",
DECIMAL_ENCLOSED_FULL_STOP = "decimalEnclosedFullstop",
DECIMAL_ENCLOSED_PAREN = "decimalEnclosedParen",
DECIMAL_ZERO = "decimalZero",
LOWER_LETTER = "lowerLetter",
LOWER_ROMAN = "lowerRoman",
NONE = "none",
ORDINAL_TEXT = "ordinalText",
UPPER_LETTER = "upperLetter",
UPPER_ROMAN = "upperRoman",
}
export interface IPageNumberTypeAttributes {
pageNumberStart?: number;
pageNumberFormatType?: PageNumberFormat;
}
export class PageNumberTypeAttributes extends XmlAttributeComponent<IPageNumberTypeAttributes> {
protected xmlKeys = {
pageNumberStart: "w:start",
pageNumberFormatType: "w:fmt",
};
}
export class PageNumberType extends XmlComponent {
constructor(start?: number, numberFormat?: PageNumberFormat) {
super("w:pgNumType");
this.root.push(
new PageNumberTypeAttributes({
pageNumberStart: start,
pageNumberFormatType: numberFormat,
}),
);
}
}

View File

@ -0,0 +1,2 @@
export * from "./page-size";
export * from "./page-size-attributes";

View File

@ -1,9 +1,14 @@
import { XmlAttributeComponent } from "file/xml-components";
export enum PageOrientation {
PORTRAIT = "portrait",
LANDSCAPE = "landscape",
}
export interface IPageSizeAttributes {
width?: number;
height?: number;
orientation?: string;
orientation?: PageOrientation;
}
export class PageSizeAttributes extends XmlAttributeComponent<IPageSizeAttributes> {

View File

@ -2,11 +2,12 @@ import { expect } from "chai";
import { Formatter } from "../../../../../export/formatter";
import { PageSize } from "./page-size";
import { PageOrientation } from "./page-size-attributes";
describe("PageSize", () => {
describe("#constructor()", () => {
it("should create page size with portrait", () => {
const properties = new PageSize(100, 200, "portrait");
const properties = new PageSize(100, 200, PageOrientation.PORTRAIT);
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:pgSz"]);
@ -15,7 +16,7 @@ describe("PageSize", () => {
});
it("should create page size with horizontal and invert the lengths", () => {
const properties = new PageSize(100, 200, "landscape");
const properties = new PageSize(100, 200, PageOrientation.LANDSCAPE);
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:pgSz"]);

View File

@ -1,11 +1,11 @@
import { XmlComponent } from "file/xml-components";
import { PageSizeAttributes } from "./page-size-attributes";
import { PageOrientation, PageSizeAttributes } from "./page-size-attributes";
export class PageSize extends XmlComponent {
constructor(width: number, height: number, orientation: string) {
constructor(width: number, height: number, orientation: PageOrientation) {
super("w:pgSz");
const flip = orientation === "landscape";
const flip = orientation === PageOrientation.LANDSCAPE;
this.root.push(
new PageSizeAttributes({

View File

@ -2,6 +2,7 @@ import { expect } from "chai";
import { Formatter } from "../../../../export/formatter";
import { SectionProperties } from "./section-properties";
import { FooterReferenceType, PageNumberFormat } from ".";
describe("SectionProperties", () => {
describe("#constructor()", () => {
@ -18,6 +19,11 @@ describe("SectionProperties", () => {
gutter: 0,
space: 708,
linePitch: 360,
headerId: 100,
footerId: 200,
footerType: FooterReferenceType.EVEN,
pageNumberStart: 10,
pageNumberFormatType: PageNumberFormat.CARDINAL_TEXT,
});
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:sectPr"]);
@ -38,6 +44,12 @@ describe("SectionProperties", () => {
},
],
});
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": [{ _attr: { "w:space": 708 } }] });
expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] });
expect(tree["w:sectPr"][4]).to.deep.equal({ "w:headerReference": [{ _attr: { "r:id": "rId100", "w:type": "default" } }] });
expect(tree["w:sectPr"][5]).to.deep.equal({ "w:footerReference": [{ _attr: { "r:id": "rId200", "w:type": "even" } }] });
expect(tree["w:sectPr"][6]).to.deep.equal({ "w:pgNumType": [{ _attr: { "w:fmt": "cardinalText", "w:start": 10 } }] });
});
it("should create section properties with no options", () => {
@ -61,6 +73,11 @@ describe("SectionProperties", () => {
},
],
});
expect(tree["w:sectPr"][2]).to.deep.equal({ "w:cols": [{ _attr: { "w:space": 708 } }] });
expect(tree["w:sectPr"][3]).to.deep.equal({ "w:docGrid": [{ _attr: { "w:linePitch": 360 } }] });
expect(tree["w:sectPr"][4]).to.deep.equal({ "w:headerReference": [{ _attr: { "r:id": "rId0", "w:type": "default" } }] });
expect(tree["w:sectPr"][5]).to.deep.equal({ "w:footerReference": [{ _attr: { "r:id": "rId0", "w:type": "default" } }] });
expect(tree["w:sectPr"][6]).to.deep.equal({ "w:pgNumType": [{ _attr: { "w:fmt": "decimal" } }] });
});
it("should create section properties with changed options", () => {

View File

@ -1,19 +1,30 @@
// http://officeopenxml.com/WPsection.php
import { XmlComponent } from "file/xml-components";
import { FooterReferenceType, IPageNumberTypeAttributes, PageNumberFormat, PageNumberType } from "./";
import { Columns } from "./columns/columns";
import { IColumnsAttributes } from "./columns/columns-attributes";
import { DocumentGrid } from "./doc-grid/doc-grid";
import { IDocGridAttributesProperties } from "./doc-grid/doc-grid-attributes";
import { FooterReference } from "./footer-reference/footer-reference";
import { HeaderReference } from "./header-reference/header-reference";
import { FooterReference, IFooterOptions } from "./footer-reference/footer-reference";
import { HeaderReference, IHeaderOptions } from "./header-reference/header-reference";
import { HeaderReferenceType } from "./header-reference/header-reference-attributes";
import { PageMargin } from "./page-margin/page-margin";
import { IPageMarginAttributes } from "./page-margin/page-margin-attributes";
import { PageSize } from "./page-size/page-size";
import { IPageSizeAttributes } from "./page-size/page-size-attributes";
import { IPageSizeAttributes, PageOrientation } from "./page-size/page-size-attributes";
// import { TitlePage } from "./title-page/title-page";
export type SectionPropertiesOptions = IPageSizeAttributes & IPageMarginAttributes & IColumnsAttributes & IDocGridAttributesProperties;
export type SectionPropertiesOptions = IPageSizeAttributes &
IPageMarginAttributes &
IColumnsAttributes &
IDocGridAttributesProperties &
IHeaderOptions &
IFooterOptions &
IPageNumberTypeAttributes;
export class SectionProperties extends XmlComponent {
private readonly options: SectionPropertiesOptions;
constructor(options?: SectionPropertiesOptions) {
super("w:sectPr");
@ -29,7 +40,13 @@ export class SectionProperties extends XmlComponent {
gutter: 0,
space: 708,
linePitch: 360,
orientation: "portrait",
orientation: PageOrientation.PORTRAIT,
headerType: HeaderReferenceType.DEFAULT,
headerId: 0,
footerType: FooterReferenceType.DEFAULT,
footerId: 0,
pageNumberStart: undefined,
pageNumberFormatType: PageNumberFormat.DECIMAL,
};
const mergedOptions = {
@ -51,7 +68,26 @@ export class SectionProperties extends XmlComponent {
);
this.root.push(new Columns(mergedOptions.space));
this.root.push(new DocumentGrid(mergedOptions.linePitch));
this.root.push(new HeaderReference());
this.root.push(new FooterReference());
this.root.push(
new HeaderReference({
headerType: mergedOptions.headerType,
headerId: mergedOptions.headerId,
}),
);
this.root.push(
new FooterReference({
footerType: mergedOptions.footerType,
footerId: mergedOptions.footerId,
}),
);
this.root.push(new PageNumberType(mergedOptions.pageNumberStart, mergedOptions.pageNumberFormatType));
this.options = mergedOptions;
}
public get Options(): SectionPropertiesOptions {
return this.options;
}
}

View File

@ -0,0 +1,11 @@
import { XmlAttributeComponent } from "file/xml-components";
export interface IHeaderReferenceAttributes {
value: string;
}
export class TitlePageAttributes extends XmlAttributeComponent<IHeaderReferenceAttributes> {
protected xmlKeys = {
value: "w:val",
};
}

View File

@ -0,0 +1,17 @@
import { expect } from "chai";
import { Formatter } from "../../../../../export/formatter";
import { TitlePage } from "./title-page";
describe("PageSize", () => {
describe("#constructor()", () => {
it("should create title page property for different first page header", () => {
const properties = new TitlePage();
const tree = new Formatter().format(properties);
expect(Object.keys(tree)).to.deep.equal(["w:titlePg"]);
expect(tree["w:titlePg"]).to.be.an.instanceof(Array);
expect(tree["w:titlePg"][0]).to.deep.equal({ _attr: { "w:val": "1" } });
});
});
});

View File

@ -0,0 +1,13 @@
import { XmlComponent } from "file/xml-components";
import { TitlePageAttributes } from "./title-page-attributes";
export class TitlePage extends XmlComponent {
constructor() {
super("w:titlePg");
this.root.push(
new TitlePageAttributes({
value: "1",
}),
);
}
}

View File

@ -23,6 +23,11 @@ describe("Document", () => {
}
assert.isTrue(true);
});
it("should create default section", () => {
const body = new Formatter().format(document)["w:document"][1]["w:body"];
expect(body[0]).to.have.property("w:sectPr");
});
});
describe("#createParagraph", () => {
@ -33,7 +38,7 @@ describe("Document", () => {
expect(body)
.to.be.an("array")
.which.has.length.at.least(1);
expect(body[1]).to.have.property("w:p");
expect(body[0]).to.have.property("w:p");
});
it("should use the text given to create a run in the paragraph", () => {
@ -43,7 +48,7 @@ describe("Document", () => {
expect(body)
.to.be.an("array")
.which.has.length.at.least(1);
expect(body[1])
expect(body[0])
.to.have.property("w:p")
.which.includes({
"w:r": [{ "w:rPr": [] }, { "w:t": [{ _attr: { "xml:space": "preserve" } }, "sample paragraph text"] }],
@ -59,7 +64,7 @@ describe("Document", () => {
expect(body)
.to.be.an("array")
.which.has.length.at.least(1);
expect(body[1]).to.have.property("w:tbl");
expect(body[0]).to.have.property("w:tbl");
});
it("should create a table with the correct dimensions", () => {
@ -68,7 +73,7 @@ describe("Document", () => {
expect(body)
.to.be.an("array")
.which.has.length.at.least(1);
expect(body[1])
expect(body[0])
.to.have.property("w:tbl")
.which.includes({
"w:tblGrid": [
@ -77,7 +82,7 @@ describe("Document", () => {
{ "w:gridCol": [{ _attr: { "w:w": 1 } }] },
],
});
expect(body[1]["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

@ -1,7 +1,6 @@
// http://officeopenxml.com/WPdocument.php
import { IMediaData } from "file/media";
import { XmlComponent } from "file/xml-components";
import { Paragraph, PictureRun } from "../paragraph";
import { Paragraph } from "../paragraph";
import { Table } from "../table";
import { Body } from "./body";
import { SectionPropertiesOptions } from "./body/section-properties/section-properties";
@ -37,8 +36,9 @@ export class Document extends XmlComponent {
this.root.push(this.body);
}
public addParagraph(paragraph: Paragraph): void {
public addParagraph(paragraph: Paragraph): Document {
this.body.push(paragraph);
return this;
}
public createParagraph(text?: string): Paragraph {
@ -57,17 +57,7 @@ export class Document extends XmlComponent {
return table;
}
public addDrawing(pictureParagraph: Paragraph): void {
this.body.push(pictureParagraph);
}
public createDrawing(imageData: IMediaData): PictureRun {
const paragraph = new Paragraph();
const run = new PictureRun(imageData);
paragraph.addRun(run);
this.addDrawing(paragraph);
return run;
public get Body(): Body {
return this.body;
}
}

View File

@ -1 +1,2 @@
export * from "./document";
export * from "./body";

View File

@ -0,0 +1,26 @@
import { XmlAttributeComponent } from "file/xml-components";
import { IDistance } from "../drawing";
export interface IAnchorAttributes extends IDistance {
allowOverlap?: "0" | "1";
behindDoc?: "0" | "1";
layoutInCell?: "0" | "1";
locked?: "0" | "1";
relativeHeight?: number;
simplePos?: "0" | "1";
}
export class AnchorAttributes extends XmlAttributeComponent<IAnchorAttributes> {
protected xmlKeys = {
distT: "distT",
distB: "distB",
distL: "distL",
distR: "distR",
allowOverlap: "allowOverlap",
behindDoc: "behindDoc",
layoutInCell: "layoutInCell",
locked: "locked",
relativeHeight: "relativeHeight",
simplePos: "simplePos",
};
}

View File

@ -0,0 +1,118 @@
import { assert } from "chai";
import { Utility } from "../../../tests/utility";
import { IDrawingOptions, TextWrapStyle } from ".././";
import { Anchor } from "./";
function createDrawing(drawingOptions: IDrawingOptions): Anchor {
return new Anchor(
1,
{
pixels: {
x: 100,
y: 100,
},
emus: {
x: 100 * 9525,
y: 100 * 9525,
},
},
drawingOptions,
);
}
describe("Anchor", () => {
let anchor: Anchor;
describe("#constructor()", () => {
it("should create a Drawing with correct root key", () => {
anchor = createDrawing({});
const newJson = Utility.jsonify(anchor);
assert.equal(newJson.rootKey, "wp:anchor");
assert.equal(newJson.root.length, 10);
});
it("should create a Drawing with all default options", () => {
anchor = createDrawing({});
const newJson = Utility.jsonify(anchor);
assert.equal(newJson.root.length, 10);
const anchorAttributes = newJson.root[0].root;
assert.include(anchorAttributes, {
distT: 0,
distB: 0,
distL: 0,
distR: 0,
simplePos: "0",
allowOverlap: "1",
behindDoc: "0",
locked: "0",
layoutInCell: "1",
relativeHeight: 952500,
});
// 1: simple pos
assert.equal(newJson.root[1].rootKey, "wp:simplePos");
// 2: horizontal position
const horizontalPosition = newJson.root[2];
assert.equal(horizontalPosition.rootKey, "wp:positionH");
assert.include(horizontalPosition.root[0].root, {
relativeFrom: "column",
});
assert.equal(horizontalPosition.root[1].rootKey, "wp:posOffset");
assert.include(horizontalPosition.root[1].root[0], 0);
// 3: vertical position
const verticalPosition = newJson.root[3];
assert.equal(verticalPosition.rootKey, "wp:positionV");
assert.include(verticalPosition.root[0].root, {
relativeFrom: "paragraph",
});
assert.equal(verticalPosition.root[1].rootKey, "wp:posOffset");
assert.include(verticalPosition.root[1].root[0], 0);
// 4: extent
const extent = newJson.root[4];
assert.equal(extent.rootKey, "wp:extent");
assert.include(extent.root[0].root, {
cx: 952500,
cy: 952500,
});
// 5: effect extent
const effectExtent = newJson.root[5];
assert.equal(effectExtent.rootKey, "wp:effectExtent");
// 6 text wrap: none
const textWrap = newJson.root[6];
assert.equal(textWrap.rootKey, "wp:wrapNone");
// 7: doc properties
const docProperties = newJson.root[7];
assert.equal(docProperties.rootKey, "wp:docPr");
// 8: graphic frame properties
const graphicFrame = newJson.root[8];
assert.equal(graphicFrame.rootKey, "wp:cNvGraphicFramePr");
// 9: graphic
const graphic = newJson.root[9];
assert.equal(graphic.rootKey, "a:graphic");
});
it("should create a Drawing with text wrapping", () => {
anchor = createDrawing({
textWrapping: {
textWrapStyle: TextWrapStyle.SQUARE,
},
});
const newJson = Utility.jsonify(anchor);
assert.equal(newJson.root.length, 10);
// 6 text wrap: square
const textWrap = newJson.root[6];
assert.equal(textWrap.rootKey, "wp:wrapSquare");
});
});
});

View File

@ -0,0 +1,88 @@
// http://officeopenxml.com/drwPicFloating.php
import { IMediaDataDimensions } from "file/media";
import { XmlComponent } from "file/xml-components";
import { IDrawingOptions } from "../drawing";
import {
HorizontalPosition,
HorizontalPositionRelativeFrom,
IFloating,
SimplePos,
VerticalPosition,
VerticalPositionRelativeFrom,
} from "../floating";
import { Graphic } from "../inline/graphic";
import { TextWrapStyle, WrapNone, WrapSquare, WrapTight, WrapTopAndBottom } from "../text-wrap";
import { DocProperties } from "./../doc-properties/doc-properties";
import { EffectExtent } from "./../effect-extent/effect-extent";
import { Extent } from "./../extent/extent";
import { GraphicFrameProperties } from "./../graphic-frame/graphic-frame-properties";
import { AnchorAttributes } from "./anchor-attributes";
const defaultOptions: IFloating = {
allowOverlap: true,
behindDocument: false,
lockAnchor: false,
layoutInCell: true,
verticalPosition: {
relative: VerticalPositionRelativeFrom.PARAGRAPH,
offset: 0,
},
horizontalPosition: {
relative: HorizontalPositionRelativeFrom.COLUMN,
offset: 0,
},
};
export class Anchor extends XmlComponent {
constructor(referenceId: number, dimensions: IMediaDataDimensions, drawingOptions: IDrawingOptions) {
super("wp:anchor");
const floating = {
...defaultOptions,
...drawingOptions.floating,
};
this.root.push(
new AnchorAttributes({
distT: 0,
distB: 0,
distL: 0,
distR: 0,
simplePos: "0", // note: word doesn't fully support - so we use 0
allowOverlap: floating.allowOverlap === true ? "1" : "0",
behindDoc: floating.behindDocument === true ? "1" : "0",
locked: floating.lockAnchor === true ? "1" : "0",
layoutInCell: floating.layoutInCell === true ? "1" : "0",
relativeHeight: dimensions.emus.y,
}),
);
this.root.push(new SimplePos());
this.root.push(new HorizontalPosition(floating.horizontalPosition));
this.root.push(new VerticalPosition(floating.verticalPosition));
this.root.push(new Extent(dimensions.emus.x, dimensions.emus.y));
this.root.push(new EffectExtent());
if (drawingOptions.textWrapping !== undefined) {
switch (drawingOptions.textWrapping.textWrapStyle) {
case TextWrapStyle.SQUARE:
this.root.push(new WrapSquare(drawingOptions.textWrapping));
break;
case TextWrapStyle.TIGHT:
this.root.push(new WrapTight(drawingOptions.textWrapping.distanceFromText));
break;
case TextWrapStyle.TOP_AND_BOTTOM:
this.root.push(new WrapTopAndBottom(drawingOptions.textWrapping.distanceFromText));
break;
case TextWrapStyle.NONE:
default:
this.root.push(new WrapNone());
}
} else {
this.root.push(new WrapNone());
}
this.root.push(new DocProperties());
this.root.push(new GraphicFrameProperties());
this.root.push(new Graphic(referenceId, dimensions.emus.x, dimensions.emus.y));
}
}

View File

@ -0,0 +1,2 @@
export * from "./anchor";
export * from "./anchor-attributes";

View File

@ -2,14 +2,12 @@ import { assert } from "chai";
import * as fs from "fs";
import { Utility } from "../../tests/utility";
import { Drawing } from "./";
import { Drawing, IDrawingOptions, PlacementPosition } from "./";
describe("Drawing", () => {
let currentBreak: Drawing;
beforeEach(() => {
const path = "./demo/images/image1.jpeg";
currentBreak = new Drawing({
function createDrawing(drawingOptions?: IDrawingOptions): Drawing {
const path = "./demo/images/image1.jpeg";
return new Drawing(
{
fileName: "test.jpg",
referenceId: 1,
path: path,
@ -23,14 +21,33 @@ describe("Drawing", () => {
y: 100 * 9525,
},
},
});
});
},
drawingOptions,
);
}
describe("Drawing", () => {
let currentBreak: Drawing;
describe("#constructor()", () => {
it("should create a Drawing with correct root key", () => {
currentBreak = createDrawing();
const newJson = Utility.jsonify(currentBreak);
assert.equal(newJson.rootKey, "w:drawing");
// console.log(JSON.stringify(newJson, null, 2));
});
it("should create a drawing with inline element when there are no options passed", () => {
currentBreak = createDrawing();
const newJson = Utility.jsonify(currentBreak);
assert.equal(newJson.root[0].rootKey, "wp:inline");
});
it("should create a drawing with anchor element when there options are passed", () => {
currentBreak = createDrawing({
position: PlacementPosition.FLOATING,
});
const newJson = Utility.jsonify(currentBreak);
assert.equal(newJson.root[0].rootKey, "wp:anchor");
});
});
});

View File

@ -1,20 +1,53 @@
import { IMediaData } from "file/media";
import { XmlComponent } from "file/xml-components";
import { Anchor } from "./anchor";
import { IFloating } from "./floating";
import { Inline } from "./inline";
import { ITextWrapping } from "./text-wrap";
export enum PlacementPosition {
INLINE,
FLOATING,
}
export interface IDistance {
distT?: number;
distB?: number;
distL?: number;
distR?: number;
}
export interface IDrawingOptions {
position?: PlacementPosition;
textWrapping?: ITextWrapping;
floating?: IFloating;
}
const defaultDrawingOptions: IDrawingOptions = {
position: PlacementPosition.INLINE,
};
export class Drawing extends XmlComponent {
private inline: Inline;
private readonly inline: Inline;
constructor(imageData: IMediaData) {
constructor(imageData: IMediaData, drawingOptions?: IDrawingOptions) {
super("w:drawing");
if (imageData === undefined) {
throw new Error("imageData cannot be undefined");
}
this.inline = new Inline(imageData.referenceId, imageData.dimensions);
const mergedOptions = {
...defaultDrawingOptions,
...drawingOptions,
};
this.root.push(this.inline);
if (mergedOptions.position === PlacementPosition.INLINE) {
this.inline = new Inline(imageData.referenceId, imageData.dimensions);
this.root.push(this.inline);
} else if (mergedOptions.position === PlacementPosition.FLOATING) {
this.root.push(new Anchor(imageData.referenceId, imageData.dimensions, mergedOptions));
}
}
public scale(factorX: number, factorY: number): void {

View File

@ -2,7 +2,7 @@ import { XmlComponent } from "file/xml-components";
import { ExtentAttributes } from "./extent-attributes";
export class Extent extends XmlComponent {
private attributes: ExtentAttributes;
private readonly attributes: ExtentAttributes;
constructor(x: number, y: number) {
super("wp:extent");

View File

@ -0,0 +1,15 @@
import { assert } from "chai";
import { VerticalPositionAlign } from ".";
import { Utility } from "../../../tests/utility";
import { Align } from "./align";
describe("Align", () => {
describe("#constructor()", () => {
it("should create a element with correct root key", () => {
const newJson = Utility.jsonify(new Align(VerticalPositionAlign.CENTER));
assert.equal(newJson.rootKey, "wp:align");
assert.include(newJson.root[0], VerticalPositionAlign.CENTER);
});
});
});

View File

@ -0,0 +1,10 @@
// http://officeopenxml.com/drwPicFloating-position.php
import { XmlComponent } from "file/xml-components";
import { HorizontalPositionAlign, VerticalPositionAlign } from "./floating-position";
export class Align extends XmlComponent {
constructor(value: HorizontalPositionAlign | VerticalPositionAlign) {
super("wp:align");
this.root.push(value);
}
}

View File

@ -0,0 +1,60 @@
// http://officeopenxml.com/drwPicFloating-position.php
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",
}
export interface IHorizontalPositionOptions {
relative: HorizontalPositionRelativeFrom;
align?: HorizontalPositionAlign;
offset?: number;
}
export interface IVerticalPositionOptions {
relative: VerticalPositionRelativeFrom;
align?: VerticalPositionAlign;
offset?: number;
}
export interface IFloating {
horizontalPosition: IHorizontalPositionOptions;
verticalPosition: IVerticalPositionOptions;
allowOverlap?: boolean;
lockAnchor?: boolean;
behindDocument?: boolean;
layoutInCell?: boolean;
}

View File

@ -0,0 +1,41 @@
import { assert } from "chai";
import { HorizontalPositionAlign, HorizontalPositionRelativeFrom } from ".";
import { Utility } from "../../../tests/utility";
import { HorizontalPosition } from "./horizontal-position";
describe("HorizontalPosition", () => {
describe("#constructor()", () => {
it("should create a element with position align", () => {
const newJson = Utility.jsonify(
new HorizontalPosition({
relative: HorizontalPositionRelativeFrom.MARGIN,
align: HorizontalPositionAlign.CENTER,
}),
);
assert.equal(newJson.rootKey, "wp:positionH");
assert.include(newJson.root[0].root, {
relativeFrom: "margin",
});
assert.equal(newJson.root[1].rootKey, "wp:align");
assert.include(newJson.root[1].root, "center");
});
it("should create a element with offset", () => {
const newJson = Utility.jsonify(
new HorizontalPosition({
relative: HorizontalPositionRelativeFrom.MARGIN,
offset: 40,
}),
);
assert.equal(newJson.rootKey, "wp:positionH");
assert.include(newJson.root[0].root, {
relativeFrom: "margin",
});
assert.equal(newJson.root[1].rootKey, "wp:posOffset");
assert.include(newJson.root[1].root[0], 40);
});
});
});

View File

@ -0,0 +1,35 @@
// http://officeopenxml.com/drwPicFloating-position.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { Align } from "./align";
import { HorizontalPositionRelativeFrom, IHorizontalPositionOptions } from "./floating-position";
import { PositionOffset } from "./position-offset";
interface IHorizontalPositionAttributes {
relativeFrom: HorizontalPositionRelativeFrom;
}
class HorizontalPositionAttributes extends XmlAttributeComponent<IHorizontalPositionAttributes> {
protected xmlKeys = {
relativeFrom: "relativeFrom",
};
}
export class HorizontalPosition extends XmlComponent {
constructor(horizontalPosition: IHorizontalPositionOptions) {
super("wp:positionH");
this.root.push(
new HorizontalPositionAttributes({
relativeFrom: horizontalPosition.relative,
}),
);
if (horizontalPosition.align) {
this.root.push(new Align(horizontalPosition.align));
} else if (horizontalPosition.offset !== undefined) {
this.root.push(new PositionOffset(horizontalPosition.offset));
} else {
throw new Error("There is no configuration provided for floating position (Align or offset)");
}
}
}

View File

@ -0,0 +1,4 @@
export * from "./floating-position";
export * from "./simple-pos";
export * from "./horizontal-position";
export * from "./vertical-position";

View File

@ -0,0 +1,14 @@
import { assert } from "chai";
import { Utility } from "../../../tests/utility";
import { PositionOffset } from "./position-offset";
describe("PositionOffset", () => {
describe("#constructor()", () => {
it("should create a element with correct root key", () => {
const newJson = Utility.jsonify(new PositionOffset(50));
assert.equal(newJson.rootKey, "wp:posOffset");
assert.equal(newJson.root[0], 50);
});
});
});

View File

@ -0,0 +1,9 @@
// http://officeopenxml.com/drwPicFloating-position.php
import { XmlComponent } from "file/xml-components";
export class PositionOffset extends XmlComponent {
constructor(offsetValue: number) {
super("wp:posOffset");
this.root.push(offsetValue.toString());
}
}

View File

@ -0,0 +1,17 @@
import { assert } from "chai";
import { SimplePos } from "./simple-pos";
import { Utility } from "../../../tests/utility";
describe("SimplePos", () => {
describe("#constructor()", () => {
it("should create a element with correct root key", () => {
const newJson = Utility.jsonify(new SimplePos());
assert.equal(newJson.rootKey, "wp:simplePos");
assert.include(newJson.root[0].root, {
x: 0,
y: 0,
});
});
});
});

View File

@ -0,0 +1,28 @@
// http://officeopenxml.com/drwPicFloating-position.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
interface ISimplePosAttributes {
x: number;
y: number;
}
class SimplePosAttributes extends XmlAttributeComponent<ISimplePosAttributes> {
protected xmlKeys = {
x: "x",
y: "y",
};
}
export class SimplePos extends XmlComponent {
constructor() {
super("wp:simplePos");
// NOTE: It's not fully supported in Microsoft Word, but this element is needed anyway
this.root.push(
new SimplePosAttributes({
x: 0,
y: 0,
}),
);
}
}

View File

@ -0,0 +1,41 @@
import { assert } from "chai";
import { VerticalPositionAlign, VerticalPositionRelativeFrom } from ".";
import { Utility } from "../../../tests/utility";
import { VerticalPosition } from "./vertical-position";
describe("VerticalPosition", () => {
describe("#constructor()", () => {
it("should create a element with position align", () => {
const newJson = Utility.jsonify(
new VerticalPosition({
relative: VerticalPositionRelativeFrom.MARGIN,
align: VerticalPositionAlign.INSIDE,
}),
);
assert.equal(newJson.rootKey, "wp:positionV");
assert.include(newJson.root[0].root, {
relativeFrom: "margin",
});
assert.equal(newJson.root[1].rootKey, "wp:align");
assert.include(newJson.root[1].root, "inside");
});
it("should create a element with offset", () => {
const newJson = Utility.jsonify(
new VerticalPosition({
relative: VerticalPositionRelativeFrom.MARGIN,
offset: 40,
}),
);
assert.equal(newJson.rootKey, "wp:positionV");
assert.include(newJson.root[0].root, {
relativeFrom: "margin",
});
assert.equal(newJson.root[1].rootKey, "wp:posOffset");
assert.include(newJson.root[1].root[0], 40);
});
});
});

View File

@ -0,0 +1,35 @@
// http://officeopenxml.com/drwPicFloating-position.php
import { XmlAttributeComponent, XmlComponent } from "file/xml-components";
import { Align } from "./align";
import { IVerticalPositionOptions, VerticalPositionRelativeFrom } from "./floating-position";
import { PositionOffset } from "./position-offset";
interface IVerticalPositionAttributes {
relativeFrom: VerticalPositionRelativeFrom;
}
class VerticalPositionAttributes extends XmlAttributeComponent<IVerticalPositionAttributes> {
protected xmlKeys = {
relativeFrom: "relativeFrom",
};
}
export class VerticalPosition extends XmlComponent {
constructor(verticalPosition: IVerticalPositionOptions) {
super("wp:positionV");
this.root.push(
new VerticalPositionAttributes({
relativeFrom: verticalPosition.relative,
}),
);
if (verticalPosition.align) {
this.root.push(new Align(verticalPosition.align));
} else if (verticalPosition.offset !== undefined) {
this.root.push(new PositionOffset(verticalPosition.offset));
} else {
throw new Error("There is no configuration provided for floating position (Align or offset)");
}
}
}

Some files were not shown because too many files have changed in this diff Show More