Merge branch 'image-support'
This commit is contained in:
11
demo/demo.js
11
demo/demo.js
@ -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
BIN
demo/penguins.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
@ -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.
|
||||||
|
@ -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;
|
||||||
|
16
ts/docx/run/picture-run.ts
Normal file
16
ts/docx/run/picture-run.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
16
ts/docx/run/run-components/drawing/index.ts
Normal file
16
ts/docx/run/run-components/drawing/index.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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}`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { XmlComponent } from "../../../../../../../../xml-components";
|
||||||
|
|
||||||
|
export class SourceRectangle extends XmlComponent {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super("a:srcRect");
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
24
ts/docx/run/run-components/drawing/inline/graphic/index.ts
Normal file
24
ts/docx/run/run-components/drawing/inline/graphic/index.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
10
ts/docx/run/run-components/drawing/inline/index.ts
Normal file
10
ts/docx/run/run-components/drawing/inline/index.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { XmlComponent } from "../xml-components";
|
import { XmlComponent } from "../../xml-components";
|
||||||
|
|
||||||
export class Text extends XmlComponent {
|
export class Text extends XmlComponent {
|
||||||
|
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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", () => {
|
||||||
|
@ -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 {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
8
ts/media/data.ts
Normal 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
40
ts/media/index.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
11
ts/relationships/attributes.ts
Normal file
11
ts/relationships/attributes.ts
Normal 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
14
ts/relationships/index.ts
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
0
ts/relationships/relationship.ts
Normal file
0
ts/relationships/relationship.ts
Normal file
19
ts/tests/docx/run/run-components/drawingTests.ts
Normal file
19
ts/tests/docx/run/run-components/drawingTests.ts
Normal 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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user