diff --git a/demo/demo14.js b/demo/demo14.js new file mode 100644 index 0000000000..0e3859eadf --- /dev/null +++ b/demo/demo14.js @@ -0,0 +1,23 @@ +const docx = require('../build'); + +var doc = new docx.Document(undefined,{differentFirstPageHeader:true}); + +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); +doc.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!'); diff --git a/demo/demo8.js b/demo/demo8.js index e849ae3f11..03ba944473 100644 --- a/demo/demo8.js +++ b/demo/demo8.js @@ -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!'); \ No newline at end of file diff --git a/src/export/packer/compiler.ts b/src/export/packer/compiler.ts index 44c7817249..6ee1665074 100644 --- a/src/export/packer/compiler.ts +++ b/src/export/packer/compiler.ts @@ -34,6 +34,7 @@ export class Compiler { 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 xmlHeader2 = xml(this.formatter.format(this.file.firstPageHeader.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)); @@ -64,6 +65,10 @@ export class Compiler { name: "word/header1.xml", }); + this.archive.append(xmlHeader2, { + name: "word/header2.xml", + }); + this.archive.append(xmlFooter, { name: "word/footer1.xml", }); diff --git a/src/file/content-types/content-types.ts b/src/file/content-types/content-types.ts index 4ce020a918..9bb3837cb2 100644 --- a/src/file/content-types/content-types.ts +++ b/src/file/content-types/content-types.ts @@ -24,7 +24,9 @@ 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.header+xml", "/word/header2.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")); diff --git a/src/file/document/body/section-properties/header-reference/header-reference.ts b/src/file/document/body/section-properties/header-reference/header-reference.ts index 3809047bef..cd9d8e7349 100644 --- a/src/file/document/body/section-properties/header-reference/header-reference.ts +++ b/src/file/document/body/section-properties/header-reference/header-reference.ts @@ -2,12 +2,12 @@ import { XmlComponent } from "file/xml-components"; import { HeaderReferenceAttributes } from "./header-reference-attributes"; export class HeaderReference extends XmlComponent { - constructor() { + constructor(order: string, refID: number) { super("w:headerReference"); this.root.push( new HeaderReferenceAttributes({ - type: "default", - id: `rId${3}`, + type: order, + id: `rId${refID}`, }), ); } diff --git a/src/file/document/body/section-properties/section-properties.ts b/src/file/document/body/section-properties/section-properties.ts index 27d3f0bd99..4cd6e35656 100644 --- a/src/file/document/body/section-properties/section-properties.ts +++ b/src/file/document/body/section-properties/section-properties.ts @@ -10,6 +10,7 @@ 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 { TitlePage } from "./title-page/title-page"; export type SectionPropertiesOptions = IPageSizeAttributes & IPageMarginAttributes & IColumnsAttributes & IDocGridAttributesProperties; @@ -30,6 +31,7 @@ export class SectionProperties extends XmlComponent { space: 708, linePitch: 360, orientation: "portrait", + differentFirstPageHeader: false, }; const mergedOptions = { @@ -51,7 +53,13 @@ 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 HeaderReference("default", 3)); + + if (mergedOptions.differentFirstPageHeader) { + this.root.push(new HeaderReference("first", 5)); + this.root.push(new TitlePage()); + } + this.root.push(new FooterReference()); } } diff --git a/src/file/document/body/section-properties/title-page/title-page-attributes.ts b/src/file/document/body/section-properties/title-page/title-page-attributes.ts new file mode 100644 index 0000000000..9022cccf3d --- /dev/null +++ b/src/file/document/body/section-properties/title-page/title-page-attributes.ts @@ -0,0 +1,11 @@ +import { XmlAttributeComponent } from "file/xml-components"; + +export interface IHeaderReferenceAttributes { + value: string; +} + +export class TitlePageAttributes extends XmlAttributeComponent { + protected xmlKeys = { + value: "w:val", + }; +} diff --git a/src/file/document/body/section-properties/title-page/title-page.spec.ts b/src/file/document/body/section-properties/title-page/title-page.spec.ts new file mode 100644 index 0000000000..75374a22db --- /dev/null +++ b/src/file/document/body/section-properties/title-page/title-page.spec.ts @@ -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" } }); + }); + }); +}); diff --git a/src/file/document/body/section-properties/title-page/title-page.ts b/src/file/document/body/section-properties/title-page/title-page.ts new file mode 100644 index 0000000000..5b11d77581 --- /dev/null +++ b/src/file/document/body/section-properties/title-page/title-page.ts @@ -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", + }), + ); + } +} diff --git a/src/file/file.ts b/src/file/file.ts index 2ab3f159fc..7c5d81e44b 100644 --- a/src/file/file.ts +++ b/src/file/file.ts @@ -5,7 +5,7 @@ import { CoreProperties, IPropertiesOptions } from "./core-properties"; import { Document } from "./document"; import { SectionPropertiesOptions } from "./document/body/section-properties/section-properties"; import { FooterWrapper } from "./footer-wrapper"; -import { HeaderWrapper } from "./header-wrapper"; +import { FirstPageHeaderWrapper, HeaderWrapper } from "./header-wrapper"; import { Media } from "./media"; import { Numbering } from "./numbering"; import { Hyperlink, Paragraph, PictureRun } from "./paragraph"; @@ -24,6 +24,9 @@ export class File { private readonly docRelationships: Relationships; private readonly fileRelationships: Relationships; private readonly headerWrapper: HeaderWrapper; + + private readonly firstPageHeaderWrapper: FirstPageHeaderWrapper; + private readonly footerWrapper: FooterWrapper; private readonly contentTypes: ContentTypes; private readonly appProperties: AppProperties; @@ -65,13 +68,23 @@ export class File { "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header", "header1.xml", ); + + this.docRelationships.createRelationship( + 5, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header", + "header2.xml", + ); + this.docRelationships.createRelationship( 4, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer", "footer1.xml", ); this.media = new Media(); + this.headerWrapper = new HeaderWrapper(this.media); + this.firstPageHeaderWrapper = new FirstPageHeaderWrapper(this.media); + this.footerWrapper = new FooterWrapper(this.media); this.contentTypes = new ContentTypes(); this.fileRelationships = new Relationships(); @@ -173,6 +186,10 @@ export class File { return this.headerWrapper; } + public get firstPageHeader(): FirstPageHeaderWrapper { + return this.firstPageHeaderWrapper; + } + public get Footer(): FooterWrapper { return this.footerWrapper; } diff --git a/src/file/header-wrapper.ts b/src/file/header-wrapper.ts index df15871bef..9382099c60 100644 --- a/src/file/header-wrapper.ts +++ b/src/file/header-wrapper.ts @@ -4,7 +4,7 @@ import { Paragraph } from "./paragraph"; import { Relationships } from "./relationships"; import { Table } from "./table"; -export class HeaderWrapper { +export class FirstPageHeaderWrapper { private readonly header: Header; private readonly relationships: Relationships; @@ -53,3 +53,59 @@ export class HeaderWrapper { return this.relationships; } } + +export class HeaderWrapper { + private readonly header: Header; + private readonly header2: Header; + private readonly relationships: Relationships; + + constructor(private readonly media: Media) { + this.header = new Header(); + this.header2 = new Header(); + this.relationships = new Relationships(); + } + + public addParagraph(paragraph: Paragraph): void { + this.header.addParagraph(paragraph); + } + + public createParagraph(text?: string): Paragraph { + const para = new Paragraph(text); + this.addParagraph(para); + return para; + } + + public addTable(table: Table): void { + this.header.addTable(table); + } + + public createTable(rows: number, cols: number): Table { + return this.header.createTable(rows, cols); + } + + public addDrawing(imageData: IMediaData): void { + this.header.addDrawing(imageData); + } + + public createImage(image: string): void { + const mediaData = this.media.addMedia(image, this.relationships.RelationshipCount); + this.relationships.createRelationship( + mediaData.referenceId, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", + `media/${mediaData.fileName}`, + ); + this.addDrawing(mediaData); + } + + public get Header(): Header { + return this.header; + } + + public get Header2(): Header { + return this.header2; + } + + public get Relationships(): Relationships { + return this.relationships; + } +} diff --git a/src/file/paragraph/run/page-number.ts b/src/file/paragraph/run/page-number.ts new file mode 100644 index 0000000000..4048c4f79a --- /dev/null +++ b/src/file/paragraph/run/page-number.ts @@ -0,0 +1,38 @@ +import { XmlAttributeComponent, XmlComponent } from "file/xml-components"; + +class FidCharAttrs extends XmlAttributeComponent<{ type: "begin" | "end" | "separate" }> { + protected xmlKeys = { type: "w:fldCharType" }; +} + +class TextAttributes extends XmlAttributeComponent<{ space: "default" | "preserve" }> { + protected xmlKeys = { space: "xml:space" }; +} + +export class Begin extends XmlComponent { + constructor() { + super("w:fldChar"); + this.root.push(new FidCharAttrs({ type: "begin" })); + } +} + +export class Page extends XmlComponent { + constructor() { + super("w:instrText"); + this.root.push(new TextAttributes({ space: "preserve" })); + this.root.push("PAGE"); + } +} + +export class Separate extends XmlComponent { + constructor() { + super("w:fldChar"); + this.root.push(new FidCharAttrs({ type: "separate" })); + } +} + +export class End extends XmlComponent { + constructor() { + super("w:fldChar"); + this.root.push(new FidCharAttrs({ type: "end" })); + } +} diff --git a/src/file/paragraph/run/run.ts b/src/file/paragraph/run/run.ts index 243cea68d3..b913e49df4 100644 --- a/src/file/paragraph/run/run.ts +++ b/src/file/paragraph/run/run.ts @@ -2,6 +2,7 @@ import { Break } from "./break"; import { Caps, SmallCaps } from "./caps"; import { Bold, Color, DoubleStrike, Italics, Size, Strike } from "./formatting"; +import { Begin, End, Page, Separate } from "./page-number"; import { RunProperties } from "./properties"; import { RunFonts } from "./run-fonts"; import { SubScript, SuperScript } from "./script"; @@ -55,6 +56,14 @@ export class Run extends XmlComponent { return this; } + public pageNumber(): Run { + this.root.push(new Begin()); + this.root.push(new Page()); + this.root.push(new Separate()); + this.root.push(new End()); + return this; + } + public smallCaps(): Run { this.properties.push(new SmallCaps()); return this;