diff --git a/demo/demo.js b/demo/demo.js index 58e7fdfd8d..2a36bb1a83 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -5,10 +5,17 @@ var doc = new docx.Document(); 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.addText(institutionText); -paragraph.addText(dateText); +paragraph.addRun(institutionText); +paragraph.addRun(dateText); doc.addParagraph(paragraph); + +// Feature coming soon +// var media = new docx.Media(); +// media.addMedia("happy-penguins", "./demo/penguins.jpg"); +// var pictureRun = new docx.PictureRun(media.getMedia("happy-penguins")); + +// var exporter = new docx.LocalPacker(doc); var exporter = new docx.LocalPacker(doc); exporter.pack('My Document'); diff --git a/demo/penguins.jpg b/demo/penguins.jpg new file mode 100644 index 0000000000..e4834ee095 Binary files /dev/null and b/demo/penguins.jpg differ diff --git a/ts/docx/index.ts b/ts/docx/index.ts index 875821c29a..1e837b8340 100644 --- a/ts/docx/index.ts +++ b/ts/docx/index.ts @@ -2,4 +2,6 @@ export { Document } from "./document"; export { Paragraph } from "./paragraph"; export { Run } from "./run"; export { TextRun } from "./run/text-run"; +export { PictureRun } from "./run/picture-run"; export { Table } from "./table"; +// Perhaps all run related stuff can be exported from run, instead of exporting Run, TextRun, PictureRun seperately. diff --git a/ts/docx/paragraph/index.ts b/ts/docx/paragraph/index.ts index 4ae172eb1b..5c42381311 100644 --- a/ts/docx/paragraph/index.ts +++ b/ts/docx/paragraph/index.ts @@ -1,5 +1,7 @@ +import { IData } from "../../media/data"; import { Num } from "../../numbering/num"; import { Run } from "../run"; +import { PictureRun } from "../run/picture-run"; import { TextRun } from "../run/text-run"; import { XmlComponent } from "../xml-components"; @@ -36,6 +38,12 @@ export class Paragraph extends XmlComponent { return run; } + public createPictureRun(imageData: IData): PictureRun { + const run = new PictureRun(imageData); + this.addRun(run); + return run; + } + public heading1(): Paragraph { this.properties.push(new Style("Heading1")); return this; diff --git a/ts/docx/run/picture-run.ts b/ts/docx/run/picture-run.ts new file mode 100644 index 0000000000..038d1fc7bf --- /dev/null +++ b/ts/docx/run/picture-run.ts @@ -0,0 +1,16 @@ +import { IData } from "../../media/data"; +import { Run } from "../run"; +import { Drawing } from "./run-components/drawing"; + +export class PictureRun extends Run { + + constructor(imageData: IData) { + super(); + + if (imageData === undefined) { + throw new Error("imageData cannot be undefined"); + } + + this.root.push(new Drawing(imageData)); + } +} diff --git a/ts/docx/run/run-components/drawing/index.ts b/ts/docx/run/run-components/drawing/index.ts new file mode 100644 index 0000000000..fab434e058 --- /dev/null +++ b/ts/docx/run/run-components/drawing/index.ts @@ -0,0 +1,16 @@ +import { IData } from "../../../../media/data"; +import { XmlComponent } from "../../../xml-components"; +import { Inline } from "./inline"; + +export class Drawing extends XmlComponent { + + constructor(imageData: IData) { + super("w:drawing"); + + if (imageData === undefined) { + throw new Error("imageData cannot be undefined"); + } + + this.root.push(new Inline(imageData.referenceId)); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/index.ts b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/index.ts new file mode 100644 index 0000000000..c717a1098c --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/index.ts @@ -0,0 +1,10 @@ +import { XmlComponent } from "../../../../../../xml-components"; +import { Pic } from "./pic"; + +export class GraphicData extends XmlComponent { + + constructor(referenceId: number) { + super("a:graphicData"); + this.root.push(new Pic(referenceId)); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/blip-fill.ts b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/blip-fill.ts new file mode 100644 index 0000000000..ac2ca1d580 --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/blip-fill.ts @@ -0,0 +1,14 @@ +import { XmlComponent } from "../../../../../../../../xml-components"; +import { Blip } from "./blip"; +import { SourceRectangle } from "./source-rectangle"; +import { Stretch } from "./stretch"; + +export class BlipFill extends XmlComponent { + + constructor(referenceId: number) { + super("pic:blipFill"); + this.root.push(new Blip(referenceId)); + this.root.push(new SourceRectangle()); + this.root.push(new Stretch()); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/blip.ts b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/blip.ts new file mode 100644 index 0000000000..2926c52893 --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/blip.ts @@ -0,0 +1,21 @@ +import { XmlAttributeComponent, XmlComponent } from "../../../../../../../../xml-components"; + +interface IBlipProperties { + embed: string; +} + +class BlipAttributes extends XmlAttributeComponent { + protected xmlKeys = { + embed: "r:embed", + }; +} + +export class Blip extends XmlComponent { + + constructor(referenceId: number) { + super("a:blip"); + this.root.push(new BlipAttributes({ + embed: `rId${referenceId}`, + })); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/source-rectangle.ts b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/source-rectangle.ts new file mode 100644 index 0000000000..32df3eaaac --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/source-rectangle.ts @@ -0,0 +1,8 @@ +import { XmlComponent } from "../../../../../../../../xml-components"; + +export class SourceRectangle extends XmlComponent { + + constructor() { + super("a:srcRect"); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/stretch.ts b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/stretch.ts new file mode 100644 index 0000000000..c42fa04ff3 --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/blip/stretch.ts @@ -0,0 +1,16 @@ +import { XmlComponent } from "../../../../../../../../xml-components"; + +class FillRectangle extends XmlComponent { + + constructor() { + super("a:fillRect"); + } +} + +export class Stretch extends XmlComponent { + + constructor() { + super("a:stretch"); + this.root.push(new FillRectangle()); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/index.ts b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/index.ts new file mode 100644 index 0000000000..54628facf8 --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/graphic/graphic-data/pic/index.ts @@ -0,0 +1,10 @@ +import { XmlComponent } from "../../../../../../../xml-components"; +import { BlipFill } from "./blip/blip-fill"; + +export class Pic extends XmlComponent { + + constructor(referenceId: number) { + super("pic:pic"); + this.root.push(new BlipFill(referenceId)); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/graphic/index.ts b/ts/docx/run/run-components/drawing/inline/graphic/index.ts new file mode 100644 index 0000000000..75dd24f75d --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/graphic/index.ts @@ -0,0 +1,24 @@ +import { XmlAttributeComponent, XmlComponent } from "../../../../../xml-components"; +import { GraphicData } from "./graphic-data"; + + +interface IGraphicProperties { + a: string; +} + +class GraphicAttributes extends XmlAttributeComponent { + protected xmlKeys = { + a: "xmlns:a", + }; +} + +export class Graphic extends XmlComponent { + + constructor(referenceId: number) { + super("a:graphic"); + this.root.push(new GraphicAttributes({ + a: "http://schemas.openxmlformats.org/drawingml/2006/main", + })); + this.root.push(new GraphicData(referenceId)); + } +} diff --git a/ts/docx/run/run-components/drawing/inline/index.ts b/ts/docx/run/run-components/drawing/inline/index.ts new file mode 100644 index 0000000000..8f752edfcc --- /dev/null +++ b/ts/docx/run/run-components/drawing/inline/index.ts @@ -0,0 +1,10 @@ +import { XmlComponent } from "../../../../xml-components"; +import { Graphic } from "./graphic"; + +export class Inline extends XmlComponent { + + constructor(referenceId: number) { + super("wp:inline"); + this.root.push(new Graphic(referenceId)); + } +} diff --git a/ts/docx/run/text.ts b/ts/docx/run/run-components/text.ts similarity index 76% rename from ts/docx/run/text.ts rename to ts/docx/run/run-components/text.ts index 745c605094..48f6495331 100644 --- a/ts/docx/run/text.ts +++ b/ts/docx/run/run-components/text.ts @@ -1,4 +1,4 @@ -import { XmlComponent } from "../xml-components"; +import { XmlComponent } from "../../xml-components"; export class Text extends XmlComponent { diff --git a/ts/docx/run/text-run.ts b/ts/docx/run/text-run.ts index d8068021aa..c285b5fe82 100644 --- a/ts/docx/run/text-run.ts +++ b/ts/docx/run/text-run.ts @@ -1,5 +1,5 @@ import { Run } from "../run"; -import { Text } from "./text"; +import { Text } from "./run-components/text"; export class TextRun extends Run { diff --git a/ts/export/packer/express.ts b/ts/export/packer/express.ts index 737c220779..16722ff484 100644 --- a/ts/export/packer/express.ts +++ b/ts/export/packer/express.ts @@ -1,5 +1,6 @@ import * as express from "express"; import { Document } from "../../docx/document"; +import { Media } from "../../media"; import { Numbering } from "../../numbering"; import { Properties } from "../../properties"; import { Styles } from "../../styles"; @@ -8,8 +9,8 @@ import { Packer } from "./packer"; export class ExpressPacker extends Packer { private res: express.Response; - constructor(document: Document, res: express.Response, styles?: Styles, properties?: Properties, numbering?: Numbering) { - super(document, styles, properties, numbering); + constructor(document: Document, res: express.Response, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) { + super(document, styles, properties, numbering, media); this.res = res; this.res.on("close", () => { diff --git a/ts/export/packer/local.ts b/ts/export/packer/local.ts index d1a5980662..bbb10e2a5a 100644 --- a/ts/export/packer/local.ts +++ b/ts/export/packer/local.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; import { Document } from "../../docx/document"; +import { Media } from "../../media"; import { Numbering } from "../../numbering"; import { Properties } from "../../properties"; import { Styles } from "../../styles"; @@ -8,8 +9,8 @@ import { Packer } from "./packer"; export class LocalPacker extends Packer { private stream: fs.WriteStream; - constructor(document: Document, styles?: Styles, properties?: Properties, numbering?: Numbering) { - super(document, styles, properties, numbering); + constructor(document: Document, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) { + super(document, styles, properties, numbering, media); } public pack(path: string): void { diff --git a/ts/export/packer/packer.ts b/ts/export/packer/packer.ts index 167d178f8a..6c6d9c422f 100644 --- a/ts/export/packer/packer.ts +++ b/ts/export/packer/packer.ts @@ -2,25 +2,32 @@ import * as archiver from "archiver"; import * as path from "path"; import * as xml from "xml"; import { Document } from "../../docx"; +import { Media } from "../../media"; import { Numbering } from "../../numbering"; import { Properties } from "../../properties"; import { Styles } from "../../styles"; import { DefaultStylesFactory } from "../../styles/factory"; import { Formatter } from "../formatter"; -const templatePath = path.resolve(__dirname, "../../../template"); +const TEMPLATE_PATH = path.resolve(__dirname, "../../../template"); export abstract class Packer { protected archive: any; - protected document: Document; private formatter: Formatter; private style: Styles; - private properties: Properties; - private numbering: Numbering; - constructor(document: Document, style?: Styles, properties?: Properties, numbering?: Numbering) { + constructor( + protected document: Document, + style?: Styles, + private properties: Properties = new Properties({ + creator: "Un-named", + revision: "1", + lastModifiedBy: "Un-named", + }), + private numbering: Numbering = new Numbering(), + private media: Media = new Media(), + ) { this.formatter = new Formatter(); - this.document = document; this.archive = archiver.create("zip", {}); if (style) { @@ -30,22 +37,6 @@ export abstract class Packer { this.style = stylesFactory.newInstance(); } - if (properties) { - this.properties = properties; - } else { - this.properties = new Properties({ - creator: "Un-named", - revision: "1", - lastModifiedBy: "Un-named", - }); - } - - if (numbering) { - this.numbering = numbering; - } else { - this.numbering = new Numbering(); - } - this.archive.on("error", (err) => { throw err; }); @@ -55,18 +46,24 @@ export abstract class Packer { this.archive.pipe(output); this.archive.glob("**", { expand: true, - cwd: templatePath, + cwd: TEMPLATE_PATH, }); this.archive.glob("**/.rels", { expand: true, - cwd: templatePath, + cwd: TEMPLATE_PATH, }); const xmlDocument = xml(this.formatter.format(this.document)); const xmlStyles = xml(this.formatter.format(this.style)); - const xmlProperties = xml(this.formatter.format(this.properties), { declaration: { standalone: "yes", encoding: "UTF-8" } }); + const xmlProperties = xml(this.formatter.format(this.properties), { + declaration: { + standalone: "yes", + encoding: "UTF-8", + }, + }); const xmlNumbering = xml(this.formatter.format(this.numbering)); + this.archive.append(xmlDocument, { name: "word/document.xml", }); @@ -83,6 +80,12 @@ export abstract class Packer { name: "word/numbering.xml", }); + for (const data of this.media.array) { + this.archive.append(data.stream, { + name: `media/${data.fileName}`, + }); + } + this.archive.finalize(); } } diff --git a/ts/index.ts b/ts/index.ts index 0227b712a7..7c5a2fda81 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -2,4 +2,5 @@ export * from "./docx"; export * from "./export"; export { Numbering } from "./numbering"; export { Styles } from "./styles"; +export { Media } from "./media"; export * from "./export"; diff --git a/ts/media/data.ts b/ts/media/data.ts new file mode 100644 index 0000000000..ada1231659 --- /dev/null +++ b/ts/media/data.ts @@ -0,0 +1,8 @@ +import * as fs from "fs"; + +export interface IData { + referenceId: number; + stream: fs.ReadStream; + path: string; + fileName: string; +} diff --git a/ts/media/index.ts b/ts/media/index.ts new file mode 100644 index 0000000000..66cddca4ad --- /dev/null +++ b/ts/media/index.ts @@ -0,0 +1,40 @@ +import * as fs from "fs"; +import * as path from "path"; +import { IData } from "./data"; + +export class Media { + private map: Map; + + constructor() { + this.map = new Map(); + } + + public getMedia(key: string): IData { + const data = this.map.get(key); + + if (data === undefined) { + throw new Error(`Cannot find image with the key ${key}`); + } + + return data; + } + + public addMedia(key: string, filePath: string): void { + this.map.set(key, { + referenceId: this.map.values.length, + stream: fs.createReadStream(filePath), + path: filePath, + fileName: path.basename(filePath), + }); + } + + public get array(): IData[] { + const array = new Array(); + + this.map.forEach((data) => { + array.push(data); + }); + + return array; + } +} diff --git a/ts/relationships/attributes.ts b/ts/relationships/attributes.ts new file mode 100644 index 0000000000..4aae08248d --- /dev/null +++ b/ts/relationships/attributes.ts @@ -0,0 +1,11 @@ +import { XmlAttributeComponent } from "../docx/xml-components"; + +interface IRelationshipsAttributesProperties { + xmlns: string; +} + +export class RelationshipsAttributes extends XmlAttributeComponent { + protected xmlKeys = { + xmlns: "xmlns", + }; +} diff --git a/ts/relationships/index.ts b/ts/relationships/index.ts new file mode 100644 index 0000000000..e2c27f36e8 --- /dev/null +++ b/ts/relationships/index.ts @@ -0,0 +1,14 @@ +import { XmlComponent } from "../docx/xml-components"; +import { RelationshipsAttributes } from "./attributes"; + +export class Relationships extends XmlComponent { + + constructor() { + super("Relationships"); + this.root.push(new RelationshipsAttributes({ + xmlns: "http://schemas.openxmlformats.org/package/2006/relationships", + })); + + // this.root.push(new Created()); + } +} diff --git a/ts/relationships/relationship.ts b/ts/relationships/relationship.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/tests/docx/run/run-components/drawingTests.ts b/ts/tests/docx/run/run-components/drawingTests.ts new file mode 100644 index 0000000000..20215a0931 --- /dev/null +++ b/ts/tests/docx/run/run-components/drawingTests.ts @@ -0,0 +1,19 @@ +import { assert } from "chai"; +import { Drawing } from "../../../../docx/run/run-components/drawing"; +import { Utility } from "../../../utility"; + +describe.only("Drawing", () => { + let currentBreak: Drawing; + + beforeEach(() => { + currentBreak = new Drawing("test.jpg"); + }); + + describe("#constructor()", () => { + it("should create a Drawing with correct root key", () => { + const newJson = Utility.jsonify(currentBreak); + assert.equal(newJson.rootKey, "w:drawing"); + console.log(JSON.stringify(newJson, null, 2)); + }); + }); +});