For a side project I'm turning .docx course notes into web pages with mammoth.js, and I wanted to know what happens to pictures cropped in Word. I don't have Word on this laptop, so I wrote a test .docx by hand following the OOXML structure. It has seven pictures made from two images: a landscape photo from Wikimedia Commons, and a screenshot of an order admin page I built myself with placeholder names, phone numbers and addresses. One case crops 35.484% off the bottom of the screenshot to hide the shipping card. mammoth 1.13.0 showed every cropped picture whole, shipping card included.
The docx keeps the whole picture
A .docx is a zip, so I renamed a copy and opened it. The crop doesn't touch the image at all. What sits in word/media/ is the full original, and the crop is a single <a:srcRect> line in document.xml with four edges in thousandths of a percent. Display size lives in wp:extent and rotation in a:xfrm rot. I wrote a few lines of Python to print every picture next to its crop:
import io, re, sys, zipfile
from PIL import Image
with zipfile.ZipFile(sys.argv[1]) as docx:
rels = docx.read("word/_rels/document.xml.rels").decode()
target = {rid: path for rid, path in re.findall(r'Id="(rId\d+)"[^>]*Target="([^"]+)"', rels)}
xml = docx.read("word/document.xml").decode()
for block in re.findall(r"<pic:pic\b.*?</pic:pic>", xml, re.S):
name = "word/" + target[re.search(r'r:embed="(rId\d+)"', block).group(1)]
trim = re.search(r"<a:srcRect([^/]*)/>", block).group(1).strip() or "(none)"
width, height = Image.open(io.BytesIO(docx.read(name))).size
print(f"{name:<22} {width}x{height:<6} srcRect {trim}")
# word/media/image2.jpeg 2736x1824 srcRect l="30000" t="20000" r="20000" b="25000"
# word/media/image4.png 1800x1240 srcRect l="0" t="0" r="0" b="35484"
So b="35484" hides 35.484% from the bottom, and the PNG behind it is still 1800x1240. To be fair, I put the full image there myself when I wrote the file, so this doesn't prove what Word does. Microsoft's support page does say that on macOS you can tick "Delete cropped areas of pictures" to shrink the file, so the cropped part stays unless you ask. I haven't checked what Word does by default on save.
That's case B, cropped 30% left, 20% top, 20% right and 25% bottom. Readers should only see the red frame, but everything outside it is still in the file. The photo is by Dietmar Rabich on Wikimedia Commons, CC BY-SA 4.0, and this derivative figure uses the same licence.
The hidden part ships in the HTML too
Next I tried docx-preview 0.4.1, and for the third run I used ImgIng (https://imging.ai/), its DOCX to HTML export, with the same seven-case .docx in a Chromium 149 open-source build. Both got the visible part right, and both made the same mistake with uneven crops. When only the bottom is cropped, the picture moves up 70.4 px in ImgIng's export (69.7 px in docx-preview) and covers the heading line above it. The case cropped 15% on all four sides didn't shift at all. ImgIng made 0 non-GET requests during conversion, and it showed no warning about crops either. Then I looked inside the output files. ImgIng's export was 9,665,338 bytes, and all seven inline images were the whole originals, byte for byte the same as the media in the docx. The shipping card wasn't visible on the page, but decoding the screenshot's data URI gave back the full 1800x1240 PNG with every placeholder phone number in it. mammoth's output has the full images too, and it shows them.
Bake the crop in before converting
So the fix has to happen in the .docx, before any converter sees it. In Word that's the "Delete cropped areas of pictures" option, which I couldn't try. I did it with a script instead. For each picture with a srcRect, it crops the media file by those four fractions, writes it back and empties the srcRect, so there's nothing left for a converter to get wrong. JPEGs get re-encoded at quality 92 and PNGs stay lossless. I couldn't do this inside mammoth, because its convertImage callback only gets the image bytes and type and never sees the crop. After baking, the docx went from 7,234,550 to 5,714,763 bytes, 21.0% smaller, and the screenshot's media became 1800x800, down from 143,480 to 84,507 bytes.
Check the offset and the hidden part again
Then I converted again and checked two things. The bottom-cropped screenshot that had moved 70.4 px now sits 0.4 px off, and the heading above it is readable. ImgIng's export of the screenshot case alone dropped from 213,557 to 134,899 bytes, and none of the three converters left a shipping card to find, because it isn't in the file anymore.
My script still skips a few cases. It doesn't split one media file used by two pictures with different crops, it ignores negative srcRect values, and I only tested inline pictures, not floating ones. I haven't tried Word, WPS, LibreOffice or pandoc either. For now, before a converted page leaves my laptop, I run that listing on the .docx first, and anything that isn't (none) gets baked.

Top comments (0)