雜記 › 技術筆記 › SVG 轉 PNG 後外部引用的圖丟了一半:同一份 594 B 檔案預覽有 3 張圖、成品只剩 1 張

SVG 轉 PNG 後外部引用的圖丟了一半:同一份 594 B 檔案預覽有 3 張圖、成品只剩 1 張

同一份 594 B 的 SVG,預覽裡 3 個外部引用的圖全在、成品 PNG 只剩內嵌那張:預覽與匯出是兩條載入路徑,墨跡各異;另量了尺寸框與 viewBox 的成對覆蓋、超畫布上限的靜默空載荷,以及 12 個族名的字寬差。

預覽裡有 3 張圖,成品只剩 1 張

起點是一份 sv_image_rel.svg:594 B,3 個 <image>,兩條以相對路徑外部引用同一張 64×64 瓦片,第三條是 data: URI 內嵌。預覽裡三張都在,探針記到 3 次 load;轉成 PNG 拿回來的成品 2576 B,外部引用那一塊墨跡 px=0,只剩內嵌那條的 4096 px。差的是兩套載入機制。

行內 DOM 與 Blob→Image→drawImage

把檔案交給SVG 轉換格式之後,「載入」與「轉換並下載」是兩段不相交的鏈路。預覽側(行 121-168):DOMParser 解析字串,取第一個 <svg> 嵌進文件,不建 Image 也不建 Blob URL,外部引用按一般網頁資源去取。匯出側(行 200-255):字串→Blob→objectURL→new Image→onload 後 drawImage 進 canvas。以圖片解碼時這份文件是 opaque origin,內部引用另算一套,分岔就是從這裡來的。

四類內部依賴,兩側讀數不同

跨檔案 <use>(249 B)在 DOM 側觸發了 load,點陣側卻是整幅 px=0。外部引用的 @import(284 B)給 64px 文字定的規則沒生效,computed 回落成 -apple-system、14px,長度讀數 85.83;同一條規則改成行內(332 B),讀數是 475.06。成品側同樣分裂:外部引用那份匯出 4972 B 的 PNG,墨跡只有 692 px。

外部引用的 @font-face(322 B)與同一個字型以 data: 內嵌(612547 B)在 DOM 欄同寬,兩邊讀數都是 400.55;點陣欄卻是 px=5401、bbox 315×49 對 px=4855、314×43。fill:url(外部圖) 那一份 256 B:DOM 裡 fill 的計算值原樣帶著 url,點陣側整幅 px=0,成品 2544 B 是一張純空圖,toast 照樣報成功。

這些不能寫成「外部引用依賴實測不支援」。本機 Chrome 是以 --allow-file-access-from-files 啟動的,改成 file:/// 與 https:// 絕對路徑外部引用的兩份樣張,匯出時反而都進了點陣,ink px=3327,成品同為 8075 B。所以能寫的只有:預覽與匯出這兩條路徑的墨跡不同,而無頭環境未能觸發攔截——不外推成線上口徑。

四類依賴:DOM 欄 對 點陣欄DOM 側點陣側外部引用 image(594 B)3 個 onloadpx=0 ・ 2576 B外部引用 @import85.83 對 475.06692 px ・ 4972 B外部引用 @font-face兩邊都 400.555401 對 4855 px外部圖作 fillrect 221x221px=0 ・ 2544 B同機同碼,兩側墨跡不同

圖 1 四類內部依賴在兩條路徑下的讀數

兩個尺寸框與 viewBox 的成對覆蓋

convert-out-w / convert-out-h 寫的是畫布像素,跟 SVG 自身只在「原始尺寸」那一步有關:viewBox 存在且 split 出 4 段、而寬或高缺位時,兩個值會成對被它的第 3、4 段頂掉;兩個屬性都寫時 viewBox 完全不參與;匯出側則只讀框。max="10000" 只是個 HTML 屬性,程式碼不讀它——勾了等比時把高打 10000,寬被反算成 13333 寫進框裡;探針寫 100000 也原樣回讀,報錯位是 null。

