vectory 0.13.1 → 0.13.2
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 +4 -4
- data/lib/vectory/svg_id_disambiguator.rb +138 -0
- data/lib/vectory/version.rb +1 -1
- data/lib/vectory.rb +1 -0
- data/vectory.gemspec +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f57a3fbf7c5b0f3c7660e32dbd1f6361d149ee27a9eef68418ced42ab0d4416b
|
|
4
|
+
data.tar.gz: 0e6dcda6b904e457b75ee3091ee27f91c6790da056f289003f289f585a66148b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdd45abe6b12a83b16d04a63fca16fa3052552909ac1a16de73623706d0b2ee8dd84a958baec8e60e14c6d4b4037c6832ec55abddda3acb94b971cd5bbbbe206
|
|
7
|
+
data.tar.gz: 9761d2d5b4169f87fbbc46555ac74ab8d3d43ad80260aadd07bc6a77aa7d0c6ea564854bef42f70b02a383a1647610a1770af8d200a43881a91001386bec2f3d
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require "leptris"
|
|
2
|
+
|
|
3
|
+
module Vectory
|
|
4
|
+
# Disambiguates element ids across multiple SVG documents embedded in one
|
|
5
|
+
# output (the multi-svg collision problem): ids that appear in more than
|
|
6
|
+
# one SVG get a per-document suffix, and every reference form is rewritten
|
|
7
|
+
# to match — `url(#id)` in presentation attributes and `<style>` blocks,
|
|
8
|
+
# `href`/`xlink:href` fragment references, and the `id` attributes
|
|
9
|
+
# themselves.
|
|
10
|
+
#
|
|
11
|
+
# This is the string-in/string-out counterpart of SvgMapping's document
|
|
12
|
+
# processing: content in, processed content out, same as SvgMapping's
|
|
13
|
+
# "serialize on entry" contract. Ported from metanorma-standoc's
|
|
14
|
+
# svg_uniqueids cleanup (lib/metanorma/cleanup/image.rb), where it was
|
|
15
|
+
# written against Nokogiri inline.
|
|
16
|
+
#
|
|
17
|
+
# Usage — N embedded SVGs, disambiguate by position:
|
|
18
|
+
#
|
|
19
|
+
# dupes = Vectory::SvgIdDisambiguator.duplicate_ids(svgs)
|
|
20
|
+
# svgs.map.with_index { |svg, i| Vectory::SvgIdDisambiguator.rewrite(svg, "_inject_#{i}", dupes) }
|
|
21
|
+
#
|
|
22
|
+
class SvgIdDisambiguator
|
|
23
|
+
# SVG elements whose ids are referenced through presentation attributes
|
|
24
|
+
# and CSS rather than href: the tag maps to the attributes that carry
|
|
25
|
+
# the reference (nil = the tag name itself is the CSS property).
|
|
26
|
+
IRI_TAG_PROPERTIES_MAP = {
|
|
27
|
+
"clipPath" => ["clip-path"],
|
|
28
|
+
"color-profile" => nil,
|
|
29
|
+
"cursor" => nil,
|
|
30
|
+
"filter" => nil,
|
|
31
|
+
"linearGradient" => ["fill", "stroke"],
|
|
32
|
+
"marker" => ["marker", "marker-end", "marker-mid", "marker-start"],
|
|
33
|
+
"mask" => nil,
|
|
34
|
+
"pattern" => ["fill", "stroke"],
|
|
35
|
+
"radialGradient" => ["fill", "stroke"],
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
URL_RE = %r{url\("?#([a-zA-Z][\w:.-]*)"?\)}.freeze
|
|
39
|
+
private_constant :URL_RE
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
# Ids that appear more than once across the given SVG contents.
|
|
43
|
+
#
|
|
44
|
+
# @param contents [Array<String>] SVG document contents
|
|
45
|
+
# @return [Hash{String => Integer}] id => occurrence count (count >= 2)
|
|
46
|
+
def duplicate_ids(contents)
|
|
47
|
+
contents.flat_map { |c| id_values(Leptris::XML.parse(c)) }
|
|
48
|
+
.group_by(&:itself).transform_values(&:count)
|
|
49
|
+
.delete_if { |_, v| v < 2 }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Suffix every id in +duplicate_ids+ with +suffix+ inside +content+,
|
|
53
|
+
# rewriting all reference forms to match. Ids absent from the map are
|
|
54
|
+
# left untouched.
|
|
55
|
+
#
|
|
56
|
+
# @param content [String] SVG document content
|
|
57
|
+
# @param suffix [String] the full disambiguator, separator included
|
|
58
|
+
# (e.g. "_inject_0")
|
|
59
|
+
# @param duplicate_ids [Hash{String => Integer}] from .duplicate_ids
|
|
60
|
+
# @return [String] the rewritten content
|
|
61
|
+
def rewrite(content, suffix, duplicate_ids)
|
|
62
|
+
doc = Leptris::XML.parse(content)
|
|
63
|
+
svg_roots(doc).each { |svg| rewrite_svg(svg, suffix, duplicate_ids) }
|
|
64
|
+
doc.to_xml
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def svg_roots(doc)
|
|
70
|
+
root = doc.root
|
|
71
|
+
if root&.name == "svg"
|
|
72
|
+
[root]
|
|
73
|
+
else
|
|
74
|
+
# SVGs embedded in a host document
|
|
75
|
+
doc.xpath("//*[local-name()='svg']")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def id_values(doc)
|
|
80
|
+
doc.xpath("//*").filter_map { |e| e["id"] if e["id"] }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def rewrite_svg(svg, suffix, ids)
|
|
84
|
+
id_elems = svg.xpath(".//*[@id]")
|
|
85
|
+
id_elems = [svg] + id_elems if svg["id"]
|
|
86
|
+
iri_properties = iri_properties(id_elems)
|
|
87
|
+
([svg] + svg.xpath("//*")).each { |e| rewrite_element(e, iri_properties, suffix, ids) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def rewrite_element(elem, iri_properties, suffix, ids)
|
|
91
|
+
if elem.name == "style"
|
|
92
|
+
elem.children = rewrite_urls(elem.text, suffix, ids)
|
|
93
|
+
return
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# All attribute reads AND writes go through attribute_nodes: mixing
|
|
97
|
+
# in the elem[...] accessors after an id write desyncs the native
|
|
98
|
+
# attribute layer (leptris-ruby#526).
|
|
99
|
+
nodes = elem.attribute_nodes || []
|
|
100
|
+
return if nodes.empty?
|
|
101
|
+
|
|
102
|
+
iri_properties.each do |p|
|
|
103
|
+
node = nodes.find { |a| a.name == p } or next
|
|
104
|
+
node.value = rewrite_urls(node.value, suffix, ids)
|
|
105
|
+
end
|
|
106
|
+
nodes.each do |a|
|
|
107
|
+
case a.name
|
|
108
|
+
when "id"
|
|
109
|
+
a.value = "#{a.value}#{suffix}" if ids[a.value]
|
|
110
|
+
when "xlink:href", "href"
|
|
111
|
+
v = a.value.to_s.strip
|
|
112
|
+
a.value = "#{a.value}#{suffix}" if v.start_with?("#") && ids[v.sub(/\A#/, "")]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The CSS-referenceable properties present among the id-bearing
|
|
118
|
+
# elements, plus "style" when any exist (standoc svg_iri_properties).
|
|
119
|
+
def iri_properties(id_elems)
|
|
120
|
+
iri_tag_names = id_elems.filter_map do |e|
|
|
121
|
+
e.name if IRI_TAG_PROPERTIES_MAP.key?(e.name)
|
|
122
|
+
end.uniq
|
|
123
|
+
iri_properties = iri_tag_names.flat_map do |t|
|
|
124
|
+
IRI_TAG_PROPERTIES_MAP[t] || [t]
|
|
125
|
+
end.uniq
|
|
126
|
+
return [] if iri_properties.empty?
|
|
127
|
+
|
|
128
|
+
iri_properties + ["style"]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def rewrite_urls(text, suffix, ids)
|
|
132
|
+
text.gsub(URL_RE) do |x|
|
|
133
|
+
ids[$1] ? "url(##{$1}#{suffix})" : x
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/vectory/version.rb
CHANGED
data/lib/vectory.rb
CHANGED
data/vectory.gemspec
CHANGED
|
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.executables = ["vectory"]
|
|
22
22
|
spec.require_paths = ["lib"]
|
|
23
23
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
-
f.match(%r{^(bin|spec)/})
|
|
24
|
+
f.match(%r{^(bin|spec|TODO\.[^/]+)/})
|
|
25
25
|
end
|
|
26
26
|
spec.test_files = `git ls-files -- {spec}/*`.split("\n")
|
|
27
27
|
spec.required_ruby_version = ">= 3.1.0"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vectory
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.13.
|
|
4
|
+
version: 0.13.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -168,6 +168,7 @@ files:
|
|
|
168
168
|
- lib/vectory/ps.rb
|
|
169
169
|
- lib/vectory/svg.rb
|
|
170
170
|
- lib/vectory/svg_document.rb
|
|
171
|
+
- lib/vectory/svg_id_disambiguator.rb
|
|
171
172
|
- lib/vectory/svg_mapping.rb
|
|
172
173
|
- lib/vectory/utils.rb
|
|
173
174
|
- lib/vectory/vector.rb
|