I spent three years on video codecs before moving to graphics tools, and one habit from that time is to treat "edited" and "rewritten" as two different things. An MP4 edit list can tell the player to skip the first second without touching a single sample. A Word crop turns out to work the same way. That is a sensible editor design, and it gets awkward once a DOCX file becomes the input of an HTML pipeline. To find out how awkward, I built a test DOCX by hand from the OOXML structure, with seven picture cases. Word did not save it, so nothing below proves what Word does. One picture is a landscape photo I found online. The other is a screenshot of a demo order page I wrote myself, and every name and phone number on it is a placeholder.
Word stores a crop as numbers over the full image
A picture in word/document.xml carries three layers of parameters. wp:extent is the frame size on the page. a:srcRect gives the four trims in thousandths of a percent. a:xfrm rot is the rotation in 1/60000 of a degree. The bytes in word/media/ stay whole, which in my sample is the 2736×1824 photo at 1,388,716 bytes. On the Word side I can only point to Microsoft's support page on reducing picture file size. Its macOS section offers "Delete cropped areas of pictures", which implies the trimmed pixels stay in the file unless someone ticks it. I have no Word on this machine, so I have not tried that option.
Dietmar Rabich took the photo and published it on Wikimedia Commons under CC BY-SA 4.0. I drew the red box, and the adapted figure carries the same license. Everything outside the red box is still in the DOCX.
Keeping the original buys reversibility
From an editor's point of view this is the right call. Undoing a crop, or widening it a week later, only works if the pixels are still there, so the cost is pushed onto file size and onto whoever reads the file next. Baking the crops into my sample cut the DOCX from 7,234,550 to 5,714,763 bytes, 21.0 % smaller. The privacy cost is harder to spot, because a converter has to understand all three layers to show what the author saw, and the ones I tried each read only part of them.
Converters read only part of the model
mammoth 1.13.0 ignored both the crop and the extent and showed every picture whole. docx-preview 0.4.1 got the crop content and the size right. On uneven trims it pushed the picture off its frame, and the screenshot cropped only at the bottom slid up about 70 px over the heading above it. macOS Quick Look honoured rotation and squeezed the uncropped image into the frame. textutil dropped all seven pictures. For the converter side I used ImgIng (https://imging.ai/) for DOCX to HTML, running the same seven-case sample in a Chromium 149 open-source build. It handled crop content and scaling, made 0 non-GET requests across four conversions, and showed the same drift. Its interface gave no warning about crops. Its export was 9,665,338 bytes, and all seven data URIs were the full originals, byte-identical to the DOCX media and 99.7 % of the file. Anyone who saves that screenshot gets the shipping block back.
This figure uses the same Commons photo under CC BY-SA 4.0. The left half has uneven trims and spills past the right and top edges. The right half has 15 % off every side and sits flush.
I normalize at the entry instead of patching renderers
Fixing this in each renderer means trusting every future converter to read three layers correctly, and CSS can only hide pixels anyway. So the cropped pixels have to be removed before conversion. The first step is a gate that lists which pictures carry a crop or rotation:
with zipfile.ZipFile(sys.argv[1]) as pkg:
xml = pkg.read("word/document.xml").decode("utf-8")
for n, drawing in enumerate(re.findall(r"<w:drawing>.*?</w:drawing>", xml, re.S), 1):
rect = re.search(r"<a:srcRect([^>]*)/>", drawing)
sides = {k: int(v) / 1000 for k, v in re.findall(r'(\w)="(\d+)"', rect[1])} if rect else {}
rot = re.search(r'<a:xfrm[^>]*rot="(\d+)"', drawing)
if any(sides.values()) or rot:
print(n, "trim %", sides, "rot", int(rot[1]) / 60000 if rot else 0)
On my sample it flags five of the seven pictures. Flagged pictures get baked, meaning the media is cut to the srcRect and the crop is cleared. After baking, the ImgIng export fell from 9.22 MB to 7.28 MB, the bottom-cropped screenshot sat at (0, −0.4) px instead of (0, −70.4), and the trimmed region could no longer be found on the page. The trade-off is that baking throws away the reversibility Word paid for, and JPEG gets re-encoded at q92. I keep the author's DOCX untouched and convert only the baked copy. My bake does not yet handle one media file used with two different crops, negative srcRect or floating images. I have also not tested Word, WPS, LibreOffice or pandoc, and I only ran the ImgIng export in Chromium. If your pipeline accepts DOCX uploads, run a gate like this on one real document from your users before the next release.


Top comments (0)