svg 根寫法 → 兩框讀數(解析在行 131-141)
svg 根上的寫法兩框讀數取哪一邊
只有 viewBox="0 0 320 240"320 × 240缺位時 viewBox 頂上來
width="100" + viewBox320 × 240寫進去的 100 被成對丟掉
width="320" height="240" + viewBox="0 0 100 50"320 × 240兩個都在,viewBox 不參與

取整方向也不統一:origin 與等比反算用 Math.round,匯出取值卻用 parseInt,同一個框裡 "333.7" 被截成 333、"1e3" 被截成 1;寫 0 或留空都是假值,一律退回 origin。

超過畫布上限,靜默交出空載荷

16385×16385 與 20000×20000 時,data URL 只剩 6 個字元,載荷就是 "data:,";toast 仍是「已轉換為 PNG (20000×20000) 並開始下載」。上限之內才是真位元組:320×240 基準圖出 35990 B,6000×6000 是 697011 B,10000×10000 是 1889260 B,魔數全是 89 50 4e 47 0d 0a 1a 0a。

字寬這筆帳:點陣一律裁到 320

"VECTOR-0123" @64,在 12 個 font-family 下的 DOM 長度讀數從 352.00(monospace 與宋體)一路排到 484.97(Arial Black),差 132.97;族名不存在也不報錯,落回繼承那一檔的 392.44。DOM 裡的 rect 還要再乘上預覽縮放,預覽因此看起來剛剛好;320 寬的畫布則把點陣墨跡一律裁到 ≤320。

12 個 font-family 的 DOM 長度讀數 @64pxArial Black484.97Arial429.22Microsoft YaHei426.63Courier New422.47Times New Roman404.16Segoe UI392.44NoSuchFace 9000392.44Consolas387.07monospace352.00宋體352.00虛線=點陣畫布的 320 上限,十條讀數全超它

圖 2 族名字寬譜,與 320 寬畫布的鉛直上限

單次處理與 object URL 回收

這一頁沒有批次入口:fileInputMultiple=false,拖曳與 change 兩處(行 104、107)都只取 files[0]。整輪 13 步測得 object URL made=11 / revoked=11,一次匯出就是 1 造 1 銷,沒有累積。「該轉多大、存成什麼格式」是另一筆帳,SVG 轉 PNG 的匯出尺寸與格式選擇那篇已經拆過,這裡不複述。

幾個常見追問

Q:預覽和成品對不上,先查哪裡?

查 SVG 裡指向外部的引用:<image> 的相對路徑、跨檔案 <use>、外部引用 @import / @font-face、fill:url(外部圖)。它們都活在 DOM 裡,點陣化時是另算一套。

Q:框裡寫大數沒報錯,是不是就安全?

不是。max 屬性和報錯位都攔不住寫入,13333 照樣進框;越過畫布上限也只不過是 6 字元的空載荷配上一則成功 toast,得自己核對成品位元組與魔數。

Q:成品文字比預覽窄?

font-family 依賴本機字型,12 族的讀數能差 132.97;320 寬的畫布再把點陣一律裁到 ≤320。要穩:匯出前把規則改成行內、字型以 data: 內嵌。

📎 相關文章

16 組開關實測 HTML 去標籤:報 19 個標籤,實際刪掉 148 個字元 2026-10-30 二進位轉 Base64 這一頁:前綴只有 5 檔,換行能解、連字號和底線直接報錯 2026-10-30 替應用做商店介紹圖,點匯出卻拿到 1242×2688:8 檔畫布預設的像素帳、噪點 24.83 倍體積與預設被裁掉 134.93 px 的手機框 2026-10-30 16 個鍵進、4 個欄出:JSON 轉表格的表頭聯集、點號展平與 CSV 引號規則 2026-10-30 取消勾選「保留註解」後,format-js 把 24 字元 URL 壓成 13 字元殘缺碼 2026-10-30