You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SheetImage.anchor addresses whole cells only (_types.ts:663-666), so images sharing a (row, col) land on top of each other. The drawing writer already emits the four elements that would separate them — it just writes "0" into all of them. Related to #407, which established that hucre always writes twoCellAnchor; adjacent to #53 and the "image position" pain points collected in #19, but neither of those asked for sub-cell offsets.
References are pinned to ef850e5.
What the export needs
A chemical-safety sheet places up to six GHS hazard pictograms side by side inside a single cell, one row per substance. Every icon shares the same (row, col) and differs only by a horizontal offset. In ExcelJS that is:
constEMU_PER_PX=9525constxPx=ICON_INSET_PX+placedInThisRow*(ICON_SIZE_PX+ICON_GAP_PX)ws.addImage(id,{tl: {nativeCol: colIndex0,nativeRow: dataRowIndex0,nativeColOff: xPx*EMU_PER_PX,// <- not expressible in hucrenativeRowOff: ICON_INSET_PX*EMU_PER_PX,},ext: {width: ICON_SIZE_PX,height: ICON_SIZE_PX},editAs: "oneCell",})
Under the current API all six stack at the cell's top-left corner. One icon per column changes the sheet's structure; pre-compositing into a single image loses the individual pictograms and their alt text. Neither is the same document.
This is the only gap found in an otherwise complete evaluation of hucre as a replacement for both ExcelJS and SheetJS — recording that as signal, not as pressure.
The elements are already written, with the value fixed at zero
path
element
writer
reader
images
xdr:from/xdr:to offsets
drawing-writer.ts:140,142,147,149 — "0"
reader.ts:1435-1453 — not read
text boxes
same
drawing-writer.ts:207,209,214,216 — "0"
same helper, not read
charts
same
drawing-writer.ts:330,332,337,339 — "0"
same helper, not read
The xdr:twoCellAnchor hucre produces is spec-correct and complete — drawing-writer.ts:137-150:
So this is not a new anchoring model — it is four string literals that no public field can reach. EMU_PER_PIXEL = 9525 is already in the same file at drawing-writer.ts:26, already converting width/height at :129-130.
The loss is on both sides, which makes it a round-trip loss rather than only a missing feature.parseAnchorPosition (reader.ts:1435-1453) matches row and col and lets colOff/rowOff fall through unread, so an Excel-authored file with offset-placed images has that placement dropped on read and cannot get it back on write. docs/PARITY.md doesn't list this among the known losses, and it ends with "Anything else is a bug" — which is why this is a report rather than a quiet workaround.
Offsets on to, not just from
reader.ts:1097 puts it in the file's own words: "On a twoCellAnchor the from/to cells are what Excel actually honours." Since hucre always writes twoCellAnchor (#407), a:ext records the intended size but Excel stretches the picture to the two corners. Six icons in one cell therefore need offsets on both corners to hold a fixed size — offsets on from alone would move them and let their widths follow the column.
Defaulting to 0 keeps every existing file byte-identical. The reader would fill the same two fields, closing the round-trip.
Units are the one real decision. Pixels at 96 DPI match the neighbouring fields — _types.ts:667-671 already documents width/height as "Rendered size in pixels at 96 DPI, stored as EMU" — and reuse EMU_PER_PIXEL. Raw EMU is what the format stores and survives round-trip without a rounding step, at the cost of a unit unlike anything else on SheetImage. I lean pixels for consistency; the argument for EMU is real and I'd rather you picked.
Testing fits what is already there: test/images.test.ts extracts xl/drawings/drawing1.xml and walks it with findChild/findChildren, so asserting xdr:colOff text on a written anchor and re-reading it lands in the same shape as the existing image assertions.
Separately — editAs, and the oneCellAnchor round-trip
Two smaller things surfaced while reading this, both distinct decisions from the offsets above:
An image read from a oneCellAnchor becomes anchor: { from } with no to (reader.ts:1330-1400), and is written back as a twoCellAnchor with a defaulted to of fromCol + 3 / fromRow + 5 (drawing-writer.ts:134-135). The round-trip changes the anchoring mode, and with it how the picture behaves when rows and columns move. Support oneCellAnchor images (not just twoCellAnchor) #53 asked for the read side of this; the write side is what remains.
There is no way to ask for editAs="oneCell", though the chart path already emits exactly that attribute at drawing-writer.ts:373. It would be the other route to fixed-size images, and cheaper than to offsets for callers who only want "don't resize with the cell".
Neither is a blocker for the use case above, and I'd rather they didn't crowd it out.
PR
Happy to send one, and that offer is real rather than polite. What would unblock me: pixels or EMU, and whether you want images only or the same treatment across text boxes and charts in one change. CONTRIBUTING.md's terms apply either way — a test that fails before the fix, the reason written into the code, and docs/PARITY.md updated in the same PR. Equally happy for you to take it if this is a surface you'd rather shape yourself.
SheetImage.anchoraddresses whole cells only (_types.ts:663-666), so images sharing a(row, col)land on top of each other. The drawing writer already emits the four elements that would separate them — it just writes"0"into all of them. Related to #407, which established that hucre always writestwoCellAnchor; adjacent to #53 and the "image position" pain points collected in #19, but neither of those asked for sub-cell offsets.References are pinned to
ef850e5.What the export needs
A chemical-safety sheet places up to six GHS hazard pictograms side by side inside a single cell, one row per substance. Every icon shares the same
(row, col)and differs only by a horizontal offset. In ExcelJS that is:Under the current API all six stack at the cell's top-left corner. One icon per column changes the sheet's structure; pre-compositing into a single image loses the individual pictograms and their alt text. Neither is the same document.
This is the only gap found in an otherwise complete evaluation of hucre as a replacement for both ExcelJS and SheetJS — recording that as signal, not as pressure.
The elements are already written, with the value fixed at zero
xdr:from/xdr:tooffsetsdrawing-writer.ts:140,142,147,149—"0"reader.ts:1435-1453— not readdrawing-writer.ts:207,209,214,216—"0"drawing-writer.ts:330,332,337,339—"0"The
xdr:twoCellAnchorhucre produces is spec-correct and complete —drawing-writer.ts:137-150:So this is not a new anchoring model — it is four string literals that no public field can reach.
EMU_PER_PIXEL = 9525is already in the same file atdrawing-writer.ts:26, already convertingwidth/heightat:129-130.The loss is on both sides, which makes it a round-trip loss rather than only a missing feature.
parseAnchorPosition(reader.ts:1435-1453) matchesrowandcoland letscolOff/rowOfffall through unread, so an Excel-authored file with offset-placed images has that placement dropped on read and cannot get it back on write.docs/PARITY.mddoesn't list this among the known losses, and it ends with "Anything else is a bug" — which is why this is a report rather than a quiet workaround.Offsets on
to, not justfromreader.ts:1097puts it in the file's own words: "On atwoCellAnchorthe from/to cells are what Excel actually honours." Since hucre always writestwoCellAnchor(#407),a:extrecords the intended size but Excel stretches the picture to the two corners. Six icons in one cell therefore need offsets on both corners to hold a fixed size — offsets onfromalone would move them and let their widths follow the column.A shape, not a demand
Defaulting to
0keeps every existing file byte-identical. The reader would fill the same two fields, closing the round-trip.Units are the one real decision. Pixels at 96 DPI match the neighbouring fields —
_types.ts:667-671already documentswidth/heightas "Rendered size in pixels at 96 DPI, stored as EMU" — and reuseEMU_PER_PIXEL. Raw EMU is what the format stores and survives round-trip without a rounding step, at the cost of a unit unlike anything else onSheetImage. I lean pixels for consistency; the argument for EMU is real and I'd rather you picked.Testing fits what is already there:
test/images.test.tsextractsxl/drawings/drawing1.xmland walks it withfindChild/findChildren, so assertingxdr:colOfftext on a written anchor and re-reading it lands in the same shape as the existing image assertions.Separately —
editAs, and theoneCellAnchorround-tripTwo smaller things surfaced while reading this, both distinct decisions from the offsets above:
oneCellAnchorbecomesanchor: { from }with noto(reader.ts:1330-1400), and is written back as atwoCellAnchorwith a defaultedtooffromCol + 3/fromRow + 5(drawing-writer.ts:134-135). The round-trip changes the anchoring mode, and with it how the picture behaves when rows and columns move. Support oneCellAnchor images (not just twoCellAnchor) #53 asked for the read side of this; the write side is what remains.editAs="oneCell", though the chart path already emits exactly that attribute atdrawing-writer.ts:373. It would be the other route to fixed-size images, and cheaper thantooffsets for callers who only want "don't resize with the cell".Neither is a blocker for the use case above, and I'd rather they didn't crowd it out.
PR
Happy to send one, and that offer is real rather than polite. What would unblock me: pixels or EMU, and whether you want images only or the same treatment across text boxes and charts in one change.
CONTRIBUTING.md's terms apply either way — a test that fails before the fix, the reason written into the code, anddocs/PARITY.mdupdated in the same PR. Equally happy for you to take it if this is a surface you'd rather shape yourself.