svgpng 0.1.0
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.
- checksums.yaml +7 -0
- data/README.adoc +81 -0
- data/docs/decisions/svg-to-png.md +368 -0
- data/docs/goldens.md +74 -0
- data/lib/svgpng/errors.rb +57 -0
- data/lib/svgpng/font_face.rb +36 -0
- data/lib/svgpng/font_inputs.rb +49 -0
- data/lib/svgpng/font_warnings.rb +45 -0
- data/lib/svgpng/limits.rb +10 -0
- data/lib/svgpng/native.rb +213 -0
- data/lib/svgpng/native_render.rb +198 -0
- data/lib/svgpng/package_resolution.rb +174 -0
- data/lib/svgpng/raw_input.rb +51 -0
- data/lib/svgpng/release_set.rb +273 -0
- data/lib/svgpng/render_profile.rb +183 -0
- data/lib/svgpng/renderer.rb +27 -0
- data/lib/svgpng/result.rb +50 -0
- data/lib/svgpng/version.rb +5 -0
- data/lib/svgpng.rb +19 -0
- data/vendor/licenses/README.txt +23 -0
- data/vendor/licenses/fonts/COPYRIGHT.txt +13 -0
- data/vendor/licenses/fonts/NotoSans-Condensed-OFL-1.1.txt +94 -0
- data/vendor/licenses/fonts/OFL-1.1.txt +92 -0
- data/vendor/licenses/libpng/LICENSE +134 -0
- data/vendor/licenses/libpng/LICENSES/MIT-0.txt +16 -0
- data/vendor/licenses/libpng/LICENSES/MIT.txt +19 -0
- data/vendor/licenses/resvg/LICENSE-APACHE +176 -0
- data/vendor/licenses/resvg/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/adler2-2.0.1/LICENSE-0BSD +12 -0
- data/vendor/licenses/rust/adler2-2.0.1/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/adler2-2.0.1/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/arrayref-0.3.9/LICENSE +26 -0
- data/vendor/licenses/rust/arrayvec-0.7.8/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/arrayvec-0.7.8/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/autocfg-1.5.1/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/autocfg-1.5.1/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/base64-0.23.1/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/base64-0.23.1/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/bitflags-2.13.2/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/bitflags-2.13.2/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/block-buffer-0.10.4/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/block-buffer-0.10.4/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/bytemuck-1.25.2/LICENSE-APACHE +61 -0
- data/vendor/licenses/rust/bytemuck-1.25.2/LICENSE-MIT +9 -0
- data/vendor/licenses/rust/bytemuck-1.25.2/LICENSE-ZLIB +11 -0
- data/vendor/licenses/rust/bytemuck_derive-1.12.1/LICENSE-APACHE +61 -0
- data/vendor/licenses/rust/bytemuck_derive-1.12.1/LICENSE-MIT +9 -0
- data/vendor/licenses/rust/bytemuck_derive-1.12.1/LICENSE-ZLIB +11 -0
- data/vendor/licenses/rust/byteorder-lite-0.1.0/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/byteorder-lite-0.1.0/UNLICENSE +24 -0
- data/vendor/licenses/rust/cfg-if-1.0.5/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/cfg-if-1.0.5/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/color_quant-1.1.0/LICENSE +21 -0
- data/vendor/licenses/rust/cpufeatures-0.2.17/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/cpufeatures-0.2.17/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/crc32fast-1.5.3/LICENSE-APACHE +202 -0
- data/vendor/licenses/rust/crc32fast-1.5.3/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/crypto-common-0.1.7/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/crypto-common-0.1.7/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/cssparser-0.35.0/LICENSE +373 -0
- data/vendor/licenses/rust/cssparser-macros-0.6.1/LICENSE +373 -0
- data/vendor/licenses/rust/data-url-0.3.2/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/data-url-0.3.2/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/digest-0.10.7/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/digest-0.10.7/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/dtoa-1.0.11/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/dtoa-1.0.11/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/dtoa-short-0.3.5/LICENSE +373 -0
- data/vendor/licenses/rust/euclid-0.22.14/COPYRIGHT +5 -0
- data/vendor/licenses/rust/euclid-0.22.14/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/euclid-0.22.14/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/fdeflate-0.3.7/LICENSE-APACHE +177 -0
- data/vendor/licenses/rust/fdeflate-0.3.7/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/flate2-1.1.10/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/flate2-1.1.10/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/float-cmp-0.9.0/LICENSE +19 -0
- data/vendor/licenses/rust/font-types-0.12.7/LICENSE-APACHE +67 -0
- data/vendor/licenses/rust/font-types-0.12.7/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/fontconfig-parser-0.5.8/LICENSE +21 -0
- data/vendor/licenses/rust/fontdb-0.24.0/LICENSE +21 -0
- data/vendor/licenses/rust/generic-array-0.14.7/LICENSE +21 -0
- data/vendor/licenses/rust/gif-0.14.2/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/gif-0.14.2/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/harfrust-0.12.0/LICENSE +22 -0
- data/vendor/licenses/rust/image-webp-0.2.4/LICENSE-APACHE +177 -0
- data/vendor/licenses/rust/image-webp-0.2.4/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/imagesize-0.15.0/LICENSE +21 -0
- data/vendor/licenses/rust/itoa-1.0.18/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/itoa-1.0.18/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/kurbo-0.13.1/LICENSE-APACHE +202 -0
- data/vendor/licenses/rust/kurbo-0.13.1/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/libc-0.2.191/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/libc-0.2.191/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/log-0.4.34/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/log-0.4.34/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/memchr-2.8.3/COPYING +3 -0
- data/vendor/licenses/rust/memchr-2.8.3/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/memchr-2.8.3/UNLICENSE +24 -0
- data/vendor/licenses/rust/memmap2-0.9.12/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/memmap2-0.9.12/LICENSE-MIT +26 -0
- data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE +25 -0
- data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE-APACHE.md +177 -0
- data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE-MIT.md +24 -0
- data/vendor/licenses/rust/miniz_oxide-0.8.9/LICENSE-ZLIB.md +14 -0
- data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE +25 -0
- data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE-APACHE.md +177 -0
- data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE-MIT.md +24 -0
- data/vendor/licenses/rust/miniz_oxide-0.9.1/LICENSE-ZLIB.md +14 -0
- data/vendor/licenses/rust/num-traits-0.2.19/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/num-traits-0.2.19/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/once_cell-1.21.4/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/once_cell-1.21.4/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/phf-0.11.3/LICENSE +20 -0
- data/vendor/licenses/rust/phf_generator-0.11.3/LICENSE +20 -0
- data/vendor/licenses/rust/phf_macros-0.11.3/LICENSE +20 -0
- data/vendor/licenses/rust/phf_shared-0.11.3/LICENSE +20 -0
- data/vendor/licenses/rust/pico-args-0.5.0/LICENSE +20 -0
- data/vendor/licenses/rust/png-0.18.1/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/png-0.18.1/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/polycool-0.4.0/LICENSE-APACHE +202 -0
- data/vendor/licenses/rust/polycool-0.4.0/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/proc-macro2-1.0.107/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/proc-macro2-1.0.107/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/quick-error-2.0.1/LICENSE-APACHE +202 -0
- data/vendor/licenses/rust/quick-error-2.0.1/LICENSE-MIT +19 -0
- data/vendor/licenses/rust/quote-1.0.47/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/quote-1.0.47/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/rand-0.8.8/COPYRIGHT +12 -0
- data/vendor/licenses/rust/rand-0.8.8/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/rand-0.8.8/LICENSE-MIT +26 -0
- data/vendor/licenses/rust/rand_core-0.6.4/COPYRIGHT +12 -0
- data/vendor/licenses/rust/rand_core-0.6.4/LICENSE-APACHE +187 -0
- data/vendor/licenses/rust/rand_core-0.6.4/LICENSE-MIT +26 -0
- data/vendor/licenses/rust/read-fonts-0.41.0/LICENSE-APACHE +67 -0
- data/vendor/licenses/rust/read-fonts-0.41.0/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/rgb-0.8.53/LICENSE +21 -0
- data/vendor/licenses/rust/roxmltree-0.20.0/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/roxmltree-0.20.0/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/roxmltree-0.21.1/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/roxmltree-0.21.1/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/ryu-1.0.23/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/ryu-1.0.23/LICENSE-BOOST +23 -0
- data/vendor/licenses/rust/serde-1.0.229/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/serde-1.0.229/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/serde_core-1.0.229/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/serde_core-1.0.229/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/serde_derive-1.0.229/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/serde_derive-1.0.229/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/serde_json-1.0.145/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/serde_json-1.0.145/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/sha2-0.10.9/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/sha2-0.10.9/LICENSE-MIT +27 -0
- data/vendor/licenses/rust/simd-adler32-0.3.10/LICENSE.md +21 -0
- data/vendor/licenses/rust/simplecss-0.2.2/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/simplecss-0.2.2/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/siphasher-1.0.4/COPYING +7 -0
- data/vendor/licenses/rust/siphasher-1.0.4/LICENSE-APACHE +202 -0
- data/vendor/licenses/rust/siphasher-1.0.4/LICENSE-MIT +17 -0
- data/vendor/licenses/rust/skrifa-0.44.0/LICENSE-APACHE +67 -0
- data/vendor/licenses/rust/skrifa-0.44.0/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/slotmap-1.1.1/LICENSE +19 -0
- data/vendor/licenses/rust/smallvec-1.16.3/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/smallvec-1.16.3/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/strict-num-0.1.1/LICENSE +19 -0
- data/vendor/licenses/rust/svgtypes-0.16.1/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/svgtypes-0.16.1/LICENSE-MIT +20 -0
- data/vendor/licenses/rust/syn-2.0.119/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/syn-2.0.119/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/syn-3.0.9/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/syn-3.0.9/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/tiny-skia-0.12.0/LICENSE +30 -0
- data/vendor/licenses/rust/tiny-skia-path-0.12.0/LICENSE +30 -0
- data/vendor/licenses/rust/tinyvec-1.13.3/LICENSE-APACHE.md +202 -0
- data/vendor/licenses/rust/tinyvec-1.13.3/LICENSE-MIT.md +5 -0
- data/vendor/licenses/rust/tinyvec-1.13.3/LICENSE-ZLIB.md +11 -0
- data/vendor/licenses/rust/typenum-1.20.1/LICENSE +1 -0
- data/vendor/licenses/rust/typenum-1.20.1/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/typenum-1.20.1/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/unicode-bidi-0.3.18/COPYRIGHT +8 -0
- data/vendor/licenses/rust/unicode-bidi-0.3.18/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/unicode-bidi-0.3.18/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/unicode-ident-1.0.27/LICENSE-APACHE +176 -0
- data/vendor/licenses/rust/unicode-ident-1.0.27/LICENSE-MIT +23 -0
- data/vendor/licenses/rust/unicode-ident-1.0.27/LICENSE-UNICODE +39 -0
- data/vendor/licenses/rust/unicode-normalization-0.1.24/COPYRIGHT +7 -0
- data/vendor/licenses/rust/unicode-normalization-0.1.24/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/unicode-normalization-0.1.24/LICENSE-MIT +25 -0
- data/vendor/licenses/rust/unicode-script-0.5.8/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/unicode-script-0.5.8/LICENSE-MIT +27 -0
- data/vendor/licenses/rust/unicode-vo-0.1.0/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/unicode-vo-0.1.0/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/version_check-0.9.5/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/version_check-0.9.5/LICENSE-MIT +19 -0
- data/vendor/licenses/rust/weezl-0.1.12/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/weezl-0.1.12/LICENSE-MIT +22 -0
- data/vendor/licenses/rust/xmlwriter-0.1.0/LICENSE +21 -0
- data/vendor/licenses/rust/zlib-rs-0.6.8/LICENSE +19 -0
- data/vendor/licenses/rust/zune-core-0.5.3/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/zune-core-0.5.3/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/zune-core-0.5.3/LICENSE-ZLIB +19 -0
- data/vendor/licenses/rust/zune-jpeg-0.5.15/LICENSE-APACHE +201 -0
- data/vendor/licenses/rust/zune-jpeg-0.5.15/LICENSE-MIT +21 -0
- data/vendor/licenses/rust/zune-jpeg-0.5.15/LICENSE-ZLIB +19 -0
- data/vendor/licenses/zlib/LICENSE +22 -0
- data/vendor/manifest.yml +102 -0
- data/vendor/rust-dependencies.json +1696 -0
- metadata +256 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b524138f15150416ade4889beb0e274c7a2d95a3c6de0ea922c6c724bcce8e7e
|
|
4
|
+
data.tar.gz: fd790268f7e181b3b0f8b1ee25226538469591575fd191a850e98c48537670b9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 700edd9628338fbb3aa4061c960d5a4168607d356723c2cf064d9c3ce4c01d2b636e9f7480aa8a376224b0f678e68d230f5adf081f4f34ba395e3be9bb84fe34
|
|
7
|
+
data.tar.gz: f080adccda2a82dd122adf67a039f47263df97c04e93c8a1871b9918c5a69f24d13f826377257d8ac6f7da256b1f4bba0291c9025733c6db7bd11e78ae9aa7f6
|
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
|