import { ScopeData, Tag, TemplateContext } from '../../compilation'; import { MimeTypeHelper } from '../../mimeType'; import { XmlGeneralNode, XmlNode } from '../../xml'; import { TemplatePlugin } from '../templatePlugin'; import { ImageContent } from './imageContent'; /** * Apparently it is not that important for the ID to be unique... * Word displays two images correctly even if they both have the same ID. * Further more, Word will assign each a unique ID upon saving (it assigns * consecutive integers starting with 1). * * Note: The same principal applies to image names. * * Tested in Word v1908 */ let nextImageId = 1; export class ImagePlugin extends TemplatePlugin { public readonly contentType = 'image'; public async simpleTagReplacements(tag: Tag, data: ScopeData, context: TemplateContext): Promise { const wordTextNode = this.utilities.docxParser.containingTextNode(tag.xmlTextNode); const content = data.getScopeData(); if (!content || !content.source) { XmlNode.remove(wordTextNode); return; } // add the image file into the archive const mediaFilePath = await context.docx.mediaFiles.add(content.source, content.format); const relType = MimeTypeHelper.getOfficeRelType(content.format); const relId = await context.currentPart.rels.add(mediaFilePath, relType); await context.docx.contentTypes.ensureContentType(content.format); // create the xml markup const imageId = nextImageId++; const imageXml = this.createMarkup(imageId, relId, content.altText, content.width, content.height); XmlNode.insertAfter(imageXml, wordTextNode); XmlNode.remove(wordTextNode); } private createMarkup(imageId: number, relId: string, altText: string, width: number, height: number): XmlNode { // http://officeopenxml.com/drwPicInline.php // // Performance note: // // I've tried to improve the markup generation performance by parsing // the string once and caching the result (and of course customizing it // per image) but it made no change whatsoever (in both cases 1000 items // loop takes around 8 seconds on my machine) so I'm sticking with this // approach which I find to be more readable. // const name = `Picture ${imageId}`; const markupText = ` ${this.docProperties(imageId, name, altText)} ${this.pictureMarkup(imageId, relId, name, width, height)} `; const markupXml = this.utilities.xmlParser.parse(markupText) as XmlGeneralNode; XmlNode.removeEmptyTextNodes(markupXml); // remove whitespace return markupXml; } private docProperties(imageId: number, name: string, altText: string): string { if (altText) { return ``; } return ` `; } private pictureMarkup(imageId: number, relId: string, name: string, width: number, height: number) { // http://officeopenxml.com/drwPic.php // legend: // nvPicPr - non-visual picture properties - id, name, etc. // blipFill - binary large image (or) picture fill - image size, image fill, etc. // spPr - shape properties - frame size, frame fill, etc. return ` `; } private pixelsToEmu(pixels: number): number { // https://stackoverflow.com/questions/20194403/openxml-distance-size-units // https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vml-units#other-units-of-measurement // https://en.wikipedia.org/wiki/Office_Open_XML_file_formats#DrawingML // http://www.java2s.com/Code/CSharp/2D-Graphics/ConvertpixelstoEMUEMUtopixels.htm return Math.round(pixels * 9525); } }