Header/footer images: correct data model + DOCX export, but never render on live canvas (skeDrawings stays empty) #7706
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Version
@univerjs/engine-render0.15.5(all@univerjs/*packages pinned to^0.15.3, all resolving to the same0.15.xline).Summary
An image inserted into a document's header or footer segment — via either (a) building the
IDocumentDatasnapshot directly beforecreateUniverDoc, or (b) Univer's owndoc.command.insert-doc-imagecommand targeted at the header/footer'ssegmentId— produces a fully correct document model (confirmed:DocumentDataModel.drawings, the header/footer body'scustomBlocks, anddocumentStyle.defaultHeaderId/defaultFooterIdare all correct, and a from-scratch DOCX export built by reading that model directly renders the image correctly on every page). But the image never renders on the live canvas. Direct inspection ofDocumentSkeleton.getSkeletonData().skeHeaders/skeFootersshows the header/footer page'sskeDrawingsMap is always empty, even though the equivalent flow for a BODY image (inserted via the toolbar's own Insert Image button) populatesskeDrawingscorrectly and renders immediately.Repro
univerAPI.createUniverDoc(snapshot).snapshot, setdocumentStyle.defaultHeaderId = "hdr1".snapshot.headers = { hdr1: { headerId: "hdr1", body: { dataStream: "\r\b\r\n", customBlocks: [{ startIndex: 1, blockType: 0, blockId: "img1" }], paragraphs: [{ startIndex: 3 }] } } }(the\bat index 1 is the custom-block placeholder character).snapshot.drawings = { img1: { unitId, subUnitId: unitId, drawingId: "img1", drawingType: 0, transform: { width: 80, height: 40, left: 0, top: 0, angle: 0 }, docTransform: { size: { width: 80, height: 40 }, positionH: { relativeFrom: 2, posOffset: 0 }, positionV: { relativeFrom: 2, posOffset: 0 }, angle: 0 }, layoutType: 0, imageSourceType: "BASE64", source: "data:image/png;base64,..." } }.snapshot.drawingsOrder = ["img1"](required —DocDrawingController.loadDrawingDataForUnitreadsdocumentDataModel.getDrawingsOrder(), which wrapssnapshot.drawingsOrderonly; it never readsheaderFooterDrawingsOrder, confirmed by grepping the shipped bundle — that field is declared onIReferenceSourcebut does not appear anywhere in any@univerjs/*runtime bundle).injector.get(IRenderManagerService).getRenderById(unitId).with(DocSkeletonManagerService).getSkeleton().getSkeletonData(). For every page,skeHeaders.get("hdr1").get(pageWidth).skeDrawings.size === 0.Reproduces identically whether the drawing is baked into the snapshot before creation, or inserted afterward through
commandService.executeCommand("doc.command.insert-doc-image", { drawings: [...] })with the active selection'ssegmentIdset to"hdr1"(confirmed viaDocSelectionManagerService.replaceDocRanges(...), executed successfully withok: true, producing a correctly-shapedRichTextEditingMutation).What we've ruled out
snapshot.headers[headerId].body.customBlocksandsnapshot.drawingsdirectly (bypassing Univer's canvas entirely) produces a correct Word document with the image in the header/footer of every page, from the exact same snapshot that fails to render on canvas.skeHeaders.get(headerId)has a freshly-built entry for the current page width at the time of the failed read (i.e., the header layout pass genuinely ran on this exact data), not a cached pre-image skeleton.DocDrawingTransformerController's_refreshDrawing(which walks each page'sskeHeaders/skeFootersand callsdrawingManagerService.refreshTransformperskeDrawingsentry) only subscribes tocurrentSkeleton$once at construction, so we suspected a later insert's positioning was simply never re-triggered. We replicated its exact logic manually (using only public APIs —Liquid,DocSkeletonManagerService.getSkeleton().getSkeletonData(),IDrawingManagerService.getDrawingData()), called it after every drawing registration (IDrawingManagerService'sadd$firing), and it still findsskeDrawings.size === 0every time. There is nothing to position because nothing was ever added toskeDrawingsin the first place.headerFooterDrawingsOrderproblem. This field exists onIReferenceSource's type declaration but does not appear anywhere in any shipped@univerjs/*0.15.5runtime bundle (grep -rl headerFooterDrawingsOrder node_modules/@univerjs/*/lib/es/*.jsreturns nothing) — nothing reads it. We set it anyway (alongsidedrawingsOrder) with no effect.doc.command.insert-doc-imagecommand and via the toolbar — confirmed live, rendering immediately with correctskeDrawingspopulation.What we found tracing the shipped (minified)
engine-renderbundleNo source maps are published, so this is necessarily minified-symbol tracing, but the control flow is unambiguous:
headerId/footerId-keyed skeleton builder) correctly receives the header/footer's own view-model tree entry fromheaderTreeMap/footerTreeMap.viewModelNode.getChildren()[0]as the tree-walk root — a section/tree child node, not the header/footer's ownDocumentViewModel-wrapping instance.gis the paragraph node's ownblocksarray (built by scanningthis.contentfor the custom-block placeholder character and pushingthis.startIndex + offset— confirmed these are absolute document-relative indices).t.getCustomBlock(I)should resolve against_customBlockCache, which is built purely fromgetBody().customBlockson whicheverDocumentViewModel-like instancetactually is (confirmed via source:_buildCustomBlockCacheiteratesthis.getBody()?.customBlocks ?? []and keys bystartIndex).getCustomBlockWithoutSetCurrentIndex(a different, absolute-index method on the same class) against a correctly-populatedcustomBlocksarray at the rightstartIndex. So the DATA that_customBlockCacheshould be built from is present and correct on the correct object.tint.getCustomBlock(I)is genuinely a different object than the one we queried directly (in which case its own_customBlockCachemay simply never have been built/attached), or whether it correctly delegates back to the header/footer's real view model (in which case the failure is elsewhere in this same function, e.g. in howg's indices are computed for a header/footer paragraph specifically, or in howu— thedrawingsobject passed via the layout config — is scoped). Either way,M == null(silently, no thrown error, consistent with thecontinueproducing zero populated entries and zero crashes) is the exact and only point at which the header/footer path diverges from the working body path for the identical shape of underlying data.Ask
Could someone with the actual (non-minified) TypeScript source confirm, in the header/footer paragraph-layout code path (as opposed to the body path), what object
tactually is at the point oft.getCustomBlock(I), and whether it's expected to have a populated_customBlockCachefor a header/footer segment? If this is a known gap, is there a supported workaround (a specific field to set, a command to call, a controller to trigger) that gets header/footer drawings intoskeDrawingstoday, short of monkey-patching the skeleton after layout?Happy to provide a minimal repro repository if useful.
All reactions