-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return a promise for imagebuffer #5
Comments
@rowanc1 |
Been a bit since I looked at it. I still think that an upstream contribution to docx is best. The maintainer is usually pretty good about getting changes turned around fast! Do you want to open an issue asking if that would be accepted on that repo? |
Hmm, not sure... I'm thinking of another approach: |
While a promise would be nice, just want to share my solution for anyone coming here in future import { Node } from '@tiptap/pm/model';
import { findChildren } from '@tiptap/react';
import {
DocxSerializer,
NodeSerializer,
MarkSerializer,
defaultMarks,
defaultNodes,
writeDocx,
} from 'prosemirror-docx';
import { useCallback } from 'react';
const nodeSerializer: NodeSerializer = {
...defaultNodes,
superImage: (state, node) => {
const width = Number(node.attrs.width.replace('px', ''));
const alignment = node.attrs.textAlign;
// TODO: need to handle margins
const widthPercent = (width / 793) * 100;
state.image(node.attrs.src, widthPercent, alignment);
state.closeBlock(node);
},
paragraph: (state, node) => {
state.renderInline(node);
state.addParagraphOptions({
alignment: node.attrs.textAlign,
});
state.closeBlock(node);
},
};
const markSerializer: MarkSerializer = {
...defaultMarks,
textStyle: (state, node, mark) => {
const attrs = mark.attrs;
return {
color: attrs.color,
size: attrs.fontSize,
font: attrs.fontFamily,
};
},
};
const serializer = new DocxSerializer(nodeSerializer, markSerializer);
export function useExportToWord() {
return useCallback(async (doc: Node, fileName: string) => {
const imageNodes = findChildren(doc, (node) => {
if (node.type.name === 'superImage') {
return true;
}
});
const srcBufferMap = new Map<string, Buffer>();
const getImageBuffer = (id: string): Buffer => {
const img = document.querySelector(
`[data-id="${id}"]`,
) as HTMLImageElement;
img.crossOrigin = 'anonymous';
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) {
throw new Error('Could not create canvas context');
}
if (!img.complete) {
throw new Error('Image is not fully loaded');
}
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0, canvas.width, canvas.height);
const dataUrl = canvas.toDataURL('image/jpeg');
const base64 = dataUrl?.split(',')[1];
// Convert base64 to buffer
const buffer = Buffer.from(base64, 'base64');
return buffer;
};
for (const node of imageNodes) {
const src = node.node.attrs.src;
if (!srcBufferMap.has(src)) {
srcBufferMap.set(src, getImageBuffer(node.node.attrs.id));
}
}
const word = serializer.serialize(doc, {
getImageBuffer: (src) => {
return srcBufferMap.get(src) || Buffer.from('');
},
});
writeDocx(word, (blob) => {
const blobURL = window.URL.createObjectURL(new Blob([blob]));
const tempLink = document.createElement('a');
tempLink.style.display = 'none';
tempLink.href = blobURL;
tempLink.download = fileName;
tempLink.setAttribute('target', '_blank');
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
window.URL.revokeObjectURL(blobURL);
});
}, []);
} |
When we actually create an
ImageRun
indocx
, the data must be there. I think, however, we can delay creating the XML node until later in the process and make our implementation either take a Buffer or aPromise<Buffer>
.That makes more sense with most ways you load an image, but does make the internals a bit more complicated.
Another alternative is to upstream the change to docx and allow them to take the promise. I might start there?
The text was updated successfully, but these errors were encountered: