Merge branch 'image-support'

This commit is contained in:
Dolan Miu
2017-04-01 12:22:23 +01:00
26 changed files with 293 additions and 33 deletions

View File

@ -5,10 +5,17 @@ var doc = new docx.Document();
var paragraph = new docx.Paragraph("Hello World"); var paragraph = new docx.Paragraph("Hello World");
var institutionText = new docx.TextRun("University College London").bold(); var institutionText = new docx.TextRun("University College London").bold();
var dateText = new docx.TextRun("5th Dec 2015").tab().bold(); var dateText = new docx.TextRun("5th Dec 2015").tab().bold();
paragraph.addText(institutionText); paragraph.addRun(institutionText);
paragraph.addText(dateText); paragraph.addRun(dateText);
doc.addParagraph(paragraph); 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); var exporter = new docx.LocalPacker(doc);
exporter.pack('My Document'); exporter.pack('My Document');

BIN
demo/penguins.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -2,4 +2,6 @@ export { Document } from "./document";
export { Paragraph } from "./paragraph"; export { Paragraph } from "./paragraph";
export { Run } from "./run"; export { Run } from "./run";
export { TextRun } from "./run/text-run"; export { TextRun } from "./run/text-run";
export { PictureRun } from "./run/picture-run";
export { Table } from "./table"; export { Table } from "./table";
// Perhaps all run related stuff can be exported from run, instead of exporting Run, TextRun, PictureRun seperately.

View File

@ -1,5 +1,7 @@
import { IData } from "../../media/data";
import { Num } from "../../numbering/num"; import { Num } from "../../numbering/num";
import { Run } from "../run"; import { Run } from "../run";
import { PictureRun } from "../run/picture-run";
import { TextRun } from "../run/text-run"; import { TextRun } from "../run/text-run";
import { XmlComponent } from "../xml-components"; import { XmlComponent } from "../xml-components";
@ -36,6 +38,12 @@ export class Paragraph extends XmlComponent {
return run; return run;
} }
public createPictureRun(imageData: IData): PictureRun {
const run = new PictureRun(imageData);
this.addRun(run);
return run;
}
public heading1(): Paragraph { public heading1(): Paragraph {
this.properties.push(new Style("Heading1")); this.properties.push(new Style("Heading1"));
return this; return this;

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,21 @@
import { XmlAttributeComponent, XmlComponent } from "../../../../../../../../xml-components";
interface IBlipProperties {
embed: string;
}
class BlipAttributes extends XmlAttributeComponent<IBlipProperties> {
protected xmlKeys = {
embed: "r:embed",
};
}
export class Blip extends XmlComponent {
constructor(referenceId: number) {
super("a:blip");
this.root.push(new BlipAttributes({
embed: `rId${referenceId}`,
}));
}
}

View File

@ -0,0 +1,8 @@
import { XmlComponent } from "../../../../../../../../xml-components";
export class SourceRectangle extends XmlComponent {
constructor() {
super("a:srcRect");
}
}

View File

@ -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());
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,24 @@
import { XmlAttributeComponent, XmlComponent } from "../../../../../xml-components";
import { GraphicData } from "./graphic-data";
interface IGraphicProperties {
a: string;
}
class GraphicAttributes extends XmlAttributeComponent<IGraphicProperties> {
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));
}
}

View File

@ -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));
}
}

View File

