Preview Shows 3 Images, the Export Keeps One
It starts from one file, sv_image_rel.svg: 594 bytes holding 3 <image> elements. Two pull the same 64 x 64 tile in through a relative path; the third is inline as a data: URI. All three are drawn in the preview pane, where the probe logs 3 load events. The PNG that comes back weighs 2,576 bytes, and the region where the linked tiles belong measures px=0 of ink; only the inline element survives, worth 4,096 px. The file did not change in between: the page reads it through two mechanisms that were never the same.
Inline DOM Versus Blob, Image and drawImage
On the SVG Converter page, "load" and "convert and download" are two chains with no shared segment. The preview chain (lines 121-168 of the template) hands the string to DOMParser, takes the first <svg> and places it inline in the document, building no Image object and no Blob URL, so an external reference is fetched like any other resource of an ordinary page. The export chain (lines 200-255) is a pipeline instead: string, Blob, objectURL, new Image, and only once that image fires onload does drawImage copy it into a canvas. Decoding the same markup as an image puts it in an opaque origin, where internal references follow a second set of rules and the canvas keeps only what that resolution produced.
Four Internal Dependencies, Two Sets of Numbers
A cross-file <use> is the cleanest case: the 249 byte sample fires load on the DOM side, so the preview shows the referenced shape, while the raster side returns px=0 for the whole frame. An external @import fails differently but splits the same way: the 284 byte sample carries a rule written for a 64px text, the rule never reaches the rasterized document, and the computed value falls back to -apple-system at 14px. Text length measures 85.83 there against 475.06 once the identical rule is inlined in a 332 byte file, because the fallback face is smaller and narrower. The export matches: the linked version writes out 4,972 bytes whose ink is only 692 px.
An external @font-face shows the split without changing a letter. The 322 byte sample and a file embedding the same face as a data: URI, 612,547 bytes of it, come out the same width in the DOM column: both measure 400.55, so the previews are indistinguishable. In the raster column they are not one drawing: one ends at px=5401 with a bbox of 315 x 49, the other at px=4855 with a bbox of 314 x 43, because only one of the two resolves its font through the document.
The fourth gives no warning: the 256 byte sample paints a rect with fill:url(...) at an external image, the DOM keeps that url verbatim as the computed fill, and previewing shows nothing. On the raster side the whole canvas measures px=0, the export hands back a 2,544 byte picture with nothing in it, and the toast still reports success.
None of this supports the sentence "external references are measured as unsupported". This Chrome was launched with --allow-file-access-from-files, and the two samples rewritten to absolute file:/// and https:// targets were rasterized after all: ink px=3327, the same 8,075 byte PNG twice. What they support is narrower: preview and export draw different ink from one and the same file, the headless setup never triggered a block here, and this does not extrapolate to the live site.
Figure 1: the four dependencies read on both paths.
Two Size Boxes, Overwritten by viewBox as a Pair
convert-out-w and convert-out-h hold canvas pixels, and they meet the SVG's own geometry at one moment only, the step reading the "original size". The rule there is paired rather than per field. If a viewBox exists and splits into 4 numbers while either the width or the height is missing from the root, both boxes are overwritten together by its third and fourth numbers. If both are present, viewBox takes no part, and the export path reads only the boxes. max="10000" is a plain HTML attribute the code never reads, which is why the arithmetic walks past it: with keep-ratio on, typing 10000 into the height makes the tool back-calculate a width of 13333 into the other box, and a probe writing 100000 reads back unchanged with the error field still null.
| Written on the svg root | Both boxes read | Which side wins |
|---|---|---|
| viewBox="0 0 320 240" only | 320 x 240 | viewBox fills a missing side |
| width="100" plus viewBox | 320 x 240 | the typed 100 drops with its pair |
| width="320" height="240" plus viewBox="0 0 100 50" | 320 x 240 | both present, viewBox loses |
Rounding splits the same way: origin and the keep-ratio back-calculation use Math.round, the export reads the box values with parseInt, so in one and the same box "333.7" truncates to 333 and "1e3" collapses to 1. A field written as 0 or left empty counts as falsy and falls back to origin.
Past the Canvas Limit, a Silent Empty Payload
Asking for 16385 x 16385, or 20000 x 20000, raises no error and produces no picture. The data URL is down to 6 characters, the payload is literally "data:,", and the toast still announces "Converted to PNG (20000x20000) and started downloading". So the byte count is the only check left. Inside the limit the bytes are real: the 320 x 240 reference image exports 35,990 bytes, 6000 x 6000 gives 697,011, 10000 x 10000 gives 1,889,260, all opening with the PNG magic number 89 50 4e 47 0d 0a 1a 0a.
The Width Account: the Raster Clipped to 320
Text width is the third axis of disagreement. The string "VECTOR-0123" was measured at 64 against the 12 font-family values on this machine, and DOM text length runs from 352.00 (monospace and SimSun) to 484.97 (Arial Black), a spread of 132.97 units over the same eleven characters. A name that does not exist warns nobody: it falls through to inheritance, landing on the 392.44 that Segoe UI measures. Two further steps hide it in preview. The rect in the DOM is multiplied by the preview's own zoom, so an over-wide run still fits the pane; a canvas 320 wide clips every raster run to 320 or less.
Figure 2: family widths against the 320 ceiling of the canvas.
One File at a Time, and object URL Reclaim
There is no batch entry here. fileInputMultiple is false, and both the drop handler and the change handler (lines 104 and 107) take only files[0]: a second file is never read. What is created is released cleanly, and a full 13-step run records object URL made=11 and revoked=11, one created and one revoked per export. How large to convert and which format to keep is a separate account, settled in the piece on SVG to PNG export size and format choice.
Common Questions
Q: Preview and export disagree, what do I check first?
Check every reference that points outside the file: an <image> with a relative path, a cross-file <use>, an external @import or @font-face, and fill:url(...) as a paint source. All four live in the DOM, and the rasterizing chain resolves them again on its own, so a clean preview says nothing about the exported pixels.
Q: The size box took a huge number without complaining, is that safe?
No. Neither the max attribute nor the error field blocks a write, so 13333 lands in the box anyway; past the canvas limit there is still no error, only a 6-character empty payload wearing a success toast. Verify the exported bytes and the magic number first.
Q: Why is the text in the export narrower than in the preview?
font-family resolves against the fonts installed on the machine, so the readings across 12 families spread by 132.97, and a canvas 320 wide clips the raster to 320 or less. Inline the CSS rules and embed the font as a data: URI.