svgpng 0.1.1-x86_64-linux

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 (208) hide show
  1. checksums.yaml +7 -0
  2. data/README.adoc +81 -0
  3. data/docs/decisions/svg-to-png.md +368 -0
  4. data/docs/goldens.md +74 -0
  5. data/lib/svgpng/errors.rb +57 -0
  6. data/lib/svgpng/font_face.rb +36 -0
  7. data/lib/svgpng/font_inputs.rb +49 -0
  8. data/lib/svgpng/font_warnings.rb +45 -0
  9. data/lib/svgpng/limits.rb +10 -0
  10. data/lib/svgpng/native/x86_64-linux/svgpng.so +0 -0
  11. data/lib/svgpng/native.rb +213 -0
  12. data/lib/svgpng/native_render.rb +198 -0
  13. data/lib/svgpng/package_resolution.rb +174 -0
  14. data/lib/svgpng/raw_input.rb +51 -0
  15. data/lib/svgpng/release_set.rb +273 -0
  16. data/lib/svgpng/render_profile.rb +183 -0
  17. data/lib/svgpng/renderer.rb +27 -0
  18. data/lib/svgpng/result.rb +50 -0
  19. data/lib/svgpng/version.rb +5 -0
  20. data/lib/svgpng.rb +19 -0
  21. data/vendor/licenses/README.txt +23 -0
  22. data/vendor/licenses/fonts/COPYRIGHT.txt +13 -0
  23. data/vendor/licenses/fonts/NotoSans-Condensed-OFL-1.1.txt +94 -0
  24. data/vendor/licenses/fonts/OFL-1.1.txt +92 -0
  25. data/vendor/licenses/libpng/LICENSE +134 -0
  26. data/vendor/licenses/libpng/LICENSES/MIT-0.txt +16 -0
  27. data/vendor/licenses/libpng/LICENSES/MIT.txt +19 -0
  28. data/vendor/licenses/resvg/LICENSE-APACHE +176 -0
  29. data/vendor/licenses/resvg/LICENSE-MIT +25 -0
  30. data/vendor/licenses/rust/adler2-2.0.1/LICENSE-0BSD +12 -0
  31. data/vendor/licenses/rust/adler2-2.0.1/LICENSE-APACHE +201 -0
  32. data/vendor/licenses/rust/adler2-2.0.1/LICENSE-MIT +23 -0
  33. data/vendor/licenses/rust/arrayref-0.3.9/LICENSE +26 -0
  34. data/vendor/licenses/rust/arrayvec-0.7.8/LICENSE-APACHE +201 -0
  35. data/vendor/licenses/rust/arrayvec-0.7.8/LICENSE-MIT +25 -0
  36. data/vendor/licenses/rust/autocfg-1.5.1/LICENSE-APACHE +201 -0
  37. data/vendor/licenses/rust/autocfg-1.5.1/LICENSE-MIT +25 -0
  38. data/vendor/licenses/rust/base64-0.23.1/LICENSE-APACHE +201 -0
  39. data/vendor/licenses/rust/base64-0.23.1/LICENSE-MIT +21 -0
  40. data/vendor/licenses/rust/bitflags-2.13.2/LICENSE-APACHE +201 -0
  41. data/vendor/licenses/rust/bitflags-2.13.2/LICENSE-MIT +25 -0
  42. data/vendor/licenses/rust/block-buffer-0.10.4/LICENSE-APACHE +201 -0
  43. data/vendor/licenses/rust/block-buffer-0.10.4/LICENSE-MIT +25 -0
  44. data/vendor/licenses/rust/bytemuck-1.25.2/LICENSE-APACHE +61 -0
  45. data/vendor/licenses/rust/bytemuck-1.25.2/LICENSE-MIT +9 -0
  46. data/vendor/licenses/rust/bytemuck-1.25.2/LICENSE-ZLIB +11 -0
  47. data/vendor/licenses/rust/bytemuck_derive-1.12.1/LICENSE-APACHE +61 -0
  48. data/vendor/licenses/rust/bytemuck_derive-1.12.1/LICENSE-MIT +9 -0
  49. data/vendor/licenses/rust/bytemuck_derive-1.12.1/LICENSE-ZLIB +11 -0
  50. data/vendor/licenses/rust/byteorder-lite-0.1.0/LICENSE-MIT +21 -0
  51. data/vendor/licenses/rust/byteorder-lite-0.1.0/UNLICENSE +24 -0
  52. data/vendor/licenses/rust/cfg-if-1.0.5/LICENSE-APACHE +201 -0
  53. data/vendor/licenses/rust/cfg-if-1.0.5/LICENSE-MIT +25 -0
  54. data/vendor/licenses/rust/color_quant-1.1.0/LICENSE +21 -0
  55. data/vendor/licenses/rust/cpufeatures-0.2.17/LICENSE-APACHE +201 -0
  56. data/vendor/licenses/rust/cpufeatures-0.2.17/LICENSE-MIT +25 -0
  57. data/vendor/licenses/rust/crc32fast-1.5.3/LICENSE-APACHE +202 -0
  58. data/vendor/licenses/rust/crc32fast-1.5.3/LICENSE-MIT +21 -0
  59. data/vendor/licenses/rust/crypto-common-0.1.7/LICENSE-APACHE +201 -0
  60. data/vendor/licenses/rust/crypto-common-0.1.7/LICENSE-MIT +25 -0
  61. data/vendor/licenses/rust/cssparser-0.35.0/LICENSE +373 -0
  62. data/vendor/licenses/rust/cssparser-macros-0.6.1/LICENSE +373 -0
  63. data/vendor/licenses/rust/data-url-0.3.2/LICENSE-APACHE +201 -0
  64. data/vendor/licenses/rust/data-url-0.3.2/LICENSE-MIT +25 -0
  65. data/vendor/licenses/rust/digest-0.10.7/LICENSE-APACHE +201 -0
  66. data/vendor/licenses/rust/digest-0.10.7/LICENSE-MIT +25 -0
  67. data/vendor/licenses/rust/dtoa-1.0.11/LICENSE-APACHE +176 -0
  68. data/vendor/licenses/rust/dtoa-1.0.11/LICENSE-MIT +23 -0
  69. data/vendor/licenses/rust/dtoa-short-0.3.5/LICENSE +373 -0
  70. data/vendor/licenses/rust/euclid-0.22.14/COPYRIGHT +5 -0
  71. data/vendor/licenses/rust/euclid-0.22.14/LICENSE-APACHE +201 -0
  72. data/vendor/licenses/rust/euclid-0.22.14/LICENSE-MIT +25 -0
  73. data/vendor/licenses/rust/fdeflate-0.3.7/LICENSE-APACHE +177 -0
  74. data/vendor/licenses/rust/fdeflate-0.3.7/LICENSE-MIT +25 -0
  75. data/vendor/licenses/rust/flate2-1.1.10/LICENSE-APACHE +201 -0
  76. data/vendor/licenses/rust/flate2-1.1.10/LICENSE-MIT +25 -0
  77. data/vendor/licenses/rust/float-cmp-0.9.0/LICENSE +19 -0
  78. data/vendor/licenses/rust/font-types-0.12.7/LICENSE-APACHE +67 -0
  79. data/vendor/licenses/rust/font-types-0.12.7/LICENSE-MIT +25 -0
  80. data/vendor/licenses/rust/fontconfig-parser-0.5.8/LICENSE +21 -0
  81. data/vendor/licenses/rust/fontdb-0.24.0/LICENSE +21 -0
  82. data/vendor/licenses/rust/generic-array-0.14.7/LICENSE +21 -0
  83. data/vendor/licenses/rust/gif-0.14.2/LICENSE-APACHE +201 -0
  84. data/vendor/licenses/rust/gif-0.14.2/LICENSE-MIT +21 -0
  85. data/vendor/licenses/rust/harfrust-0.12.0/LICENSE +22 -0
  86. data/vendor/licenses/rust/image-webp-0.2.4/LICENSE-APACHE +177 -0
  87. data/vendor/licenses/rust/image-webp-0.2.4/LICENSE-MIT +25 -0
  88. data/vendor/licenses/rust/imagesize-0.15.0/LICENSE +21 -0
  89. data/vendor/licenses/rust/itoa-1.0.18/LICENSE-APACHE +176 -0
  90. data/vendor/licenses/rust/itoa-1.0.18/LICENSE-MIT +23 -0
  91. data/vendor/licenses/rust/kurbo-0.13.1/LICENSE-APACHE +202 -0
  92. data/vendor/licenses/rust/kurbo-0.13.1/LICENSE-MIT +25 -0
  93. data/vendor/licenses/rust/libc-0.2.191/LICENSE-APACHE +176 -0
  94. data/vendor/licenses/rust/libc-0.2.191/LICENSE-MIT +25 -0
  95. data/vendor/licenses/rust/log-0.4.34/LICENSE-APACHE +201 -0
  96. data/vendor/licenses/rust/log-0.4.34/LICENSE-MIT +25 -0
  97. data/vendor/licenses/rust/memchr-2.8.3/COPYING +3 -0
  98. data/vendor/licenses/rust/memchr-2.8.3/LICENSE-MIT +21 -0
  99. data/vendor/licenses/rust/memchr-2.8.3/UNLICENSE +24 -0
  100. data/vendor/licenses/rust/memmap2-0.9.12/LICENSE-APACHE +201 -0
  101. data/vendor/licenses/rust/memmap2-0.9.12/LICENSE-MIT +26 -0
  102. data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE +25 -0
  103. data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE-APACHE.md +177 -0
  104. data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE-MIT.md +24 -0
  105. data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE-ZLIB.md +14 -0
  106. data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE +25 -0
  107. data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE-APACHE.md +177 -0
  108. data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE-MIT.md +24 -0
  109. data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE-ZLIB.md +14 -0
  110. data/vendor/licenses/rust/num-traits-0.2.19/LICENSE-APACHE +201 -0
  111. data/vendor/licenses/rust/num-traits-0.2.19/LICENSE-MIT +25 -0
  112. data/vendor/licenses/rust/once_cell-1.21.4/LICENSE-APACHE +201 -0
  113. data/vendor/licenses/rust/once_cell-1.21.4/LICENSE-MIT +23 -0
  114. data/vendor/licenses/rust/phf-0.11.3/LICENSE +20 -0
  115. data/vendor/licenses/rust/phf_generator-0.11.3/LICENSE +20 -0
  116. data/vendor/licenses/rust/phf_macros-0.11.3/LICENSE +20 -0
  117. data/vendor/licenses/rust/phf_shared-0.11.3/LICENSE +20 -0
  118. data/vendor/licenses/rust/pico-args-0.5.0/LICENSE +20 -0
  119. data/vendor/licenses/rust/png-0.18.1/LICENSE-APACHE +201 -0
  120. data/vendor/licenses/rust/png-0.18.1/LICENSE-MIT +25 -0
  121. data/vendor/licenses/rust/polycool-0.4.0/LICENSE-APACHE +202 -0
  122. data/vendor/licenses/rust/polycool-0.4.0/LICENSE-MIT +25 -0
  123. data/vendor/licenses/rust/proc-macro2-1.0.107/LICENSE-APACHE +176 -0
  124. data/vendor/licenses/rust/proc-macro2-1.0.107/LICENSE-MIT +23 -0
  125. data/vendor/licenses/rust/quick-error-2.0.1/LICENSE-APACHE +202 -0
  126. data/vendor/licenses/rust/quick-error-2.0.1/LICENSE-MIT +19 -0
  127. data/vendor/licenses/rust/quote-1.0.47/LICENSE-APACHE +176 -0
  128. data/vendor/licenses/rust/quote-1.0.47/LICENSE-MIT +23 -0
  129. data/vendor/licenses/rust/rand-0.8.8/COPYRIGHT +12 -0
  130. data/vendor/licenses/rust/rand-0.8.8/LICENSE-APACHE +176 -0
  131. data/vendor/licenses/rust/rand-0.8.8/LICENSE-MIT +26 -0
  132. data/vendor/licenses/rust/rand_core-0.6.4/COPYRIGHT +12 -0
  133. data/vendor/licenses/rust/rand_core-0.6.4/LICENSE-APACHE +187 -0
  134. data/vendor/licenses/rust/rand_core-0.6.4/LICENSE-MIT +26 -0
  135. data/vendor/licenses/rust/read-fonts-0.41.0/LICENSE-APACHE +67 -0
  136. data/vendor/licenses/rust/read-fonts-0.41.0/LICENSE-MIT +25 -0
  137. data/vendor/licenses/rust/rgb-0.8.53/LICENSE +21 -0
  138. data/vendor/licenses/rust/roxmltree-0.20.0/LICENSE-APACHE +201 -0
  139. data/vendor/licenses/rust/roxmltree-0.20.0/LICENSE-MIT +21 -0
  140. data/vendor/licenses/rust/roxmltree-0.21.1/LICENSE-APACHE +201 -0
  141. data/vendor/licenses/rust/roxmltree-0.21.1/LICENSE-MIT +21 -0
  142. data/vendor/licenses/rust/ryu-1.0.23/LICENSE-APACHE +176 -0
  143. data/vendor/licenses/rust/ryu-1.0.23/LICENSE-BOOST +23 -0
  144. data/vendor/licenses/rust/serde-1.0.229/LICENSE-APACHE +176 -0
  145. data/vendor/licenses/rust/serde-1.0.229/LICENSE-MIT +23 -0
  146. data/vendor/licenses/rust/serde_core-1.0.229/LICENSE-APACHE +176 -0
  147. data/vendor/licenses/rust/serde_core-1.0.229/LICENSE-MIT +23 -0
  148. data/vendor/licenses/rust/serde_derive-1.0.229/LICENSE-APACHE +176 -0
  149. data/vendor/licenses/rust/serde_derive-1.0.229/LICENSE-MIT +23 -0
  150. data/vendor/licenses/rust/serde_json-1.0.145/LICENSE-APACHE +176 -0
  151. data/vendor/licenses/rust/serde_json-1.0.145/LICENSE-MIT +23 -0
  152. data/vendor/licenses/rust/sha2-0.10.9/LICENSE-APACHE +201 -0
  153. data/vendor/licenses/rust/sha2-0.10.9/LICENSE-MIT +27 -0
  154. data/vendor/licenses/rust/simd-adler32-0.3.10/LICENSE.md +21 -0
  155. data/vendor/licenses/rust/simplecss-0.2.2/LICENSE-APACHE +201 -0
  156. data/vendor/licenses/rust/simplecss-0.2.2/LICENSE-MIT +21 -0
  157. data/vendor/licenses/rust/siphasher-1.0.4/COPYING +7 -0
  158. data/vendor/licenses/rust/siphasher-1.0.4/LICENSE-APACHE +202 -0
  159. data/vendor/licenses/rust/siphasher-1.0.4/LICENSE-MIT +17 -0
  160. data/vendor/licenses/rust/skrifa-0.44.0/LICENSE-APACHE +67 -0
  161. data/vendor/licenses/rust/skrifa-0.44.0/LICENSE-MIT +25 -0
  162. data/vendor/licenses/rust/slotmap-1.1.1/LICENSE +19 -0
  163. data/vendor/licenses/rust/smallvec-1.16.3/LICENSE-APACHE +201 -0
  164. data/vendor/licenses/rust/smallvec-1.16.3/LICENSE-MIT +25 -0
  165. data/vendor/licenses/rust/strict-num-0.1.1/LICENSE +19 -0
  166. data/vendor/licenses/rust/svgtypes-0.16.1/LICENSE-APACHE +201 -0
  167. data/vendor/licenses/rust/svgtypes-0.16.1/LICENSE-MIT +20 -0
  168. data/vendor/licenses/rust/syn-2.0.119/LICENSE-APACHE +176 -0
  169. data/vendor/licenses/rust/syn-2.0.119/LICENSE-MIT +23 -0
  170. data/vendor/licenses/rust/syn-3.0.9/LICENSE-APACHE +176 -0
  171. data/vendor/licenses/rust/syn-3.0.9/LICENSE-MIT +23 -0
  172. data/vendor/licenses/rust/tiny-skia-0.12.0/LICENSE +30 -0
  173. data/vendor/licenses/rust/tiny-skia-path-0.12.0/LICENSE +30 -0
  174. data/vendor/licenses/rust/tinyvec-1.13.3/LICENSE-APACHE.md +202 -0
  175. data/vendor/licenses/rust/tinyvec-1.13.3/LICENSE-MIT.md +5 -0
  176. data/vendor/licenses/rust/tinyvec-1.13.3/LICENSE-ZLIB.md +11 -0
  177. data/vendor/licenses/rust/typenum-1.20.1/LICENSE +1 -0
  178. data/vendor/licenses/rust/typenum-1.20.1/LICENSE-APACHE +201 -0
  179. data/vendor/licenses/rust/typenum-1.20.1/LICENSE-MIT +21 -0
  180. data/vendor/licenses/rust/unicode-bidi-0.3.18/COPYRIGHT +8 -0
  181. data/vendor/licenses/rust/unicode-bidi-0.3.18/LICENSE-APACHE +201 -0
  182. data/vendor/licenses/rust/unicode-bidi-0.3.18/LICENSE-MIT +25 -0
  183. data/vendor/licenses/rust/unicode-ident-1.0.27/LICENSE-APACHE +176 -0
  184. data/vendor/licenses/rust/unicode-ident-1.0.27/LICENSE-MIT +23 -0
  185. data/vendor/licenses/rust/unicode-ident-1.0.27/LICENSE-UNICODE +39 -0
  186. data/vendor/licenses/rust/unicode-normalization-0.1.24/COPYRIGHT +7 -0
  187. data/vendor/licenses/rust/unicode-normalization-0.1.24/LICENSE-APACHE +201 -0
  188. data/vendor/licenses/rust/unicode-normalization-0.1.24/LICENSE-MIT +25 -0
  189. data/vendor/licenses/rust/unicode-script-0.5.8/LICENSE-APACHE +201 -0
  190. data/vendor/licenses/rust/unicode-script-0.5.8/LICENSE-MIT +27 -0
  191. data/vendor/licenses/rust/unicode-vo-0.1.0/LICENSE-APACHE +201 -0
  192. data/vendor/licenses/rust/unicode-vo-0.1.0/LICENSE-MIT +21 -0
  193. data/vendor/licenses/rust/version_check-0.9.5/LICENSE-APACHE +201 -0
  194. data/vendor/licenses/rust/version_check-0.9.5/LICENSE-MIT +19 -0
  195. data/vendor/licenses/rust/weezl-0.1.12/LICENSE-APACHE +201 -0
  196. data/vendor/licenses/rust/weezl-0.1.12/LICENSE-MIT +22 -0
  197. data/vendor/licenses/rust/xmlwriter-0.1.0/LICENSE +21 -0
  198. data/vendor/licenses/rust/zlib-rs-0.6.8/LICENSE +19 -0
  199. data/vendor/licenses/rust/zune-core-0.5.3/LICENSE-APACHE +201 -0
  200. data/vendor/licenses/rust/zune-core-0.5.3/LICENSE-MIT +21 -0
  201. data/vendor/licenses/rust/zune-core-0.5.3/LICENSE-ZLIB +19 -0
  202. data/vendor/licenses/rust/zune-jpeg-0.5.15/LICENSE-APACHE +201 -0
  203. data/vendor/licenses/rust/zune-jpeg-0.5.15/LICENSE-MIT +21 -0
  204. data/vendor/licenses/rust/zune-jpeg-0.5.15/LICENSE-ZLIB +19 -0
  205. data/vendor/licenses/zlib/LICENSE +22 -0
  206. data/vendor/manifest.yml +102 -0
  207. data/vendor/rust-dependencies.json +1696 -0
  208. metadata +262 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b76392780b68130dc6524567462fbb23c2386313e62235b38caa154643c3f029
