@zseven-w/dsh-openpencil-linux-arm64 0.1.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/LICENSE +21 -0
  2. package/THIRD_PARTY_NOTICES.md +72 -0
  3. package/bin/op-host-web-server +0 -0
  4. package/openpencil-runtime.json +211 -0
  5. package/package.json +34 -0
  6. package/web/canvaskit/LICENSE +29 -0
  7. package/web/canvaskit/README.md +21 -0
  8. package/web/canvaskit/canvaskit.js +288 -0
  9. package/web/canvaskit/canvaskit.wasm +0 -0
  10. package/web/pkg/assets/iconify-catalog-core.json +1 -0
  11. package/web/pkg/assets/prompt_center_previews/component-data-grid.jpg +0 -0
  12. package/web/pkg/assets/prompt_center_previews/component-form-lab.jpg +0 -0
  13. package/web/pkg/assets/prompt_center_previews/dashboard-pulse.jpg +0 -0
  14. package/web/pkg/assets/prompt_center_previews/dashboard-sentinel.jpg +0 -0
  15. package/web/pkg/assets/prompt_center_previews/extreme-calendar.jpg +0 -0
  16. package/web/pkg/assets/prompt_center_previews/extreme-calm.jpg +0 -0
  17. package/web/pkg/assets/prompt_center_previews/extreme-daily-app.jpg +0 -0
  18. package/web/pkg/assets/prompt_center_previews/extreme-now-playing.jpg +0 -0
  19. package/web/pkg/assets/prompt_center_previews/extreme-weather.jpg +0 -0
  20. package/web/pkg/assets/prompt_center_previews/freeform-aloft.jpg +0 -0
  21. package/web/pkg/assets/prompt_center_previews/freeform-bloom.jpg +0 -0
  22. package/web/pkg/assets/prompt_center_previews/freeform-companion.jpg +0 -0
  23. package/web/pkg/assets/prompt_center_previews/freeform-daybreak.jpg +0 -0
  24. package/web/pkg/assets/prompt_center_previews/freeform-ember.jpg +0 -0
  25. package/web/pkg/assets/prompt_center_previews/freeform-forage.jpg +0 -0
  26. package/web/pkg/assets/prompt_center_previews/freeform-gallery.jpg +0 -0
  27. package/web/pkg/assets/prompt_center_previews/freeform-hearth.jpg +0 -0
  28. package/web/pkg/assets/prompt_center_previews/freeform-lingua.jpg +0 -0
  29. package/web/pkg/assets/prompt_center_previews/freeform-marginalia.jpg +0 -0
  30. package/web/pkg/assets/prompt_center_previews/freeform-marquee.jpg +0 -0
  31. package/web/pkg/assets/prompt_center_previews/freeform-meteo.jpg +0 -0
  32. package/web/pkg/assets/prompt_center_previews/freeform-nightcap.jpg +0 -0
  33. package/web/pkg/assets/prompt_center_previews/freeform-nocturne.jpg +0 -0
  34. package/web/pkg/assets/prompt_center_previews/freeform-relic.jpg +0 -0
  35. package/web/pkg/assets/prompt_center_previews/freeform-ritual.jpg +0 -0
  36. package/web/pkg/assets/prompt_center_previews/freeform-still.jpg +0 -0
  37. package/web/pkg/assets/prompt_center_previews/freeform-verdant.jpg +0 -0
  38. package/web/pkg/assets/prompt_center_previews/freeform-volt.jpg +0 -0
  39. package/web/pkg/assets/prompt_center_previews/freeform-wander.jpg +0 -0
  40. package/web/pkg/assets/prompt_center_previews/gallery-aloft.jpg +0 -0
  41. package/web/pkg/assets/prompt_center_previews/gallery-bloom.jpg +0 -0
  42. package/web/pkg/assets/prompt_center_previews/gallery-companion.jpg +0 -0
  43. package/web/pkg/assets/prompt_center_previews/gallery-daybreak.jpg +0 -0
  44. package/web/pkg/assets/prompt_center_previews/gallery-ember.jpg +0 -0
  45. package/web/pkg/assets/prompt_center_previews/gallery-forage.jpg +0 -0
  46. package/web/pkg/assets/prompt_center_previews/gallery-gallery.jpg +0 -0
  47. package/web/pkg/assets/prompt_center_previews/gallery-hearth.jpg +0 -0
  48. package/web/pkg/assets/prompt_center_previews/gallery-lingua.jpg +0 -0
  49. package/web/pkg/assets/prompt_center_previews/gallery-marginalia.jpg +0 -0
  50. package/web/pkg/assets/prompt_center_previews/gallery-marquee.jpg +0 -0
  51. package/web/pkg/assets/prompt_center_previews/gallery-meteo.jpg +0 -0
  52. package/web/pkg/assets/prompt_center_previews/gallery-nightcap.jpg +0 -0
  53. package/web/pkg/assets/prompt_center_previews/gallery-nocturne.jpg +0 -0
  54. package/web/pkg/assets/prompt_center_previews/gallery-relic.jpg +0 -0
  55. package/web/pkg/assets/prompt_center_previews/gallery-ritual.jpg +0 -0
  56. package/web/pkg/assets/prompt_center_previews/gallery-still.jpg +0 -0
  57. package/web/pkg/assets/prompt_center_previews/gallery-verdant.jpg +0 -0
  58. package/web/pkg/assets/prompt_center_previews/gallery-volt.jpg +0 -0
  59. package/web/pkg/assets/prompt_center_previews/gallery-wander.jpg +0 -0
  60. package/web/pkg/assets/prompt_center_previews/modify-complete-states.jpg +0 -0
  61. package/web/pkg/assets/prompt_center_previews/modify-polish-current.jpg +0 -0
  62. package/web/pkg/assets/prompt_center_previews/starter-barbershop.jpg +0 -0
  63. package/web/pkg/assets/prompt_center_previews/starter-coffee-shop.jpg +0 -0
  64. package/web/pkg/assets/prompt_center_previews/starter-dashboard.jpg +0 -0
  65. package/web/pkg/assets/prompt_center_previews/starter-travel-app.jpg +0 -0
  66. package/web/pkg/assets/prompt_center_previews/web-atelier.jpg +0 -0
  67. package/web/pkg/assets/prompt_center_previews/web-kilnform.jpg +0 -0
  68. package/web/pkg/assets/prompt_center_previews/web-orbit.jpg +0 -0
  69. package/web/pkg/assets/prompt_center_previews/web-reefwright.jpg +0 -0
  70. package/web/pkg/assets/scene_template_previews/app-onboarding-triptych.jpg +0 -0
  71. package/web/pkg/assets/scene_template_previews/banxin-rule-deck.jpg +0 -0
  72. package/web/pkg/assets/scene_template_previews/before-after.jpg +0 -0
  73. package/web/pkg/assets/scene_template_previews/bookreview-silk-carousel.jpg +0 -0
  74. package/web/pkg/assets/scene_template_previews/brand-concept-sheet.jpg +0 -0
  75. package/web/pkg/assets/scene_template_previews/challenge-tracker-infographic.jpg +0 -0
  76. package/web/pkg/assets/scene_template_previews/cityguide-film-carousel.jpg +0 -0
  77. package/web/pkg/assets/scene_template_previews/concept-contrast-infographic.jpg +0 -0
  78. package/web/pkg/assets/scene_template_previews/daily-sign-card.jpg +0 -0
  79. package/web/pkg/assets/scene_template_previews/data-report-infographic.jpg +0 -0
  80. package/web/pkg/assets/scene_template_previews/data-story-infographic.jpg +0 -0
  81. package/web/pkg/assets/scene_template_previews/datareport-grid-carousel.jpg +0 -0
  82. package/web/pkg/assets/scene_template_previews/diy-blueprint-guide.jpg +0 -0
  83. package/web/pkg/assets/scene_template_previews/do-dont-comparison.jpg +0 -0
  84. package/web/pkg/assets/scene_template_previews/dossier-linen-deck.jpg +0 -0
  85. package/web/pkg/assets/scene_template_previews/ecosystem-map-infographic.jpg +0 -0
  86. package/web/pkg/assets/scene_template_previews/event-poster-deck.jpg +0 -0
  87. package/web/pkg/assets/scene_template_previews/faq-thread-infographic.jpg +0 -0
  88. package/web/pkg/assets/scene_template_previews/gradient-tech.jpg +0 -0
  89. package/web/pkg/assets/scene_template_previews/gridpaper-graphite-deck.jpg +0 -0
  90. package/web/pkg/assets/scene_template_previews/journal-checklist-card.jpg +0 -0
  91. package/web/pkg/assets/scene_template_previews/knowledge-card-square.jpg +0 -0
  92. package/web/pkg/assets/scene_template_previews/knowledge-card-vertical.jpg +0 -0
  93. package/web/pkg/assets/scene_template_previews/knowledge-carousel.jpg +0 -0
  94. package/web/pkg/assets/scene_template_previews/lecture-deck-light.jpg +0 -0
  95. package/web/pkg/assets/scene_template_previews/ledger-tick-deck.jpg +0 -0
  96. package/web/pkg/assets/scene_template_previews/logo-qa-board.jpg +0 -0
  97. package/web/pkg/assets/scene_template_previews/metric-single-card.jpg +0 -0
  98. package/web/pkg/assets/scene_template_previews/milestone-timeline-infographic.jpg +0 -0
  99. package/web/pkg/assets/scene_template_previews/minimal-keynote.jpg +0 -0
  100. package/web/pkg/assets/scene_template_previews/myth-truth-comparison.jpg +0 -0
  101. package/web/pkg/assets/scene_template_previews/notice-board-card.jpg +0 -0
  102. package/web/pkg/assets/scene_template_previews/opinion-longform-carousel.jpg +0 -0
  103. package/web/pkg/assets/scene_template_previews/photo-composition-tutorial.jpg +0 -0
  104. package/web/pkg/assets/scene_template_previews/pitch-deck-dark.jpg +0 -0
  105. package/web/pkg/assets/scene_template_previews/pitfall-list-infographic.jpg +0 -0
  106. package/web/pkg/assets/scene_template_previews/price-tier-card.jpg +0 -0
  107. package/web/pkg/assets/scene_template_previews/pricing-tiers-comparison.jpg +0 -0
  108. package/web/pkg/assets/scene_template_previews/product-landing-light.jpg +0 -0
  109. package/web/pkg/assets/scene_template_previews/punch-quote-card.jpg +0 -0
  110. package/web/pkg/assets/scene_template_previews/qa-chalkboard-carousel.jpg +0 -0
  111. package/web/pkg/assets/scene_template_previews/quote-frame-card.jpg +0 -0
  112. package/web/pkg/assets/scene_template_previews/ranking-board-infographic.jpg +0 -0
  113. package/web/pkg/assets/scene_template_previews/recipe-four-step.jpg +0 -0
  114. package/web/pkg/assets/scene_template_previews/saas-landing-orange.jpg +0 -0
  115. package/web/pkg/assets/scene_template_previews/scenario-guide-comparison.jpg +0 -0
  116. package/web/pkg/assets/scene_template_previews/screenshot-tutorial.jpg +0 -0
  117. package/web/pkg/assets/scene_template_previews/skincare-routine-cards.jpg +0 -0
  118. package/web/pkg/assets/scene_template_previews/slide-deck.jpg +0 -0
  119. package/web/pkg/assets/scene_template_previews/software-step-tutorial.jpg +0 -0
  120. package/web/pkg/assets/scene_template_previews/sounding-navy-deck.jpg +0 -0
  121. package/web/pkg/assets/scene_template_previews/spec-table-comparison.jpg +0 -0
  122. package/web/pkg/assets/scene_template_previews/spine-culture-card.jpg +0 -0
  123. package/web/pkg/assets/scene_template_previews/steps-flow-infographic.jpg +0 -0
  124. package/web/pkg/assets/scene_template_previews/storage-makeover-steps.jpg +0 -0
  125. package/web/pkg/assets/scene_template_previews/story-night-carousel.jpg +0 -0
  126. package/web/pkg/assets/scene_template_previews/three-way-comparison.jpg +0 -0
  127. package/web/pkg/assets/scene_template_previews/tidemark-slate-deck.jpg +0 -0
  128. package/web/pkg/assets/scene_template_previews/time-shift-comparison.jpg +0 -0
  129. package/web/pkg/assets/scene_template_previews/toolkit-notebook-carousel.jpg +0 -0
  130. package/web/pkg/assets/scene_template_previews/tradeoff-scale-comparison.jpg +0 -0
  131. package/web/pkg/assets/scene_template_previews/tutorial-journal-carousel.jpg +0 -0
  132. package/web/pkg/assets/scene_template_previews/version-diff-comparison.jpg +0 -0
  133. package/web/pkg/assets/scene_template_previews/weekly-report-lesson.jpg +0 -0
  134. package/web/pkg/assets/scene_template_previews/workout-breakdown-guide.jpg +0 -0
  135. package/web/pkg/assets/scene_template_previews/yearreview-mineral-carousel.jpg +0 -0
  136. package/web/pkg/assets/scene_templates/app-onboarding-triptych.op +1087 -0
  137. package/web/pkg/assets/scene_templates/banxin-rule-deck.op +2702 -0
  138. package/web/pkg/assets/scene_templates/before-after.op +753 -0
  139. package/web/pkg/assets/scene_templates/bookreview-silk-carousel.op +3272 -0
  140. package/web/pkg/assets/scene_templates/brand-concept-sheet.op +1 -0
  141. package/web/pkg/assets/scene_templates/challenge-tracker-infographic.op +2448 -0
  142. package/web/pkg/assets/scene_templates/cityguide-film-carousel.op +13691 -0
  143. package/web/pkg/assets/scene_templates/concept-contrast-infographic.op +1433 -0
  144. package/web/pkg/assets/scene_templates/daily-sign-card.op +370 -0
  145. package/web/pkg/assets/scene_templates/data-report-infographic.op +1946 -0
  146. package/web/pkg/assets/scene_templates/data-story-infographic.op +1916 -0
  147. package/web/pkg/assets/scene_templates/datareport-grid-carousel.op +2799 -0
  148. package/web/pkg/assets/scene_templates/diy-blueprint-guide.op +2744 -0
  149. package/web/pkg/assets/scene_templates/do-dont-comparison.op +1170 -0
  150. package/web/pkg/assets/scene_templates/dossier-linen-deck.op +7045 -0
  151. package/web/pkg/assets/scene_templates/ecosystem-map-infographic.op +2015 -0
  152. package/web/pkg/assets/scene_templates/event-poster-deck.op +2756 -0
  153. package/web/pkg/assets/scene_templates/faq-thread-infographic.op +1698 -0
  154. package/web/pkg/assets/scene_templates/gradient-tech.op +3322 -0
  155. package/web/pkg/assets/scene_templates/gridpaper-graphite-deck.op +11996 -0
  156. package/web/pkg/assets/scene_templates/journal-checklist-card.op +764 -0
  157. package/web/pkg/assets/scene_templates/knowledge-card-square.op +560 -0
  158. package/web/pkg/assets/scene_templates/knowledge-card-vertical.op +719 -0
  159. package/web/pkg/assets/scene_templates/knowledge-carousel.op +1640 -0
  160. package/web/pkg/assets/scene_templates/lecture-deck-light.op +5211 -0
  161. package/web/pkg/assets/scene_templates/ledger-tick-deck.op +6756 -0
  162. package/web/pkg/assets/scene_templates/logo-qa-board.op +1 -0
  163. package/web/pkg/assets/scene_templates/metric-single-card.op +259 -0
  164. package/web/pkg/assets/scene_templates/milestone-timeline-infographic.op +2116 -0
  165. package/web/pkg/assets/scene_templates/minimal-keynote.op +1085 -0
  166. package/web/pkg/assets/scene_templates/myth-truth-comparison.op +1315 -0
  167. package/web/pkg/assets/scene_templates/notice-board-card.op +671 -0
  168. package/web/pkg/assets/scene_templates/opinion-longform-carousel.op +2492 -0
  169. package/web/pkg/assets/scene_templates/photo-composition-tutorial.op +2558 -0
  170. package/web/pkg/assets/scene_templates/pitch-deck-dark.op +3020 -0
  171. package/web/pkg/assets/scene_templates/pitfall-list-infographic.op +1646 -0
  172. package/web/pkg/assets/scene_templates/price-tier-card.op +647 -0
  173. package/web/pkg/assets/scene_templates/pricing-tiers-comparison.op +1762 -0
  174. package/web/pkg/assets/scene_templates/product-landing-light.op +10717 -0
  175. package/web/pkg/assets/scene_templates/punch-quote-card.op +438 -0
  176. package/web/pkg/assets/scene_templates/qa-chalkboard-carousel.op +2568 -0
  177. package/web/pkg/assets/scene_templates/quote-frame-card.op +1233 -0
  178. package/web/pkg/assets/scene_templates/ranking-board-infographic.op +1945 -0
  179. package/web/pkg/assets/scene_templates/recipe-four-step.op +1177 -0
  180. package/web/pkg/assets/scene_templates/saas-landing-orange.op +6616 -0
  181. package/web/pkg/assets/scene_templates/scenario-guide-comparison.op +1721 -0
  182. package/web/pkg/assets/scene_templates/screenshot-tutorial.op +1 -0
  183. package/web/pkg/assets/scene_templates/skincare-routine-cards.op +3240 -0
  184. package/web/pkg/assets/scene_templates/slide-deck.op +2475 -0
  185. package/web/pkg/assets/scene_templates/software-step-tutorial.op +797 -0
  186. package/web/pkg/assets/scene_templates/sounding-navy-deck.op +4292 -0
  187. package/web/pkg/assets/scene_templates/spec-table-comparison.op +2033 -0
  188. package/web/pkg/assets/scene_templates/spine-culture-card.op +647 -0
  189. package/web/pkg/assets/scene_templates/steps-flow-infographic.op +1664 -0
  190. package/web/pkg/assets/scene_templates/storage-makeover-steps.op +2912 -0
  191. package/web/pkg/assets/scene_templates/story-night-carousel.op +2814 -0
  192. package/web/pkg/assets/scene_templates/three-way-comparison.op +1917 -0
  193. package/web/pkg/assets/scene_templates/tidemark-slate-deck.op +5716 -0
  194. package/web/pkg/assets/scene_templates/time-shift-comparison.op +1127 -0
  195. package/web/pkg/assets/scene_templates/toolkit-notebook-carousel.op +5335 -0
  196. package/web/pkg/assets/scene_templates/tradeoff-scale-comparison.op +995 -0
  197. package/web/pkg/assets/scene_templates/tutorial-journal-carousel.op +4425 -0
  198. package/web/pkg/assets/scene_templates/version-diff-comparison.op +1001 -0
  199. package/web/pkg/assets/scene_templates/weekly-report-lesson.op +2188 -0
  200. package/web/pkg/assets/scene_templates/workout-breakdown-guide.op +2579 -0
  201. package/web/pkg/assets/scene_templates/yearreview-mineral-carousel.op +4367 -0
  202. package/web/pkg/op_host_web.js +1971 -0
  203. package/web/pkg/op_host_web_bg.wasm +0 -0
  204. package/web/pkg/snippets/op-host-web-f712ff5933a5d456/src/figma_temp_worker.js +494 -0
  205. package/web/pkg/snippets/op-host-web-f712ff5933a5d456/src/op_ck_bridge.js +1712 -0
  206. package/web/pkg/snippets/op-host-web-f712ff5933a5d456/src/op_ck_image_cache.js +404 -0
