DEV Community

hao jia
hao jia

Posted on

Mapping Word's srcRect crop to CSS and why center scaling drifts


Our internal doc portal turns uploaded DOCX files into HTML, and I was checking what happens to cropped pictures before we let anyone use it. One test picture is a screenshot of an order admin page with the shipping block cropped off the bottom. After conversion with docx-preview 0.4.1 the visible area was correct, but the whole picture sat about 70 px too high and covered the heading above it, so only "Scree" was left. The bigger problem showed up when I looked at what the HTML actually carried. A note on the sample first: the DOCX is one I wrote by hand following the OOXML structure, with seven picture cases, so it is not a file Word saved. The admin screenshot comes from a demo page I built myself, and every name, phone number and address on it is a placeholder.

Three values describe a cropped picture

In word/document.xml a picture carries three things. wp:extent is the size of the frame on the page. a:srcRect holds l, t, r and b, each in thousandths of a percent, so b="35484" means 35.484 % trimmed from the bottom. a:xfrm rot is the rotation in 1/60000 of a degree. The file in word/media/ stays whole. In my sample that is by construction, so it proves nothing about Word itself. Microsoft's support page on reducing picture file size does describe a "Delete cropped areas of pictures" option in Word for Mac, which suggests the trimmed pixels stay unless you tick it. I have no Word on this machine and have not tried it.

Overflow plus negative margins works in all three engines

The mapping I trust is a fixed box at extent size with overflow: hidden, holding the full image scaled up and pulled left and up:

const pct = (v = 0) => v / 100000;
const imgW = boxW / (1 - pct(l) - pct(r)), imgH = boxH / (1 - pct(t) - pct(b));
const imgStyle = { width: imgW, height: imgH, marginLeft: -pct(l) * imgW, marginTop: -pct(t) * imgH };
Enter fullscreen mode Exit fullscreen mode

I rendered the three cropped cases this way and compared each against a reference made by really cropping and resizing the image. Normalized correlation came out 0.967 / 0.998 / 0.958 in a Chromium 149 open-source build, 0.962 / 0.999 / 0.961 in Firefox 151 and 0.956 / 0.994 / 0.961 in WebKit 26.5, which is the engine build and not a Safari release. The shorter route, object-view-box: inset(...), only worked in Chromium. Firefox and WebKit returned false from CSS.supports and showed the whole picture.

Scaling from the center pushes the picture off its frame

The converters I looked at do it differently. Judging from the exported markup, the <img> is sized to extent, clip-path keeps the visible rectangle, and transform: scale() blows it back up. Since transform-origin defaults to the center, the visible part lands off target whenever left and right, or top and bottom, are trimmed by different amounts. The helper takes the four trims as fractions, so 35.484 % is 0.35484:

const drift = (L, T, R, B, w, h) => ({ dx: w * (L - R) / (2 * (1 - L - R)), dy: h * (T - B) / (2 * (1 - T - B)) });
// B -> dx 57.6, dy -19.2   D -> dx 0, dy -70.4   G (15 % each side) -> 0, 0
Enter fullscreen mode Exit fullscreen mode

Measured offsets matched: (58, −19) for case B, −70.4 px for case D. For the converter side I also used ImgIng (https://imging.ai/) for DOCX to HTML, with the same seven-case sample DOCX in the Chromium 149 open-source build. It got the crop content and the scaling right and made 0 non-GET requests during conversion. It showed the same drift as docx-preview, and its interface gave no warning about crops or rotation. Cropping only the bottom, probably the most common way to trim a screenshot, is exactly the case that slides up over the text above it.

Red frame is the Word frame. Left: unequal trims shift the picture right and up. Right: 15 % off every side fits exactly

The photo in this figure is from Wikimedia Commons by Dietmar Rabich, CC BY-SA 4.0, and the figure is shared under the same license. On the left the picture spills past the red frame on the right and top edges. On the right, with all four sides trimmed equally, it sits flush.

docx-preview output for case D: the cropped screenshot moved up and covers the heading

CSS only hides the cropped part

None of this removes anything. The ImgIng export was 9,665,338 bytes, and all seven data URIs were the full originals, byte-identical to the media in the DOCX. Decoding the case D PNG gave 1800×1240 with the shipping details, phone number, address and payment ID all there. The mammoth output carried the same full images and simply displayed them. I once spent three months with legal reviewing what left our network, so this part stopped me more than the drift did.

Bake the crop before converting

What fixed both problems was cropping the media to match srcRect and then clearing srcRect, re-encoding JPEG at q92 and keeping PNG lossless. The DOCX went from 7,234,550 to 5,714,763 bytes, 21.0 % smaller. The ImgIng export dropped from 9.22 MB to 7.28 MB, drift for B and D went to (0, 0) and (0, −0.4), and the trimmed region could no longer be found on the page. My script does not handle one media file referenced with different crops, negative srcRect or floating images. Word, WPS, LibreOffice and pandoc are untested, and I only ran the ImgIng export in Chromium 149. In our pipeline the check now runs before conversion: grep document.xml for a non-zero srcRect, bake those pictures, then compare one cropped picture's position against its frame.

Top comments (0)