UNPKG

myst-to-docx

Version:

Export from a MyST Markdown document to Microsoft Word (*.docx)

120 lines (119 loc) 4.34 kB
import { InternalHyperlink, SimpleField, Bookmark, SequentialIdentifier, TextRun, Document, Packer, SectionType, } from 'docx'; import { Buffer } from 'buffer'; // Important for frontend development! import { selectAll } from 'unist-util-select'; export function createShortId() { return Math.random().toString(36).slice(2); } export function createDocFromState(state, footer, styles) { const { title, description, keywords } = state.frontmatter; const doc = new Document({ title, description, keywords: keywords === null || keywords === void 0 ? void 0 : keywords.join(', '), footnotes: state.footnotes, numbering: { config: state.numbering, }, sections: [ { properties: { type: SectionType.CONTINUOUS, }, children: state.children, footers: footer ? { default: footer } : undefined, }, ], externalStyles: styles, }); return doc; } export async function writeDocx(doc, write) { const buffer = await Packer.toBuffer(doc); return write(buffer); } const DEFAULT_IMAGE_WIDTH = 70; const DEFAULT_PAGE_WIDTH_PIXELS = 800; // The docx width is about the page width of 8.5x11 export const MAX_DOCX_IMAGE_WIDTH = 600; export function getImageWidth(width, maxWidth = MAX_DOCX_IMAGE_WIDTH) { if (typeof width === 'number' && Number.isNaN(width)) { // If it is nan, return with the default. return getImageWidth(DEFAULT_IMAGE_WIDTH); } if (typeof width === 'string') { if (width.endsWith('%')) { return getImageWidth(Number(width.replace('%', ''))); } else if (width.endsWith('px')) { return getImageWidth(Number(width.replace('px', '')) / DEFAULT_PAGE_WIDTH_PIXELS); } console.log(`Unknown width ${width} in getImageWidth`); return getImageWidth(DEFAULT_IMAGE_WIDTH); } let lineWidth = width !== null && width !== void 0 ? width : DEFAULT_IMAGE_WIDTH; if (lineWidth < 1) lineWidth *= 100; if (lineWidth > 100) lineWidth = 100; return (lineWidth / 100) * maxWidth; } async function getImageDimensions(file) { return new Promise((resolve, reject) => { const img = new Image(); // the following handler will fire after a successful loading of the image img.onload = () => { const { naturalWidth: width, naturalHeight: height } = img; resolve({ width, height }); }; // and this handler will fire if there was an error with the image (like if it's not really an image or a corrupted one) img.onerror = () => { reject('There was some problem with the image.'); }; img.src = URL.createObjectURL(file); }); } /** * For frontend development, fetch images as Blobs, get their dimensions and * return options for the docx serializer. * * @param tree the mdast document * @returns options for the serializer */ export async function fetchImagesAsBuffers(tree) { const images = selectAll('image', tree); const buffers = {}; const dimensions = {}; await Promise.all(images.map(async (image) => { const response = await fetch(image.url); const blob = await response.blob(); const buffer = await blob.arrayBuffer(); dimensions[image.url] = await getImageDimensions(blob); buffers[image.url] = Buffer.from(buffer); })); return { getImageBuffer(url) { return buffers[url]; }, getImageDimensions(url) { return dimensions[url]; }, }; } export function createReferenceBookmark(id, kind, before, after) { const textBefore = before ? [new TextRun(before)] : []; const textAfter = after ? [new TextRun(after)] : []; return new Bookmark({ id, children: [...textBefore, new SequentialIdentifier(kind), ...textAfter], }); } export function createReference(id, before, after) { const children = []; if (before) children.push(new TextRun(before)); children.push(new SimpleField(`REF ${id} \\h`)); if (after) children.push(new TextRun(after)); const ref = new InternalHyperlink({ anchor: id, children }); return ref; }