@ -1,4 +1,4 @@
import { XmlComponent } from "../xml-components"; import { XmlComponent } from "../../xml-components";
export class Text extends XmlComponent { export class Text extends XmlComponent {

View File

@ -1,5 +1,5 @@
import { Run } from "../run"; import { Run } from "../run";
import { Text } from "./text"; import { Text } from "./run-components/text";
export class TextRun extends Run { export class TextRun extends Run {

View File

@ -1,5 +1,6 @@
import * as express from "express"; import * as express from "express";
import { Document } from "../../docx/document"; import { Document } from "../../docx/document";
import { Media } from "../../media";
import { Numbering } from "../../numbering"; import { Numbering } from "../../numbering";
import { Properties } from "../../properties"; import { Properties } from "../../properties";
import { Styles } from "../../styles"; import { Styles } from "../../styles";
@ -8,8 +9,8 @@ import { Packer } from "./packer";
export class ExpressPacker extends Packer { export class ExpressPacker extends Packer {
private res: express.Response; private res: express.Response;
constructor(document: Document, res: express.Response, styles?: Styles, properties?: Properties, numbering?: Numbering) { constructor(document: Document, res: express.Response, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) {
super(document, styles, properties, numbering); super(document, styles, properties, numbering, media);
this.res = res; this.res = res;
this.res.on("close", () => { this.res.on("close", () => {

View File

@ -1,5 +1,6 @@
import * as fs from "fs"; import * as fs from "fs";
import { Document } from "../../docx/document"; import { Document } from "../../docx/document";
import { Media } from "../../media";
import { Numbering } from "../../numbering"; import { Numbering } from "../../numbering";
import { Properties } from "../../properties"; import { Properties } from "../../properties";
import { Styles } from "../../styles"; import { Styles } from "../../styles";
@ -8,8 +9,8 @@ import { Packer } from "./packer";
export class LocalPacker extends Packer { export class LocalPacker extends Packer {
private stream: fs.WriteStream; private stream: fs.WriteStream;
constructor(document: Document, styles?: Styles, properties?: Properties, numbering?: Numbering) { constructor(document: Document, styles?: Styles, properties?: Properties, numbering?: Numbering, media?: Media) {
super(document, styles, properties, numbering); super(document, styles, properties, numbering, media);
} }
public pack(path: string): void { public pack(path: string): void {

View File

@ -2,25 +2,32 @@ import * as archiver from "archiver";
import * as path from "path"; import * as path from "path";
import * as xml from "xml"; import * as xml from "xml";
import { Document } from "../../docx"; import { Document } from "../../docx";
import { Media } from "../../media";
import { Numbering } from "../../numbering"; import { Numbering } from "../../numbering";
import { Properties } from "../../properties"; import { Properties } from "../../properties";
import { Styles } from "../../styles"; import { Styles } from "../../styles";
import { DefaultStylesFactory } from "../../styles/factory"; import { DefaultStylesFactory } from "../../styles/factory";
import { Formatter } from "../formatter"; import { Formatter } from "../formatter";
const templatePath = path.resolve(__dirname, "../../../template"); const TEMPLATE_PATH = path.resolve(__dirname, "../../../template");
export abstract class Packer { export abstract class Packer {
protected archive: any; protected archive: any;
protected document: Document;
private formatter: Formatter; private formatter: Formatter;
private style: Styles; 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.formatter = new Formatter();
this.document = document;
this.archive = archiver.create("zip", {}); this.archive = archiver.create("zip", {});
if (style) { if (style) {
@ -30,22 +37,6 @@ export abstract class Packer {
this.style = stylesFactory.newInstance(); 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) => { this.archive.on("error", (err) => {
throw err; throw err;
}); });
@ -55,18 +46,24 @@ export abstract class Packer {
this.archive.pipe(output); this.archive.pipe(output);
this.archive.glob("**", { this.archive.glob("**", {
expand: true, expand: true,
cwd: templatePath, cwd: TEMPLATE_PATH,
}); });
this.archive.glob("**/.rels", { this.archive.glob("**/.rels", {
expand: true, expand: true,
cwd: templatePath, cwd: TEMPLATE_PATH,
}); });
const xmlDocument = xml(this.formatter.format(this.document)); const xmlDocument = xml(this.formatter.format(this.document));
const xmlStyles = xml(this.formatter.format(this.style)); 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)); const xmlNumbering = xml(this.formatter.format(this.numbering));
this.archive.append(xmlDocument, { this.archive.append(xmlDocument, {
name: "word/document.xml", name: "word/document.xml",
}); });
@ -83,6 +80,12 @@ export abstract class Packer {
name: "word/numbering.xml", name: "word/numbering.xml",
}); });
for (const data of this.media.array) {
this.archive.append(data.stream, {
name: `media/${data.fileName}`,
});
}
this.archive.finalize(); this.archive.finalize();
} }
} }

View File

@ -2,4 +2,5 @@ export * from "./docx";
export * from "./export"; export * from "./export";
export { Numbering } from "./numbering"; export { Numbering } from "./numbering";
export { Styles } from "./styles"; export { Styles } from "./styles";
export { Media } from "./media";
export * from "./export"; export * from "./export";

8
ts/media/data.ts Normal file
View File

@ -0,0 +1,8 @@
import * as fs from "fs";
export interface IData {
referenceId: number;
stream: fs.ReadStream;
path: string;
fileName: string;
}

40
ts/media/index.ts Normal file
View File

@ -0,0 +1,40 @@
import * as fs from "fs";
import * as path from "path";
import { IData } from "./data";
export class Media {
private map: Map<string, IData>;
constructor() {
this.map = new Map<string, IData>();
}
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<IData>();
this.map.forEach((data) => {
array.push(data);
});
return array;
}
}

View File

@ -0,0 +1,11 @@
import { XmlAttributeComponent } from "../docx/xml-components";
interface IRelationshipsAttributesProperties {
xmlns: string;
}
export class RelationshipsAttributes extends XmlAttributeComponent<IRelationshipsAttributesProperties> {
protected xmlKeys = {
xmlns: "xmlns",
};
}

14
ts/relationships/index.ts Normal file
View File

@ -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());
}
}

View File

View File

@ -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));
});
});
});