@@ -0,0 +1,404 @@
1
+ // CanvasKit image residency and thumbnail drawing.
2
+ //
3
+ // Full document images and blur-up thumbnails intentionally use independent
4
+ // LRU budgets. A large document therefore cannot evict every thumbnail, and
5
+ // a thumbnail-heavy document cannot consume the full-resolution raster budget.
6
+
7
+ const FULL_IMAGE_CACHE_CAP = 4096;
8
+ const FULL_IMAGE_CACHE_BYTE_BUDGET = 384 * 1024 * 1024;
9
+ const THUMBNAIL_CACHE_CAP = 1024;
10
+ const THUMBNAIL_CACHE_BYTE_BUDGET = 4 * 1024 * 1024;
11
+ const THUMBNAIL_ENCODED_BYTE_LIMIT = 4 * 1024;
12
+ const THUMBNAIL_MAX_EDGE = 32;
13
+ const THUMBNAIL_FAILURE_CAP = 1024;
14
+ const JPEG_SOF_MARKERS = new Set([
15
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7,
16
+ 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
17
+ ]);
18
+
19
+ // Read JPEG SOF metadata before CanvasKit expands pixels. The persisted table
20
+ // is untrusted input: a very compressible, multi-megapixel JPEG can fit under
21
+ // the 4 KiB encoded limit but is not safe to decode synchronously in paint.
22
+ const jpegFitsThumbnailBounds = (jpeg) => {
23
+ if (!jpeg || jpeg.byteLength < 4 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8) {
24
+ return false;
25
+ }
26
+ let offset = 2;
27
+ while (offset < jpeg.byteLength) {
28
+ while (offset < jpeg.byteLength && jpeg[offset] === 0xff) offset += 1;
29
+ if (offset >= jpeg.byteLength) return false;
30
+ const marker = jpeg[offset++];
31
+ if (marker === 0xd9 || marker === 0xda) return false;
32
+ if (marker === 0x01 || marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7)) {
33
+ continue;
34
+ }
35
+ if (offset + 1 >= jpeg.byteLength) return false;
36
+ const length = (jpeg[offset] << 8) | jpeg[offset + 1];
37
+ if (length < 2 || offset + length > jpeg.byteLength) return false;
38
+ if (JPEG_SOF_MARKERS.has(marker)) {
39
+ if (length < 7) return false;
40
+ const height = (jpeg[offset + 3] << 8) | jpeg[offset + 4];
41
+ const width = (jpeg[offset + 5] << 8) | jpeg[offset + 6];
42
+ return (
43
+ width > 0 &&
44
+ height > 0 &&
45
+ width <= THUMBNAIL_MAX_EDGE &&
46
+ height <= THUMBNAIL_MAX_EDGE
47
+ );
48
+ }
49
+ offset += length;
50
+ }
51
+ return false;
52
+ };
53
+
54
+ const imageKey = (lo, hi) => String(hi >>> 0) + ':' + String(lo >>> 0);
55
+
56
+ const copyBytes = (bytes) =>
57
+ bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
58
+
59
+ const decodedRasterBytes = (image) => {
60
+ const width = image.width();
61
+ const height = image.height();
62
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
63
+ return 0;
64
+ }
65
+ return width * height * 4;
66
+ };
67
+
68
+ const deleteImage = (image) => {
69
+ if (image && image.delete) image.delete();
70
+ };
71
+
72
+ // SVG has no magic bytes — sniff the markup (first 4 KiB decode as text and
73
+ // contain an `<svg` tag). Mirrors the native seam's sniff in
74
+ // `op-editor-ui/.../svg_raster.rs`.
75
+ const sniffsAsSvg = (bytes) => {
76
+ try {
77
+ const head = new TextDecoder().decode(
78
+ bytes.subarray(0, Math.min(bytes.byteLength, 4096)),
79
+ );
80
+ const trimmed = head.replace(/^/, '').trimStart();
81
+ return trimmed.startsWith('<') && trimmed.includes('<svg');
82
+ } catch (_error) {
83
+ return false;
84
+ }
85
+ };
86
+
87
+ const SVG_RASTER_MAX_EDGE = 2048;
88
+ const SVG_RASTER_SCALE = 2;
89
+ const SVG_FAILURE_CAP = 1024;
90
+
91
+ export function createWebImageCaches(CK) {
92
+ const fullImageCache = new Map();
93
+ let fullImageCacheBytes = 0;
94
+ const thumbnailCache = new Map();
95
+ let thumbnailCacheBytes = 0;
96
+ const thumbnailFailures = new Set();
97
+ // SVG rasterizations in flight / permanently failed. Pending keys report
98
+ // decode success to the Rust side so the id is not negative-cached; paint
99
+ // keeps re-requesting until the browser's async decode lands the raster
100
+ // in `fullImageCache`.
101
+ const svgPending = new Set();
102
+ const svgFailures = new Set();
103
+
104
+ const rememberSvgFailure = (key) => {
105
+ if (svgFailures.size >= SVG_FAILURE_CAP) {
106
+ svgFailures.delete(svgFailures.values().next().value);
107
+ }
108
+ svgFailures.add(key);
109
+ };
110
+
111
+ const installRasterizedSvg = (key, img) => {
112
+ const sourceW = img.naturalWidth || img.width;
113
+ const sourceH = img.naturalHeight || img.height;
114
+ if (!(sourceW > 0) || !(sourceH > 0)) return false;
115
+ const scale = Math.min(
116
+ SVG_RASTER_SCALE,
117
+ SVG_RASTER_MAX_EDGE / Math.max(sourceW, sourceH),
118
+ );
119
+ if (!(scale > 0)) return false;
120
+ const width = Math.max(1, Math.round(sourceW * scale));
121
+ const height = Math.max(1, Math.round(sourceH * scale));
122
+ const surface = document.createElement('canvas');
123
+ surface.width = width;
124
+ surface.height = height;
125
+ const context = surface.getContext('2d');
126
+ if (!context) return false;
127
+ context.drawImage(img, 0, 0, width, height);
128
+ const pixels = context.getImageData(0, 0, width, height);
129
+ if (!CK.MakeImage) return false;
130
+ const image = CK.MakeImage(
131
+ {
132
+ width,
133
+ height,
134
+ colorType: CK.ColorType.RGBA_8888,
135
+ alphaType: CK.AlphaType.Unpremul,
136
+ colorSpace: CK.ColorSpace.SRGB,
137
+ },
138
+ pixels.data,
139
+ 4 * width,
140
+ );
141
+ if (!image) return false;
142
+ const bytes = decodedRasterBytes(image);
143
+ if (!(bytes > 0)) {
144
+ deleteImage(image);
145
+ return false;
146
+ }
147
+ fullImageCache.set(key, {
148
+ image,
149
+ bytes,
150
+ coversEdgePx: Number.MAX_SAFE_INTEGER,
151
+ });
152
+ fullImageCacheBytes += bytes;
153
+ evictFullImages();
154
+ return fullImageCache.has(key);
155
+ };
156
+
157
+ // The browser is the SVG decoder CanvasKit lacks: load the markup into an
158
+ // `<img>` (async), draw it onto a 2d canvas, and install the pixels as an
159
+ // ordinary CanvasKit image. resvg does this on native; carrying it into
160
+ // the wasm bundle would bust the 6 MiB ceiling for a codec the platform
161
+ // already ships.
162
+ const startSvgRaster = (key, encoded) => {
163
+ if (svgPending.has(key)) return;
164
+ svgPending.add(key);
165
+ let url = null;
166
+ const settle = (installed) => {
167
+ if (url) URL.revokeObjectURL(url);
168
+ svgPending.delete(key);
169
+ if (!installed) rememberSvgFailure(key);
170
+ };
171
+ try {
172
+ const blob = new Blob([copyBytes(encoded)], { type: 'image/svg+xml' });
173
+ url = URL.createObjectURL(blob);
174
+ const img = new Image();
175
+ img.onload = () => {
176
+ try {
177
+ settle(installRasterizedSvg(key, img));
178
+ } catch (_error) {
179
+ settle(false);
180
+ }
181
+ };
182
+ img.onerror = () => settle(false);
183
+ img.src = url;
184
+ } catch (_error) {
185
+ settle(false);
186
+ }
187
+ };
188
+
189
+ const evictFullImages = () => {
190
+ while (
191
+ fullImageCache.size > FULL_IMAGE_CACHE_CAP ||
192
+ fullImageCacheBytes > FULL_IMAGE_CACHE_BYTE_BUDGET
193
+ ) {
194
+ const oldestKey = fullImageCache.keys().next().value;
195
+ const oldest = fullImageCache.get(oldestKey);
196
+ if (!oldest) break;
197
+ fullImageCache.delete(oldestKey);
198
+ fullImageCacheBytes -= oldest.bytes;
199
+ deleteImage(oldest.image);
200
+ }
201
+ };
202
+
203
+ const evictThumbnails = () => {
204
+ while (
205
+ thumbnailCache.size > THUMBNAIL_CACHE_CAP ||
206
+ thumbnailCacheBytes > THUMBNAIL_CACHE_BYTE_BUDGET
207
+ ) {
208
+ const oldestKey = thumbnailCache.keys().next().value;
209
+ const oldest = thumbnailCache.get(oldestKey);
210
+ if (!oldest) break;
211
+ thumbnailCache.delete(oldestKey);
212
+ thumbnailCacheBytes -= oldest.bytes;
213
+ deleteImage(oldest.image);
214
+ }
215
+ };
216
+
217
+ const rememberThumbnailFailure = (key) => {
218
+ if (thumbnailFailures.has(key)) return;
219
+ if (thumbnailFailures.size >= THUMBNAIL_FAILURE_CAP) {
220
+ thumbnailFailures.delete(thumbnailFailures.values().next().value);
221
+ }
222
+ thumbnailFailures.add(key);
223
+ };
224
+
225
+ // A cached raster satisfies a draw only when it is at least as sharp
226
+ // as the view needs. Rastering every visible image at its authored
227
+ // size is what made a zoomed-out image-dense page evict and re-decode
228
+ // continuously; sizing to the view keeps hundreds of thumbnails cheap.
229
+ const hasFullImage = (lo, hi, maxEdgePx) => {
230
+ const hit = fullImageCache.get(imageKey(lo, hi));
231
+ if (!hit) return false;
232
+ if (!(maxEdgePx > 0)) return true;
233
+ return hit.coversEdgePx >= maxEdgePx;
234
+ };
235
+
236
+ const fullImage = (lo, hi) => {
237
+ const key = imageKey(lo, hi);
238
+ const hit = fullImageCache.get(key);
239
+ if (!hit) return null;
240
+ fullImageCache.delete(key);
241
+ fullImageCache.set(key, hit);
242
+ return hit.image;
243
+ };
244
+
245
+ // NOTE: the browser decodes at the source's own size. Sizing the
246
+ // raster to the view (as the native host does) needs a surface
247
+ // round-trip whose snapshot aliases the surface's pixels, so freeing
248
+ // the surface would dangle the cached image — deferred rather than
249
+ // shipped unsafely. A full raster serves every requested size, so it
250
+ // records itself as covering any edge.
251
+ const installFullImage = (lo, hi, encoded, _maxEdgePx) => {
252
+ const key = imageKey(lo, hi);
253
+ if (fullImageCache.has(key)) return true;
254
+
255
+ let image = null;
256
+ try {
257
+ image = CK.MakeImageFromEncoded(copyBytes(encoded));
258
+ if (!image) {
259
+ // CanvasKit has no SVG codec — hand the markup to the browser's own
260
+ // decoder. Reporting `true` while the async raster is in flight
261
+ // keeps the id off the Rust side's permanent-failure cache; paint
262
+ // re-requests each frame until the raster lands (or the failure is
263
+ // remembered here and this returns false for good).
264
+ if (svgFailures.has(key)) return false;
265
+ if (sniffsAsSvg(encoded)) {
266
+ startSvgRaster(key, encoded);
267
+ return true;
268
+ }
269
+ return false;
270
+ }
271
+ const bytes = decodedRasterBytes(image);
272
+ if (!(bytes > 0)) {
273
+ deleteImage(image);
274
+ return false;
275
+ }
276
+ fullImageCache.set(key, {
277
+ image,
278
+ bytes,
279
+ coversEdgePx: Number.MAX_SAFE_INTEGER,
280
+ });
281
+ fullImageCacheBytes += bytes;
282
+ evictFullImages();
283
+ return fullImageCache.has(key);
284
+ } catch (_error) {
285
+ deleteImage(image);
286
+ return false;
287
+ }
288
+ };
289
+
290
+ const thumbnailImage = (lo, hi, jpeg) => {
291
+ const key = imageKey(lo, hi);
292
+ const hit = thumbnailCache.get(key);
293
+ if (hit) {
294
+ thumbnailCache.delete(key);
295
+ thumbnailCache.set(key, hit);
296
+ return hit.image;
297
+ }
298
+ if (thumbnailFailures.has(key)) return null;
299
+ if (!jpeg || jpeg.byteLength > THUMBNAIL_ENCODED_BYTE_LIMIT) {
300
+ rememberThumbnailFailure(key);
301
+ return null;
302
+ }
303
+ if (!jpegFitsThumbnailBounds(jpeg)) {
304
+ rememberThumbnailFailure(key);
305
+ return null;
306
+ }
307
+
308
+ let image = null;
309
+ try {
310
+ image = CK.MakeImageFromEncoded(copyBytes(jpeg));
311
+ if (!image) {
312
+ rememberThumbnailFailure(key);
313
+ return null;
314
+ }
315
+ const bytes = decodedRasterBytes(image);
316
+ if (!(bytes > 0) || bytes > THUMBNAIL_CACHE_BYTE_BUDGET) {
317
+ deleteImage(image);
318
+ rememberThumbnailFailure(key);
319
+ return null;
320
+ }
321
+ thumbnailCache.set(key, { image, bytes });
322
+ thumbnailCacheBytes += bytes;
323
+ evictThumbnails();
324
+ return image;
325
+ } catch (_error) {
326
+ deleteImage(image);
327
+ rememberThumbnailFailure(key);
328
+ return null;
329
+ }
330
+ };
331
+
332
+ const discardThumbnail = (key) => {
333
+ const entry = thumbnailCache.get(key);
334
+ if (!entry) return;
335
+ thumbnailCache.delete(key);
336
+ thumbnailCacheBytes -= entry.bytes;
337
+ deleteImage(entry.image);
338
+ };
339
+
340
+ const drawThumbnailCover = (canvas, lo, hi, jpeg, x, y, w, h) => {
341
+ if (!(w > 0) || !(h > 0)) return false;
342
+ const image = thumbnailImage(lo, hi, jpeg);
343
+ if (!image) return false;
344
+
345
+ const imageW = image.width();
346
+ const imageH = image.height();
347
+ if (!(imageW > 0) || !(imageH > 0)) return false;
348
+ const scale = Math.max(w / imageW, h / imageH);
349
+ const drawW = imageW * scale;
350
+ const drawH = imageH * scale;
351
+ const src = CK.LTRBRect(0, 0, imageW, imageH);
352
+ const dst = CK.LTRBRect(
353
+ x + (w - drawW) / 2,
354
+ y + (h - drawH) / 2,
355
+ x + (w + drawW) / 2,
356
+ y + (h + drawH) / 2,
357
+ );
358
+ const clip = CK.LTRBRect(x, y, x + w, y + h);
359
+ const paint = new CK.Paint();
360
+ paint.setAntiAlias(true);
361
+
362
+ let saved = false;
363
+ try {
364
+ canvas.save();
365
+ saved = true;
366
+ canvas.clipRect(clip, CK.ClipOp.Intersect, true);
367
+ if (canvas.drawImageRectOptions) {
368
+ canvas.drawImageRectOptions(
369
+ image,
370
+ src,
371
+ dst,
372
+ CK.FilterMode.Linear,
373
+ CK.MipmapMode.None,
374
+ paint,
375
+ );
376
+ } else {
377
+ canvas.drawImageRect(image, src, dst, paint, false);
378
+ }
379
+ } catch (_error) {
380
+ const key = imageKey(lo, hi);
381
+ discardThumbnail(key);
382
+ rememberThumbnailFailure(key);
383
+ return false;
384
+ } finally {
385
+ if (saved) canvas.restore();
386
+ paint.delete();
387
+ }
388
+ return true;
389
+ };
390
+
391
+ return {
392
+ hasFullImage,
393
+ fullImage,
394
+ installFullImage,
395
+ drawThumbnailCover,
396
+ };
397
+ }
398
+
399
+ // wasm-bindgen only copies local modules named by Rust attributes; it does not
400
+ // recursively package relative imports from another local module. Rust obtains
401
+ // this function object and passes it into the bridge during initialization.
402
+ export function webImageCacheFactory() {
403
+ return createWebImageCaches;
404
+ }