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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Svgpng
|
|
4
|
+
# Shape/size checks only: native metadata validation follows before rendering.
|
|
5
|
+
class FontInputs
|
|
6
|
+
attr_reader :fonts, :fallback_fonts
|
|
7
|
+
|
|
8
|
+
def initialize(fonts:, fallback_fonts:)
|
|
9
|
+
raise InputError, "fonts must be a Hash" unless fonts.is_a?(Hash)
|
|
10
|
+
raise InputError, "fallback_fonts must be an Array" unless fallback_fonts.is_a?(Array)
|
|
11
|
+
|
|
12
|
+
check_count!(fonts, fallback_fonts)
|
|
13
|
+
@fonts = fonts.to_h { |family, faces| normalize_family(family, faces) }.freeze
|
|
14
|
+
@fallback_fonts = validate_faces(fallback_fonts)
|
|
15
|
+
check_total_bytes!
|
|
16
|
+
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def check_count!(fonts, fallback_fonts)
|
|
23
|
+
count = fallback_fonts.length
|
|
24
|
+
fonts.each_value { |faces| count += faces.is_a?(Array) ? faces.length : 1 }
|
|
25
|
+
raise LimitError, "Too many font faces (maximum #{Limits::FONT_COUNT})" if count > Limits::FONT_COUNT
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def normalize_family(family, faces)
|
|
29
|
+
raise InputError, "Font family keys must be nonempty Strings" unless family.is_a?(String) && !family.strip.empty?
|
|
30
|
+
|
|
31
|
+
entries = faces.is_a?(Array) ? faces : [faces]
|
|
32
|
+
raise InputError, "Font family must have at least one face" if entries.empty?
|
|
33
|
+
|
|
34
|
+
[family.dup.freeze, validate_faces(entries)]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def check_total_bytes!
|
|
38
|
+
total = @fonts.values.flatten.sum { |face| face.bytes.bytesize } +
|
|
39
|
+
@fallback_fonts.sum { |face| face.bytes.bytesize }
|
|
40
|
+
raise LimitError, "Total font bytes exceed #{Limits::TOTAL_FONT_BYTES}" if total > Limits::TOTAL_FONT_BYTES
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def validate_faces(faces)
|
|
44
|
+
raise InputError, "Every font must be a Svgpng::FontFace" unless faces.all?(FontFace)
|
|
45
|
+
|
|
46
|
+
faces.dup.freeze
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "result"
|
|
4
|
+
|
|
5
|
+
module Svgpng
|
|
6
|
+
# Collects ordered substitution diagnostics emitted by a renderer.
|
|
7
|
+
class FontWarnings
|
|
8
|
+
def initialize
|
|
9
|
+
@warnings = []
|
|
10
|
+
@keys = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# rubocop:disable-next Metrics/ParameterLists -- Mirrors the native event fields.
|
|
14
|
+
def add(requested_family:, selected_family:, style:, weight:, stretch:, scalars:)
|
|
15
|
+
normalized_scalars = normalize_scalars(scalars)
|
|
16
|
+
warning = FontSubstitutionWarning.new(requested_family: requested_family,
|
|
17
|
+
selected_family: selected_family,
|
|
18
|
+
style: style, weight: weight,
|
|
19
|
+
stretch: stretch, scalars: normalized_scalars)
|
|
20
|
+
key = [warning.requested_family, warning.selected_family, warning.style,
|
|
21
|
+
warning.weight, warning.stretch, warning.scalars]
|
|
22
|
+
@warnings << warning unless @keys.key?(key)
|
|
23
|
+
@keys[key] = true
|
|
24
|
+
warning
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def warnings
|
|
28
|
+
@warnings.dup.freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def normalize_scalars(scalars)
|
|
34
|
+
unless scalars.is_a?(Array) && scalars.all? { |scalar| valid_scalar?(scalar) }
|
|
35
|
+
raise ArgumentError, "scalars must be Unicode scalar values (0..0x10FFFF, excluding surrogates)"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
scalars.uniq.sort.freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def valid_scalar?(scalar)
|
|
42
|
+
scalar.is_a?(Integer) && scalar.between?(0, 0x10FFFF) && !scalar.between?(0xD800, 0xDFFF)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "render_profile"
|
|
5
|
+
require "fiddle"
|
|
6
|
+
require "fiddle/struct"
|
|
7
|
+
require_relative "native_render"
|
|
8
|
+
|
|
9
|
+
module Svgpng
|
|
10
|
+
# Opens only the artifact belonging to this installed gem, on explicit request.
|
|
11
|
+
module Native
|
|
12
|
+
SUPPORTED_TARGETS = %w[arm64-darwin x86_64-darwin x86_64-linux aarch64-linux
|
|
13
|
+
x86_64-linux-musl aarch64-linux-musl x64-mingw-ucrt].map(&:freeze).freeze
|
|
14
|
+
SUFFIXES = { "darwin" => "dylib", "linux" => "so", "mingw" => "dll" }.freeze
|
|
15
|
+
|
|
16
|
+
def self.load
|
|
17
|
+
Library.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.packaged_library_path
|
|
21
|
+
platform = Gem::Platform.local
|
|
22
|
+
target = SUPPORTED_TARGETS.find { |candidate| candidate == platform.to_s }
|
|
23
|
+
target ||= compatible_target(platform)
|
|
24
|
+
raise UnsupportedPlatformError.new(platform: platform.to_s, supported_targets: SUPPORTED_TARGETS) unless target
|
|
25
|
+
|
|
26
|
+
suffix = SUFFIXES.fetch(platform.os)
|
|
27
|
+
File.join(File.realpath(__dir__), "native", target, "svgpng.#{suffix}")
|
|
28
|
+
end
|
|
29
|
+
private_class_method :packaged_library_path
|
|
30
|
+
|
|
31
|
+
def self.compatible_target(platform)
|
|
32
|
+
SUPPORTED_TARGETS.find do |name|
|
|
33
|
+
candidate = Gem::Platform.new(name)
|
|
34
|
+
next false unless candidate.cpu == platform.cpu && candidate.os == platform.os
|
|
35
|
+
|
|
36
|
+
case platform.os
|
|
37
|
+
when "darwin" then true
|
|
38
|
+
when "linux" then candidate.version == (platform.version == "gnu" ? nil : platform.version)
|
|
39
|
+
else false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
private_class_method :compatible_target
|
|
44
|
+
|
|
45
|
+
# This binding is a pixel conversion primitive, not a rendering backend.
|
|
46
|
+
# Keep the two ownership-related ABI primitives and their guards in one binding.
|
|
47
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
48
|
+
class Library
|
|
49
|
+
include RenderBinding
|
|
50
|
+
|
|
51
|
+
SIZE_MAX = (1 << (Fiddle::SIZEOF_SIZE_T * 8)) - 1
|
|
52
|
+
POINTER_MAX = (1 << (Fiddle::SIZEOF_VOIDP * 8)) - 1
|
|
53
|
+
STATUS_ERRORS = { 1 => InputError, 2 => LimitError, 3 => LimitError,
|
|
54
|
+
4 => InputError, 5 => InputError }.freeze
|
|
55
|
+
STATUS_NAMES = { 1 => "invalid argument", 2 => "arithmetic overflow", 3 => "limit exceeded",
|
|
56
|
+
4 => "buffer too small", 5 => "overlapping buffers" }.freeze
|
|
57
|
+
PNG_OUTPUT = Fiddle::CStructBuilder.create(Fiddle::CStruct, [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T],
|
|
58
|
+
%w[bytes length])
|
|
59
|
+
PNG_STATUS_ERRORS = { 1 => InputError, 2 => LimitError, 3 => LimitError, 4 => InputError,
|
|
60
|
+
5 => EncodingError, 6 => EncodingError, 7 => NativeError }.freeze
|
|
61
|
+
PNG_STATUS_NAMES = { 1 => "invalid argument", 2 => "arithmetic overflow", 3 => "limit exceeded",
|
|
62
|
+
4 => "buffer too small", 5 => "allocation failed", 6 => "encoding failed",
|
|
63
|
+
7 => "dependency version mismatch" }.freeze
|
|
64
|
+
|
|
65
|
+
def initialize
|
|
66
|
+
path = Native.send(:packaged_library_path)
|
|
67
|
+
check_packaged_file(path)
|
|
68
|
+
@handle = Fiddle::Handle.new(path, Fiddle::RTLD_NOW)
|
|
69
|
+
arguments = [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_SIZE_T,
|
|
70
|
+
Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_SIZE_T,
|
|
71
|
+
Fiddle::TYPE_SIZE_T, Fiddle::TYPE_SIZE_T]
|
|
72
|
+
@convert = Fiddle::Function.new(@handle["svgpng_v1_rgba_to_straight"], arguments, Fiddle::TYPE_INT)
|
|
73
|
+
rescue Fiddle::DLError => e
|
|
74
|
+
raise NativeError, "Cannot load packaged SVGPNG RGBA ABI: #{e.message}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def rgba_to_straight(source, width:, height:, source_stride: nil, destination_stride: nil)
|
|
78
|
+
source = snapshot_source(source)
|
|
79
|
+
source_stride = width.is_a?(Integer) ? width * 4 : nil if source_stride.nil?
|
|
80
|
+
destination_stride = width.is_a?(Integer) ? width * 4 : nil if destination_stride.nil?
|
|
81
|
+
source_bytes, destination_bytes = validate_buffers(source, width, height, source_stride, destination_stride)
|
|
82
|
+
convert(source, width, height, source_stride, destination_stride, source_bytes, destination_bytes)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def encode_rgba(source, width:, height:, source_stride: nil)
|
|
86
|
+
source = snapshot_source(source)
|
|
87
|
+
source_stride = width.is_a?(Integer) ? width * 4 : nil if source_stride.nil?
|
|
88
|
+
source_bytes = validate_buffers(source, width, height, source_stride, source_stride).first
|
|
89
|
+
encode_png(source, source_bytes, source_stride, width, height)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def snapshot_source(source)
|
|
95
|
+
# Class-side checking cannot be overridden by the untrusted source object.
|
|
96
|
+
# rubocop:disable-next Style/CaseEquality
|
|
97
|
+
raise InputError, "RGBA source must be bytes" unless String === source
|
|
98
|
+
|
|
99
|
+
# Normalize subclass methods and isolate later caller mutation before checking capacity.
|
|
100
|
+
String.new(source).byteslice(0, RenderProfile::MAX_BUFFER_BYTES).freeze
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def encoder_functions
|
|
104
|
+
@encoder_functions ||= begin
|
|
105
|
+
arguments = [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_SIZE_T,
|
|
106
|
+
Fiddle::TYPE_SIZE_T, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_VOIDP]
|
|
107
|
+
encode = Fiddle::Function.new(@handle["svgpng_v1_png_encode"], arguments, Fiddle::TYPE_INT)
|
|
108
|
+
release = Fiddle::Function.new(@handle["svgpng_v1_png_free"], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOID)
|
|
109
|
+
[encode, release].freeze
|
|
110
|
+
end
|
|
111
|
+
rescue Fiddle::DLError => e
|
|
112
|
+
raise NativeError, "Cannot load packaged SVGPNG encoder ABI: #{e.message}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Keep shim release ahead of both Ruby buffer releases on every exit path.
|
|
116
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/AbcSize
|
|
117
|
+
def encode_png(source, source_bytes, source_stride, width, height)
|
|
118
|
+
encode, release = encoder_functions
|
|
119
|
+
output = PNG_OUTPUT.malloc(Fiddle::RUBY_FREE)
|
|
120
|
+
output.bytes = 0
|
|
121
|
+
output.length = 0
|
|
122
|
+
input = Fiddle::Pointer.malloc(source_bytes, Fiddle::RUBY_FREE)
|
|
123
|
+
input[0, source_bytes] = source.byteslice(0, source_bytes)
|
|
124
|
+
status = encode.call(input, source_bytes, source_stride, width, height, output.to_ptr)
|
|
125
|
+
check_png_status(status)
|
|
126
|
+
copy_png(output)
|
|
127
|
+
ensure
|
|
128
|
+
begin
|
|
129
|
+
release.call(output.to_ptr) if output
|
|
130
|
+
ensure
|
|
131
|
+
output&.to_ptr&.call_free
|
|
132
|
+
input&.call_free
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def check_png_status(status)
|
|
137
|
+
return if status.zero?
|
|
138
|
+
|
|
139
|
+
message = PNG_STATUS_NAMES.fetch(status, "unknown status #{status}")
|
|
140
|
+
raise PNG_STATUS_ERRORS.fetch(status, NativeError), "SVGPNG PNG encoding: #{message}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def copy_png(output)
|
|
144
|
+
length = output.length
|
|
145
|
+
raise NativeError, "PNG encoder returned an empty output" unless length.positive?
|
|
146
|
+
raise LimitError, "PNG encoder output exceeds limits" if length > RenderProfile::MAX_BUFFER_BYTES
|
|
147
|
+
|
|
148
|
+
address = output.bytes.to_i
|
|
149
|
+
if !address.positive? || address > POINTER_MAX - length
|
|
150
|
+
raise NativeError, "PNG encoder returned an invalid output pointer"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
Fiddle::Pointer.new(address)[0, length].b
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def check_packaged_file(path)
|
|
157
|
+
unless File.file?(path) && !File.symlink?(path) && File.realpath(path) == path
|
|
158
|
+
raise UnsupportedPlatformError.new(platform: Gem::Platform.local.to_s, supported_targets: SUPPORTED_TARGETS)
|
|
159
|
+
end
|
|
160
|
+
rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ELOOP
|
|
161
|
+
raise UnsupportedPlatformError.new(platform: Gem::Platform.local.to_s, supported_targets: SUPPORTED_TARGETS)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Keep all pre-allocation ABI guards together for auditing against rgba.c.
|
|
165
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
166
|
+
def validate_buffers(source, width, height, source_stride, destination_stride)
|
|
167
|
+
values = [width, height, source_stride, destination_stride]
|
|
168
|
+
unless source.is_a?(String) && values.all? { |value| value.is_a?(Integer) && value.positive? }
|
|
169
|
+
raise InputError, "RGBA source must be bytes with positive integer dimensions and strides"
|
|
170
|
+
end
|
|
171
|
+
raise LimitError, "RGBA integer exceeds size_t" if values.any? { |value| value > SIZE_MAX }
|
|
172
|
+
if width > RenderProfile::MAX_DIMENSION || height > RenderProfile::MAX_DIMENSION ||
|
|
173
|
+
width * height > RenderProfile::MAX_PIXELS
|
|
174
|
+
raise LimitError, "RGBA dimensions exceed limits"
|
|
175
|
+
end
|
|
176
|
+
if [source_stride, destination_stride].min < width * 4
|
|
177
|
+
raise InputError, "RGBA stride is smaller than a pixel row"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
capacities = [source_stride * height, destination_stride * height]
|
|
181
|
+
if capacities.any? { |value| value > SIZE_MAX || value > RenderProfile::MAX_BUFFER_BYTES }
|
|
182
|
+
raise LimitError, "RGBA buffer exceeds limits"
|
|
183
|
+
end
|
|
184
|
+
raise InputError, "RGBA source buffer is too small" if source.bytesize < capacities.first
|
|
185
|
+
|
|
186
|
+
capacities
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Explicit arguments mirror the checked C ABI without a second mutable descriptor.
|
|
190
|
+
# rubocop:disable-next Metrics/ParameterLists, Metrics/MethodLength
|
|
191
|
+
def convert(source, width, height, source_stride, destination_stride, source_bytes, destination_bytes)
|
|
192
|
+
input = Fiddle::Pointer.malloc(source_bytes, Fiddle::RUBY_FREE)
|
|
193
|
+
output = Fiddle::Pointer.malloc(destination_bytes, Fiddle::RUBY_FREE)
|
|
194
|
+
input[0, source_bytes] = source.byteslice(0, source_bytes)
|
|
195
|
+
output[0, destination_bytes] = "\0".b * destination_bytes
|
|
196
|
+
status = @convert.call(input, source_bytes, source_stride, output, destination_bytes, destination_stride,
|
|
197
|
+
width, height)
|
|
198
|
+
check_status(status)
|
|
199
|
+
output[0, destination_bytes].b
|
|
200
|
+
ensure
|
|
201
|
+
output&.call_free
|
|
202
|
+
input&.call_free
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def check_status(status)
|
|
206
|
+
return if status.zero?
|
|
207
|
+
|
|
208
|
+
message = STATUS_NAMES.fetch(status, "unknown status #{status}")
|
|
209
|
+
raise STATUS_ERRORS.fetch(status, NativeError), "SVGPNG RGBA conversion: #{message}"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Svgpng
|
|
6
|
+
module Native
|
|
7
|
+
# Owns all temporary arguments and copies renderer output before releasing it.
|
|
8
|
+
module RenderBinding # rubocop:disable Metrics/ModuleLength -- Keep ABI layout and ownership guards together.
|
|
9
|
+
SIZE = Fiddle::TYPE_SIZE_T
|
|
10
|
+
PTR = Fiddle::TYPE_VOIDP
|
|
11
|
+
RENDER_OUTPUT = Fiddle::CStructBuilder.create(Fiddle::CStruct, [PTR, SIZE, PTR, SIZE],
|
|
12
|
+
%w[pixels pixels_length diagnostics diagnostics_length])
|
|
13
|
+
FONT = Fiddle::CStructBuilder.create(Fiddle::CStruct, [PTR, SIZE, PTR, SIZE, PTR, SIZE, SIZE],
|
|
14
|
+
%w[bytes length requested_family family_length source_id source_id_length
|
|
15
|
+
fallback_order])
|
|
16
|
+
OPTIONS = Fiddle::CStructBuilder.create(Fiddle::CStruct,
|
|
17
|
+
([Fiddle::TYPE_INT32_T] * 4) + ([Fiddle::TYPE_DOUBLE] * 5),
|
|
18
|
+
%w[abi_version width height background_rgba intrinsic_width
|
|
19
|
+
intrinsic_height fit translate_x translate_y])
|
|
20
|
+
ERRORS = { 1 => InputError, 2 => LimitError, 3 => FontMetadataError, 4 => DuplicateFontError,
|
|
21
|
+
5 => MissingFamilyError, 6 => MissingGlyphError, 7 => ForbiddenReferenceError,
|
|
22
|
+
8 => NativeError, 9 => NativeError }.freeze
|
|
23
|
+
DIAGNOSTIC_LIMIT = 4 * 1024 * 1024
|
|
24
|
+
|
|
25
|
+
def root_attributes(source)
|
|
26
|
+
renderer_call(source) do |input, output, _owned|
|
|
27
|
+
status = render_functions.fetch(:root).call(input, source.bytesize, output.to_ptr)
|
|
28
|
+
check_render_status(status, output)
|
|
29
|
+
attributes = copy_diagnostics(output)
|
|
30
|
+
unless attributes.is_a?(Hash) && attributes.keys.all?(String) && attributes.values.all?(String)
|
|
31
|
+
raise NativeError, "Renderer returned invalid root attributes"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
attributes
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# One call owns all buffers until both output copies complete.
|
|
39
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
|
|
40
|
+
def render_pixels(source, layout:, fonts:)
|
|
41
|
+
renderer_call(source) do |input, output, owned|
|
|
42
|
+
options = render_options(layout, owned)
|
|
43
|
+
entries, count = render_fonts(fonts, owned)
|
|
44
|
+
status = render_functions.fetch(:render).call(input, source.bytesize, entries, count,
|
|
45
|
+
options.to_ptr, output.to_ptr)
|
|
46
|
+
check_render_status(status, output)
|
|
47
|
+
unless output.pixels_length == layout.buffer_bytes
|
|
48
|
+
raise NativeError, "Renderer returned an invalid pixel buffer length"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
pixels = copy_render_bytes(output.pixels, output.pixels_length, RenderProfile::MAX_BUFFER_BYTES)
|
|
52
|
+
diagnostics = copy_diagnostics(output)
|
|
53
|
+
raise NativeError, "Renderer returned invalid diagnostics" unless diagnostics.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
[pixels, diagnostics]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def render_functions
|
|
62
|
+
@render_functions ||= {
|
|
63
|
+
root: Fiddle::Function.new(@handle["svgpng_v1_root_attributes"], [PTR, SIZE, PTR], Fiddle::TYPE_INT),
|
|
64
|
+
render: Fiddle::Function.new(@handle["svgpng_v1_render"], [PTR, SIZE, PTR, SIZE, PTR, PTR], Fiddle::TYPE_INT),
|
|
65
|
+
free: Fiddle::Function.new(@handle["svgpng_v1_render_free"], [PTR], Fiddle::TYPE_VOID)
|
|
66
|
+
}.freeze
|
|
67
|
+
rescue Fiddle::DLError
|
|
68
|
+
raise NativeError, "Cannot load packaged SVGPNG renderer ABI"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Release the shim descriptor before any caller-owned arguments.
|
|
72
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
73
|
+
def renderer_call(source)
|
|
74
|
+
functions = render_functions
|
|
75
|
+
owned = []
|
|
76
|
+
input = own_bytes(source, owned)
|
|
77
|
+
output = RENDER_OUTPUT.new(own_buffer(RENDER_OUTPUT.size, owned))
|
|
78
|
+
output.to_ptr[0, RENDER_OUTPUT.size] = "\0" * RENDER_OUTPUT.size
|
|
79
|
+
yield input, output, owned
|
|
80
|
+
ensure
|
|
81
|
+
begin
|
|
82
|
+
functions.fetch(:free).call(output.to_ptr) if output
|
|
83
|
+
ensure
|
|
84
|
+
owned&.reverse_each(&:call_free)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def own_buffer(length, owned)
|
|
89
|
+
Fiddle::Pointer.malloc([length, 1].max, Fiddle::RUBY_FREE).tap { |pointer| owned << pointer }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def own_bytes(bytes, owned)
|
|
93
|
+
pointer = own_buffer(bytes.bytesize, owned)
|
|
94
|
+
pointer[0, bytes.bytesize] = bytes
|
|
95
|
+
pointer
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Mirror the C ABI fields directly for alignment auditing.
|
|
99
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
100
|
+
def render_options(layout, owned)
|
|
101
|
+
options = OPTIONS.new(own_buffer(OPTIONS.size, owned))
|
|
102
|
+
options.abi_version = 1
|
|
103
|
+
options.width = layout.width
|
|
104
|
+
options.height = layout.height
|
|
105
|
+
options.background_rgba = layout.background.inject(0) { |value, channel| (value << 8) | channel }
|
|
106
|
+
%i[intrinsic_width intrinsic_height translate_x translate_y].each do |key|
|
|
107
|
+
options[key.to_s] = layout.public_send(key).to_f
|
|
108
|
+
end
|
|
109
|
+
options.fit = layout.scale.to_f
|
|
110
|
+
options
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Keep every borrowed font pointer beside its owned allocation.
|
|
114
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
|
|
115
|
+
def render_fonts(inputs, owned)
|
|
116
|
+
faces = inputs.fonts.flat_map { |family, entries| entries.map { |face| [face, family, Library::SIZE_MAX] } }
|
|
117
|
+
faces.concat(inputs.fallback_fonts.each_with_index.map { |face, order| [face, face.family || "", order] })
|
|
118
|
+
pointer = own_buffer(FONT.size * faces.length, owned)
|
|
119
|
+
faces.each_with_index do |(face, family, order), index|
|
|
120
|
+
entry = FONT.new(pointer + (index * FONT.size))
|
|
121
|
+
entry.bytes = own_bytes(face.bytes, owned)
|
|
122
|
+
entry.length = face.bytes.bytesize
|
|
123
|
+
entry.requested_family = own_bytes(family, owned)
|
|
124
|
+
entry.family_length = family.bytesize
|
|
125
|
+
entry.source_id = own_bytes(face.source_id, owned)
|
|
126
|
+
entry.source_id_length = face.source_id.bytesize
|
|
127
|
+
entry.fallback_order = order
|
|
128
|
+
end
|
|
129
|
+
[pointer, faces.length]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def check_render_status(status, output)
|
|
133
|
+
return if status.zero?
|
|
134
|
+
|
|
135
|
+
error = ERRORS.fetch(status, NativeError)
|
|
136
|
+
details = render_error_details(status, output)
|
|
137
|
+
raise error.new("SVGPNG rendering failed (status #{status})", **details)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Native prose and unknown fields never cross the public error boundary.
|
|
141
|
+
def render_error_details(status, output)
|
|
142
|
+
return {} unless [5, 6, 7].include?(status)
|
|
143
|
+
|
|
144
|
+
diagnostics = copy_diagnostics(output)
|
|
145
|
+
detail = diagnostics["error"] if diagnostics.is_a?(Hash)
|
|
146
|
+
return {} unless detail.is_a?(Hash)
|
|
147
|
+
return reference_error_details(detail) if status == 7
|
|
148
|
+
|
|
149
|
+
fields = { requested_family: error_family(detail["requested_family"]) }
|
|
150
|
+
fields[:scalars] = error_scalars(detail["scalars"]) if status == 6
|
|
151
|
+
fields
|
|
152
|
+
rescue Error
|
|
153
|
+
{} # Optional diagnostics cannot replace the native status classification.
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def error_family(value)
|
|
157
|
+
return unless value.is_a?(String) && value.bytesize.between?(1, 1024) && value.valid_encoding?
|
|
158
|
+
return if /[[:cntrl:]]/.match?(value)
|
|
159
|
+
|
|
160
|
+
value
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def error_scalars(value)
|
|
164
|
+
return unless value.is_a?(Array) && value.length <= 256
|
|
165
|
+
return unless value.all? do |scalar|
|
|
166
|
+
scalar.is_a?(Integer) && scalar.between?(0, 0x10FFFF) && !scalar.between?(0xD800, 0xDFFF)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
value.uniq.sort
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def reference_error_details(detail)
|
|
173
|
+
reference_class = detail["reference_class"]
|
|
174
|
+
location = detail["location"]
|
|
175
|
+
classes = %w[relative absolute file network scheme css_import doctype]
|
|
176
|
+
locations = %w[image@href use@href feImage@href svg@href style@url style@import attribute@url document@doctype]
|
|
177
|
+
{ reference_class: classes.include?(reference_class) ? reference_class : nil,
|
|
178
|
+
location: locations.include?(location) ? location : nil }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def copy_diagnostics(output)
|
|
182
|
+
bytes = copy_render_bytes(output.diagnostics, output.diagnostics_length, DIAGNOSTIC_LIMIT)
|
|
183
|
+
JSON.parse(bytes, max_nesting: 32)
|
|
184
|
+
rescue JSON::ParserError
|
|
185
|
+
raise NativeError, "Renderer returned malformed diagnostics"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def copy_render_bytes(pointer, length, limit)
|
|
189
|
+
raise LimitError, "Renderer output exceeds limits" if length > limit
|
|
190
|
+
if !length.positive? || !pointer.to_i.positive? || pointer.to_i > Library::POINTER_MAX - length
|
|
191
|
+
raise NativeError, "Renderer returned an invalid output pointer"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
Fiddle::Pointer.new(pointer.to_i)[0, length].b
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|