4
+ data.tar.gz: 233036490363b3da5615fe1377115eb7342735f282f03b45cd07a437a74944c4
5
+ SHA512:
6
+ metadata.gz: ee5d0fcad109d3710970f7e72c08df8afddf6acf791282317c1c85f54ab9d630cd8f20b64c678ac0fb6ba48f07a8a3116dfff849d63f6bf612102ea87dc9c9ef
7
+ data.tar.gz: af74783198f95bb153f757bb0998f358409903ae0d81220c37298180105fc7ef374f791fb2d4afb15a70abdf1fb4ec0ef5a35af41184b446ea9004fb8854f867
data/README.adoc ADDED
@@ -0,0 +1,81 @@
1
+ = Svgpng
2
+
3
+ Svgpng is a byte-oriented Ruby API for rendering SVG to PNG. This checkout
4
+ implements the Ruby pipeline, owned native renderer, explicit font selection, and
5
+ pinned PNG encoder. Real native renders, goldens, and an installed developer
6
+ package have been checked locally. Seven-target CI and release artifact
7
+ qualification remain pending; no platform support or cross-platform byte identity
8
+ is advertised yet.
9
+
10
+ == Current status
11
+
12
+ The Ruby boundary and fixed rendering contract are recorded in
13
+ link:docs/decisions/svg-to-png.md[the rendering decision]. Rendering requires a
14
+ matching packaged native library; without one it raises
15
+ `Svgpng::UnsupportedPlatformError`. Requiring the Ruby API does not load native
16
+ code or probe system libraries.
17
+
18
+ == Interface
19
+
20
+ `Svgpng.render(svg_bytes, profile: {}, fonts: {}, fallback_fonts: [])` accepts
21
+ SVG bytes and caller-supplied fonts. It returns a `Svgpng::Result` with
22
+ `png_bytes`, `width`, `height`, `warnings`, and `font_manifest`. The API accepts
23
+ no filesystem, network, base-directory, resource-map, or loader input. Font
24
+ faces use `Svgpng::FontFace.new(bytes:, source_id:, family: nil)`; metadata is
25
+ read from the font bytes. The optional `family:` value asserts a matching family
26
+ for fallback use and cannot override the supplied metadata.
27
+
28
+ Fixed limits, sizing, reference policy, font selection, and PNG encoding have
29
+ local native acceptance coverage. The decision document records their contract,
30
+ ownership, target packages, and the checks still required before support can be
31
+ advertised.
32
+
33
+ == Developer checks
34
+
35
+ Run `bundle exec rspec` and `bundle exec rubocop`. The default suite includes
36
+ the dependency-free C pixel conversion checks and native loader controls.
37
+ The lazy Fiddle binding covers root attributes, rendering, pixel conversion, and
38
+ PNG encoding. Real font and golden acceptance uses an explicitly built artifact:
39
+
40
+ [source,sh]
41
+ ----
42
+ SVGPNG_TEST_NATIVE_LIBRARY=/absolute/path/to/svgpng.dylib \
43
+ bundle exec rspec spec/renderer_font_integration_spec.rb spec/svgpng/golden_spec.rb
44
+ ----
45
+
46
+ See link:docs/goldens.md[the golden workflow] for corpus coverage and intentional
47
+ regeneration.
48
+
49
+ The PNG encoder source requires libpng 1.6.59 and zlib 1.3.2. To run its
50
+ round-trip, chunk-profile, deterministic-byte, and allocation-failure assertions,
51
+ provide verified static dependency installations:
52
+
53
+ [source,sh]
54
+ ----
55
+ SVGPNG_LIBPNG_PREFIX=/path/to/libpng-1.6.59 \
56
+ SVGPNG_ZLIB_PREFIX=/path/to/zlib-1.3.2 \
57
+ bundle exec rake native:check_encoder
58
+ ----
59
+
60
+ This developer task invokes a C11 compiler (`SVGPNG_CC`, or Ruby's configured
61
+ compiler) and links the explicit `lib/libpng16.a` and `lib/libz.a` archives.
62
+ Missing dependencies and different header/runtime versions fail the check.
63
+ It does not establish source provenance, a complete renderer dependency closure,
64
+ cross-platform output identity, or an installable native renderer package.
65
+
66
+ To check a staged eight-gem set, provide its checksum manifest and an empty
67
+ temporary install directory:
68
+
69
+ [source,sh]
70
+ ----
71
+ SVGPNG_RELEASE_DIR=/path/to/staged-gems \
72
+ SVGPNG_RELEASE_MANIFEST=/path/to/checksums.yml \
73
+ SVGPNG_RESOLUTION_DESTINATION=/path/to/empty-install \
74
+ bundle exec rake svgpng:verify_package_resolution
75
+ ----
76
+
77
+ This verifies artifact digests and package metadata, builds a temporary local
78
+ RubyGems index containing the full set, and resolves and installs from that index
79
+ without network access. It does not execute the installed renderer. The test suite
80
+ uses real gem containers with synthetic native payloads; those fixtures establish
81
+ selection and installation behavior only.
@@ -0,0 +1,368 @@
1
+ # SVG-to-PNG rendering contract
2
+
3
+ **Status: implemented and locally verified; native delivery gates pending.**
4
+ This decision fixes the public behavior and native ownership. The Ruby pipeline,
5
+ owned resvg fork and render ABI, reference policy, explicit-font resolver, PNG
6
+ encoder, golden corpus, and installed developer package have local acceptance
7
+ evidence. Seven-target CI, the complete staged native release set, and
8
+ cross-platform byte identity remain pending. No publication or platform-support
9
+ claim follows from local verification.
10
+
11
+ ## Public Ruby boundary
12
+
13
+ The public call is:
14
+
15
+ ```ruby
16
+ Svgpng.render(svg_bytes, profile: {}, fonts: {}, fallback_fonts: [])
17
+ ```
18
+
19
+ `svg_bytes` is a byte string containing one SVG document. `profile` is a hash
20
+ with only symbol keys `:width`, `:height`, `:scale`, and `:background`.
21
+ `fonts` maps a requested CSS family spelling to one or more `Svgpng::FontFace`
22
+ objects. `fallback_fonts` is an ordered array of those faces. A face is
23
+ constructed as `Svgpng::FontFace.new(bytes:, source_id:, family: nil)`; its bytes are
24
+ immutable after construction and `source_id` is caller-provided diagnostic
25
+ text. Family names, style, weight, stretch, and glyph coverage are derived from
26
+ OpenType metadata in the bytes. Caller declarations cannot override metadata.
27
+ For fallback faces, the optional `family:` keyword asserts a family spelling
28
+ against the supplied font's metadata; a mismatch raises `FontMetadataError`.
29
+ Omitting it lets the renderer derive the fallback family from metadata.
30
+ The assertion does not change fallback order, source IDs, or manifest identity.
31
+ There is no resource map, base path, directory, filesystem loader, network
32
+ loader, or other external-resource injection API.
33
+
34
+ Successful rendering returns `Svgpng::Result` with `png_bytes`, integer
35
+ `width` and `height`, ordered structured `warnings`, and a `font_manifest`
36
+ diagnostic describing resolved faces and their source IDs and content digests.
37
+ The manifest never exposes native pointers or engine-specific objects. Digests
38
+ are computed from supplied bytes at runtime; no native-source digest is asserted
39
+ by this decision.
40
+
41
+ The stable public error classes are `ProfileError`, `InputError`, `FontError`,
42
+ `FontMetadataError`, `DuplicateFontError`, `MissingFamilyError`,
43
+ `MissingGlyphError`, `ForbiddenReferenceError`, `LimitError`, `NativeError`,
44
+ `EncodingError`, and `UnsupportedPlatformError`, all under `Svgpng`. They
45
+ represent observable input, policy, native-boundary, and encoding failures.
46
+ Errors must not include sensitive host paths or resource contents. Invalid
47
+ profile values use `ProfileError`; malformed SVG or URI syntax uses
48
+ `InputError`; invalid OpenType bytes use `FontMetadataError`; duplicate
49
+ normalized faces use `DuplicateFontError`; absent requested families use
50
+ `MissingFamilyError`; uncovered shaped clusters use `MissingGlyphError`;
51
+ prohibited references use `ForbiddenReferenceError`; preflight/pre-allocation
52
+ bounds use `LimitError`; native status failures use `NativeError`; PNG writer
53
+ failures use `EncodingError`. A shell install can be required and its non-render
54
+ API inspected on any platform; only a render call without a matching native
55
+ package raises `UnsupportedPlatformError`, including detected and supported
56
+ platform identifiers.
57
+
58
+ ## Profile validation and output geometry
59
+
60
+ The implementation accepts only the four profile keys above. Unknown keys and
61
+ string keys are errors. `width` and `height`, when present, must be positive
62
+ Ruby integers (booleans are not integers for this contract), within 32768.
63
+ `scale` must be a positive, finite base-10 decimal string. Parse it as an exact
64
+ rational; reject numeric Ruby values, NaN, infinity, zero, negatives, and
65
+ malformed strings. `background` must be a string. `scale` cannot be combined
66
+ with either dimension. After deriving and rounding dimensions, enforce each
67
+ dimension <= 32768 and total pixels <= 64,000,000.
68
+
69
+ Resolve the SVG's intrinsic viewport in the pinned parser. CSS px is one source
70
+ unit; physical CSS lengths convert at 96 dpi. Resolve percentage root width or
71
+ height against a 300 by 150 default viewport. Reject other relative root sizing,
72
+ including `em`, `rem`, `ex`, and `calc()`, so the result cannot depend on font
73
+ or host state. Reject non-positive or non-finite resolved dimensions.
74
+
75
+ Intrinsic-size resolution proceeds as follows:
76
+
77
+ 1. If root width and height both resolve positively, use both.
78
+ 2. If exactly one resolves and a positive `viewBox` is usable, derive the
79
+ missing axis using the viewBox aspect ratio. If no usable viewBox exists,
80
+ use 300 for a missing width or 150 for a missing height.
81
+ 3. If neither resolves, use the positive viewBox width and height when present;
82
+ otherwise use 300 by 150.
83
+
84
+ The document's `preserveAspectRatio` (default `xMidYMid meet`) governs drawing
85
+ the viewBox inside this intrinsic viewport. It is separate from the outer
86
+ profile fit described below.
87
+
88
+ Output size precedence is fixed:
89
+
90
+ 1. Both `width` and `height`: use that exact canvas.
91
+ 2. `width` only: derive height from the intrinsic aspect ratio.
92
+ 3. `height` only: derive width from the intrinsic aspect ratio.
93
+ 4. `scale` only: multiply both intrinsic dimensions by the exact scale.
94
+ 5. Empty profile: use intrinsic dimensions.
95
+
96
+ Round each derived positive dimension by `floor(value + 1/2)`. Clamp a positive
97
+ result below one pixel to one. Do not clamp zero or invalid source values into
98
+ valid output. Check dimensions, pixel count, row stride, and output-buffer
99
+ arithmetic after rounding and before the corresponding allocation. RGBA8 row
100
+ stride is `width * 4`; all multiplications and sums are checked for overflow.
101
+
102
+ Preserve the intrinsic aspect ratio. For an exact two-axis canvas, compute
103
+ `fit = min(output_width / intrinsic_width, output_height / intrinsic_height)`.
104
+ Scale both viewport axes by `fit` and center them using translations
105
+ `(output_width - scaled_width) / 2` and
106
+ `(output_height - scaled_height) / 2`. Pass those values to the native transform.
107
+ Never stretch axes independently. The outer fit does not replace the SVG's own
108
+ viewBox `preserveAspectRatio` behavior.
109
+
110
+ The default background is transparent black. Accepted strings are exactly
111
+ `transparent`, `#RRGGBB`, and `#RRGGBBAA`; expand six hex digits with alpha `FF`.
112
+ For each channel, premultiply using integer division:
113
+ `(channel * alpha + 127) / 255`. Fill the complete canvas, including letterbox
114
+ areas, with that premultiplied color before compositing the SVG source-over.
115
+ There is no background operation after RGBA unpremultiplication.
116
+
117
+ Required profile fixtures include the 300 by 150 no-size/no-viewBox default,
118
+ viewBox-only dimensions, one-axis derivation, both-axis fitting and padding,
119
+ scale, half-pixel rounding, transparent and explicit-alpha backgrounds, and
120
+ each rejected key, type, value, limit, and incompatible combination.
121
+
122
+ ## Fonts and diagnostics
123
+
124
+ System-font loading is disabled and the package bundles no fonts. Normalize
125
+ family matching with Unicode NFC, CSS quote removal, whitespace collapse, and
126
+ ASCII case folding. Every `fonts` key must match at least one family name found
127
+ in its supplied face metadata. Reject malformed fonts and duplicate normalized
128
+ family/style/weight/stretch faces; do not break ties using hash iteration or
129
+ filesystem order.
130
+
131
+ The resolver uses resvg's computed CSS family, style, weight, and stretch
132
+ request. Within an exact requested family it follows the pinned resvg/fontdb
133
+ face-matching algorithm. For each shaped cluster, first select an exact-family
134
+ face with full cluster coverage. If none covers it, consider `fallback_fonts`
135
+ strictly in caller order and match style, weight, and stretch within each
136
+ fallback family. The face selected by the resolver callback must be the face
137
+ resvg actually shapes and renders; an independent preflight guess is
138
+ insufficient. If no supplied face covers a cluster, raise `MissingGlyphError`
139
+ with the requested family and missing Unicode scalars.
140
+
141
+ Emit substitution warnings in first document-use order. Deduplicate by
142
+ requested family, selected family, style/weight/stretch, and missing scalar set.
143
+ Each warning names requested and selected families and the triggering scalars.
144
+ The result's font manifest reports resolved face/source diagnostics and
145
+ byte-derived SHA-256 digests. Real native acceptance compares regular, bold,
146
+ italic, and condensed selection with exact-face controls, including fallback
147
+ selection in both supplied-face orders. The golden corpus records caller family
148
+ order and first-use warning order with deduplication.
149
+
150
+ ## SVG references and parser boundary
151
+
152
+ At the raw render byte boundary, reject every XML `DOCTYPE` declaration before
153
+ DOM construction or entity expansion. The guard must be token-aware so
154
+ `DOCTYPE` text in comments or CDATA is not mistaken for a declaration. Permit
155
+ same-document fragments such as `href="#symbol"` and `url(#gradient)`, and
156
+ inline `data:` values already contained in the SVG bytes. Reject malformed URI
157
+ syntax as `InputError`.
158
+
159
+ The pinned XML/CSS parser boundary rejects relative and absolute paths,
160
+ drive and UNC paths, traversal or symlink-looking paths, protocol-relative
161
+ URLs, `file:`, `http:`, `https:`, every other non-`data:` scheme, and CSS
162
+ `@import` before any resolver or decoder can request external bytes. Apply this
163
+ rule to all URI-bearing SVG attributes and CSS `url(...)`. The rejection patch
164
+ returns a stable forbidden-reference status and reference class/document
165
+ location without echoing sensitive host content. It accepts no resource bytes
166
+ and provides no loader callback. Leave resvg's filesystem resources directory
167
+ unset; do not change working directory, materialize files, or use an HTTP
168
+ client. Tests must prove forbidden inputs fail without filesystem or socket
169
+ access, including concurrent renders. Positive controls cover inline `data:`
170
+ raster values and same-document fragments.
171
+
172
+ ## Limits and unsupported SVG behavior
173
+
174
+ Enforce these fixed limits before the associated allocation:
175
+
176
+ | Resource | Limit |
177
+ | --- | ---: |
178
+ | SVG input bytes | 16 MiB |
179
+ | Font faces in one request | 64 |
180
+ | One font's bytes | 32 MiB |
181
+ | Total font bytes in one request | 128 MiB |
182
+ | Output width or height | 32768 pixels |
183
+ | Output pixels | 64,000,000 |
184
+ | Row stride and output buffer | checked arithmetic; buffer <= 256 MiB |
185
+
186
+ Count/byte limits are checked before parsing or copying their associated data;
187
+ dimension, pixel, stride, and output-buffer checks happen after size resolution
188
+ and before allocation. `LimitError` covers only these enforceable checks.
189
+ There is no safe cancellation API for the synchronous resvg call, so there is
190
+ no in-process render-time deadline. Callers who need a wall-clock bound must
191
+ render in a killable isolated process. Static constructs unsupported by the
192
+ pinned resvg release retain resvg's documented behavior; Svgpng does not
193
+ promise a blanket error for every ignored or unsupported construct.
194
+
195
+ ## Native ownership and packaging decision
196
+
197
+ The native pipeline is owned as follows:
198
+
199
+ * `native/svgpng_renderer/` owns the Rust C ABI for guarded root attributes,
200
+ rendering, render-output allocation/freeing, and status diagnostics. Its
201
+ isolated upstream copy implements font-byte loading, resolver and shaping
202
+ hooks, and parser-side reference rejection.
203
+ * `native/svgpng_shim/` owns checked RGBA unpremultiplication and the PNG encoder
204
+ ABI, including its output allocation/freeing. Ruby returns copied owned bytes
205
+ and never frees memory allocated by a different allocator.
206
+ * The Claricle-maintained resvg fork is based on upstream **v0.48.1**. Its
207
+ reviewable patch and exact source identity are recorded in
208
+ `vendor/manifest.yml`: fork commit
209
+ `1b17e0b252844414bf108b59b78e0a2148b3a91a`, source SHA-256
210
+ `1a708ce8627a52546e088b377f52b69daa874a4dd59d16b0fef7030c7e3cf1ff`.
211
+ The upstream C API alone is not assumed to enforce this contract.
212
+ * The shared library statically links the fork, **libpng 1.6.59**, and
213
+ **zlib 1.3.2**. The packaged shared library is the dependency closure; neither
214
+ Fiddle nor the dynamic loader may resolve ambient copies of these libraries.
215
+ * `lib/svgpng/native.rb` and `native_render.rb` bind the C ABI through Fiddle;
216
+ `lib/svgpng/renderer.rb` coordinates Ruby input validation and native calls.
217
+ Do not compile at gem install time, add a compiled Ruby extension, shell out,
218
+ or search system library paths.
219
+ * `vendor/manifest.yml` records source/archive and patch digests, the Cargo lock
220
+ hash, dependency and font license inventory, and measured local build settings.
221
+ Rust **1.85.0** was used for the local build. The golden manifest records the
222
+ actual artifact checksum and size. Per-target release binary checksums and
223
+ sizes remain pending; upstream license inventory still identifies its
224
+ redistribution audit as pending.
225
+
226
+ ### Local macOS renderer build
227
+
228
+ `rake native:build_renderer` is a developer-only, host build helper. It
229
+ requires explicit `SVGPNG_CARGO_HOME`, `SVGPNG_RUSTUP_HOME`,
230
+ `SVGPNG_LIBPNG_PREFIX`, `SVGPNG_ZLIB_PREFIX`, and
231
+ `SVGPNG_RENDERER_OUTPUT` values. The Cargo invocation is locked and offline;
232
+ the dependency prefixes must contain `include/png.h` plus `lib/libpng16.a`
233
+ and `include/zlib.h` plus `lib/libz.a`. It combines the Rust static library
234
+ with `native/svgpng_shim/rgba.c` and `encoder.c` and links those exact PNG and
235
+ zlib archives. The output must be a `.dylib` under `/private/tmp`; this task
236
+ does not populate `lib/svgpng/native/`, create a gem, cross-compile, or qualify
237
+ a release target. Apple framework and system-library flags are local linker
238
+ inputs. Rust's reported native static-library requirements on the macOS build
239
+ host were `-lSystem -lc -lm`; no Apple frameworks were reported.
240
+
241
+ ### Local macOS developer package
242
+
243
+ `rake native:local_package` builds one temporary, developer-labeled
244
+ `arm64-darwin` gem from the Ruby shell and an explicitly supplied `.dylib`.
245
+ Set `SVGPNG_TEST_NATIVE_LIBRARY` to that built artifact,
246
+ `SVGPNG_DEV_PACKAGE_DIR` to an empty directory below `/private/tmp`, and
247
+ `SVGPNG_DEV_LABEL` to a short identifier for the build. The task rejects
248
+ non-regular or symlinked inputs, inspects `otool -L`, and permits only the
249
+ system runtime dependency; libpng and zlib must be statically linked. It stages
250
+ the gem without modifying `lib/svgpng/native/`, checks its gem metadata and
251
+ payload, installs it locally into an isolated `GEM_HOME`, and runs a 2-by-3
252
+ render smoke test using only the installed gem. The package SHA-256 and
253
+ diagnostic output are written beside the gem. This verifies one local package
254
+ and installed API path; it is not release qualification, a published download
255
+ check, or evidence for any other target.
256
+
257
+ The initial package set is seven native gems and one shell gem, all at the same
258
+ version, initially **0.1.0**:
259
+
260
+ | RubyGems platform | Native package |
261
+ | --- | --- |
262
+ | `arm64-darwin` | one shared library |
263
+ | `x86_64-darwin` | one shared library |
264
+ | `x86_64-linux` | one shared library |
265
+ | `aarch64-linux` | one shared library |
266
+ | `x86_64-linux-musl` | one shared library |
267
+ | `aarch64-linux-musl` | one shared library |
268
+ | `x64-mingw-ucrt` | one shared library |
269
+ | `ruby` | Ruby API and errors only; no native library |
270
+
271
+ One native artifact serves every supported Ruby version for its platform. On a
272
+ supported target, RubyGems should select the matching native package when the
273
+ complete set is available. On other targets, the `ruby` shell installs and
274
+ loads without probing ambient libraries; rendering raises
275
+ `UnsupportedPlatformError` with the detected platform and supported targets.
276
+ Do not advertise a native target until that target passes the byte-identity
277
+ gate.
278
+
279
+ ## PNG byte profile
280
+
281
+ resvg supplies premultiplied RGBA8 rows in top-to-bottom order. For every
282
+ component, including alpha zero, convert to straight RGBA as follows:
283
+
284
+ ```text
285
+ if alpha == 0: straight = 0
286
+ else: straight = min(255, (premultiplied * 255 + alpha / 2) / alpha)
287
+ ```
288
+
289
+ Use checked integer arithmetic. Treat resulting channels as sRGB sample values;
290
+ perform no gamma or color-profile transform. Background compositing is already
291
+ complete in resvg before this conversion.
292
+
293
+ Encode PNG color type 6 (RGBA), bit depth 8, no interlace, and
294
+ `PNG_FILTER_NONE`. Use zlib level 9, default strategy, window 15, memory level
295
+ 8, and a 32 KiB libpng compression buffer. Emit only the PNG signature,
296
+ `IHDR`, one or more deterministic `IDAT` chunks, and `IEND`, in that order.
297
+ Omit time, text, DPI, gamma, chromaticity, sRGB, ICC, EXIF, and every other
298
+ ancillary chunk. Golden tests must inspect structure and chunk order as well as
299
+ the complete file digest.
300
+
301
+ The encoder checks and committed golden PNGs pin local chunk structure and
302
+ compression output. Matching these bytes on all seven targets remains a delivery
303
+ gate; local results alone do not establish cross-platform byte identity.
304
+
305
+ ## Delivery and evidence gates
306
+
307
+ The required implementation sequence is captured by owned cards 01, 02, 03,
308
+ and 05 in the Vectory `TODO.svg-to-png` plan. Card 01 establishes the Ruby
309
+ contract and focused profile examples. Card 03 proves the font-selection and
310
+ forbidden-reference hooks. Card 02 builds an unpublished, checksummed local
311
+ candidate set of all seven native gems plus the shell, including a native gem
312
+ for Vectory's development/CI platform. Card 05 proves repeated exact output on
313
+ all seven targets and verifies the staged artifacts by checksum. Until those
314
+ gates pass, native delivery and support remain pending; no 0.1.0 publication or
315
+ cross-platform determinism claim follows from this design document.
316
+
317
+ The bounded golden corpus must record fixture source and licence, complete
318
+ profile, SVG/font digests and normalized metadata, resvg fork/upstream/patch
319
+ identifiers, libpng/zlib/toolchain/build identifiers, encoding settings,
320
+ expected dimensions, ordered warnings/errors, and expected SHA-256. Positive
321
+ fixtures cover dimensions/viewBox, paths and strokes, gradients/clips/masks,
322
+ alpha/background, unpremultiplication edges, inline raster data, fragments,
323
+ exact fonts, face matching, ordered fallback, partial coverage, warning order,
324
+ and enforceable limits. Negative fixtures cover malformed input, missing font
325
+ or glyph, prohibited references, limits, loader/native failures, and encoding
326
+ failures. Representative unsupported static constructs pin documented resvg
327
+ output behavior without asserting a universal error. Each positive case renders
328
+ twice in a clean process and must match its committed PNG byte-for-byte on all
329
+ seven targets. Host fonts, locale, timezone, working directory, environment,
330
+ and render order/concurrency must not change bytes or warnings.
331
+
332
+ The artifact set is immutable after the gate. Verify that the seven native gems
333
+ and shell gem match staged SHA-256 values; do not rebuild for publication.
334
+ Publication is one eight-file release set, followed by clean package-resolution
335
+ checks proving native selection and a minimal golden on a supported target and
336
+ shell selection, successful require, and render-time unsupported-platform
337
+ failure on an unsupported target. Golden updates require an explicit
338
+ compatibility change; before 1.0, a byte change requires a minor version bump
339
+ and changelog entry, and from 1.0 it requires a major version bump (working
340
+ rule pending confirmation).
341
+
342
+ Current verification state: Ruby and real native acceptance cover exact profiles,
343
+ input/reference guards, owned ABI buffers, PNG encoding, and explicit-font
344
+ selection and diagnostics. Sizing goldens include width-only, height-only,
345
+ two-axis containment, decimal scale, and the positive 32768-by-1 boundary. Font
346
+ acceptance includes weight, italic style, condensed stretch, ordered fallback,
347
+ whole-cluster coverage, and first-use deduplicated warnings. Golden checks compare
348
+ repeated clean-process renders with committed PNG bytes and metadata. A local
349
+ macOS developer gem has been installed and rendered through its installed API.
350
+ The shell also builds, installs, and loads without a native library, with the
351
+ documented render-time unsupported-platform failure. Release-set resolution
352
+ specs use real gem containers with synthetic native payloads, proving selection
353
+ and installation only. The seven-target CI qualification and real staged release
354
+ artifact set remain pending, as do publication and the cross-platform support
355
+ gates.
356
+
357
+ ### Initial Ruby preflight representation
358
+
359
+ The Ruby raw-input guard currently accepts UTF-8 XML bytes (including an optional
360
+ UTF-8 BOM); invalid UTF-8 and NUL-bearing encodings raise `InputError` before parsing.
361
+ The native parser separately enforces URI/CSS and nested-resource policy; this
362
+ Ruby guard does not replace that parser boundary.
363
+ Other XML encodings are a current guard limitation, not a finalized restriction
364
+ on the byte-oriented render contract.
365
+ Intrinsic numeric tokens are bounded to 1024 bytes and an absolute decimal exponent
366
+ of 4096 before rational construction (`LimitError`); values overflowing finite IEEE
367
+ 754 magnitude are invalid (`ProfileError`). Positive underflow remains exact for
368
+ rounding and the one-pixel clamp. These limits prevent unbounded big-integer parsing.
data/docs/goldens.md ADDED
@@ -0,0 +1,74 @@
1
+ # Reproducing and updating goldens
2
+
3
+ The corpus at `spec/fixtures/svg_to_png/manifest.json` pins SVG inputs, explicit
4
+ font bytes, PNG bytes, public metadata, typed failures, and encoder settings.
5
+ `bundle exec rake svgpng:verify_manifest` checks the committed checksums without
6
+ loading a native renderer. Native golden specs use the explicitly supplied
7
+ `SVGPNG_TEST_NATIVE_LIBRARY`; see the native build instructions in
8
+ [`svg-to-png.md`](decisions/svg-to-png.md).
9
+
10
+ The committed corpus includes width-only and height-only sizing, two-axis
11
+ containment, exact decimal scale, and a positive 32768-by-1 canvas boundary. Font
12
+ cases use supplied regular, bold, italic, and condensed faces, caller-ordered
13
+ fallback families, whole-cluster fallback, and first-use warning order with
14
+ deduplication. Native font integration also compares style and stretch selection
15
+ against exact-face controls in both supplied-face orders. These local acceptance
16
+ checks do not establish the pending seven-target byte-identity gate.
17
+
18
+ Golden changes are intentional compatibility changes. Review the renderer,
19
+ encoder, dependency, version and CHANGELOG impact before regeneration. Build the
20
+ native artifact from the source pinned in `vendor/manifest.yml`, finish that build,
21
+ and keep the artifact unchanged while the task runs. Use an absolute artifact
22
+ path. Do not regenerate with a downloaded or unrelated renderer merely to make a
23
+ comparison pass.
24
+
25
+ From the repository root, after building the artifact, run:
26
+
27
+ ```sh
28
+ SVGPNG_COMPATIBILITY_UPDATE=yes \
29
+ SVGPNG_COMPATIBILITY_IMPACT='Reviewed: describe the version and CHANGELOG decision here' \
30
+ SVGPNG_TEST_NATIVE_LIBRARY=/absolute/path/to/svgpng.dylib \
31
+ SVGPNG_GOLDEN_BUILD_COMMAND='Record the exact command and build environment used' \
32
+ SVGPNG_GOLDEN_TOOLCHAIN='Record actual rustc -Vv and C compiler --version output' \
33
+ bundle exec rake svgpng:update_goldens
34
+ ```
35
+
36
+ Replace each descriptive value with the reviewed decision and actual build
37
+ information; the task rejects missing or blank values. The artifact extension is
38
+ platform dependent. The build-command and toolchain values are operator supplied
39
+ attestations; hashing the artifact does not prove that it was built from the
40
+ recorded source. Preserve the corresponding build log as review evidence.
41
+
42
+ The task first verifies existing input/output checksums and encoding settings. For
43
+ an intentional SVG or font change, review and update its input checksum in the
44
+ manifest before running the task. Keep old PNG checksums until regeneration.
45
+ The task prints old and new renderer identifiers and PNG digests, renders every
46
+ entry before writing outputs, and rejects changed error classes. Error details
47
+ and successful metadata are updated for review. If rendering fails or the
48
+ artifact checksum changes during rendering, it writes no golden files. File
49
+ writes themselves are not a filesystem transaction; restore from version control
50
+ if a write fails.
51
+
52
+ The new `renderer` record replaces the old record using the current vendor
53
+ manifest: upstream/fork commits, patch hashes, renderer source and Cargo lock
54
+ hashes, and all three source archive identities. It also records the SHA256 and
55
+ size of the actual library used, reviewed compatibility impact, explicit build
56
+ command/toolchain information, declared vendor build settings, and running Ruby
57
+ and platform. Declared vendor settings and actual toolchain evidence are distinct.
58
+ A source update therefore updates the golden renderer identity even if PNG bytes
59
+ are unchanged. Keep vendor provenance current before regenerating.
60
+
61
+ After regeneration, inspect the PNGs and review every manifest change, including
62
+ warnings, font selection, dimensions and failure details. Run the manifest check
63
+ again and the native golden specs against the same artifact:
64
+
65
+ ```sh
66
+ bundle exec rake svgpng:verify_manifest
67
+ SVGPNG_TEST_NATIVE_LIBRARY=/absolute/path/to/svgpng.dylib \
68
+ bundle exec rspec spec/svgpng/golden_spec.rb
69
+ ```
70
+
71
+ Commit intended input, output and provenance changes together. Do not accept
72
+ unexplained byte drift. Fixture origins and font licensing are documented in the
73
+ [corpus README](../spec/fixtures/svg_to_png/README.md); dependency licenses are
74
+ recorded under `vendor/licenses` and `vendor/rust-dependencies.json`.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Svgpng
4
+ class Error < StandardError; end
5
+ class InputError < Error; end
6
+ class ProfileError < InputError; end
7
+ class LimitError < InputError; end
8
+ class FontError < InputError; end
9
+ class FontMetadataError < FontError; end
10
+ class DuplicateFontError < FontError; end
11
+
12
+ # An unresolved requested family, optionally identified by native diagnostics.
13
+ class MissingFamilyError < FontError
14
+ attr_reader :requested_family
15
+
16
+ def initialize(message = nil, requested_family: nil)
17
+ @requested_family = requested_family&.dup&.freeze
18
+ super(message)
19
+ end
20
+ end
21
+
22
+ # Missing Unicode scalar values for an explicitly requested family.
23
+ class MissingGlyphError < FontError
24
+ attr_reader :requested_family, :scalars
25
+
26
+ def initialize(message = nil, requested_family: nil, scalars: nil)
27
+ @requested_family = requested_family&.dup&.freeze
28
+ @scalars = scalars&.dup&.freeze
29
+ super(message)
30
+ end
31
+ end
32
+
33
+ # A rejected reference, with structural identifiers rather than resource paths.
34
+ class ForbiddenReferenceError < InputError
35
+ attr_reader :reference_class, :location
36
+
37
+ def initialize(message = nil, reference_class: nil, location: nil)
38
+ @reference_class = reference_class&.dup&.freeze
39
+ @location = location&.dup&.freeze
40
+ super(message)
41
+ end
42
+ end
43
+
44
+ class NativeError < Error; end
45
+ class EncodingError < NativeError; end
46
+
47
+ # Raised only when a native-free shell is asked to render.
48
+ class UnsupportedPlatformError < Error
49
+ attr_reader :platform, :supported_targets
50
+
51
+ def initialize(platform:, supported_targets:)
52
+ @platform = platform.dup.freeze
53
+ @supported_targets = supported_targets.map { |target| target.dup.freeze }.freeze
54
+ super("No packaged SVGPNG renderer for #{platform}; supported targets: #{supported_targets.join(", ")}")
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Svgpng
6
+ # A byte container. Only the pinned native parser may assert face metadata.
7
+ class FontFace
8
+ attr_reader :bytes, :source_id, :sha256, :family
9
+
10
+ def initialize(bytes:, source_id:, family: nil)
11
+ validate_bytes!(bytes)
12
+ raise InputError, "Font source_id must be a nonempty String" unless source_id.is_a?(String) && !source_id.empty?
13
+
14
+ validate_family!(family)
15
+
16
+ @bytes = bytes.b.freeze
17
+ @source_id = source_id.dup.freeze
18
+ @family = family&.dup&.freeze
19
+ @sha256 = Digest::SHA256.hexdigest(@bytes).freeze
20
+ freeze
21
+ end
22
+
23
+ private
24
+
25
+ def validate_family!(family)
26
+ return if family.nil? || (family.is_a?(String) && !family.strip.empty?)
27
+
28
+ raise InputError, "Fallback font family must be a nonempty String"
29
+ end
30
+
31
+ def validate_bytes!(bytes)
32
+ raise FontMetadataError, "Font bytes must be a nonempty String" unless bytes.is_a?(String) && !bytes.empty?
33
+ raise LimitError, "Font exceeds #{Limits::FONT_BYTES} bytes" if bytes.bytesize > Limits::FONT_BYTES
34
+ end
35
+ end
36
+ end