vectory 0.13.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66b15c386fa1dcdd0fc02473b9062f3eca19ed5b1bc40f2facec9577a4495fd4
4
- data.tar.gz: d5bc9be14ab660ddbbba77548a205a472eb4215bb848c57d5e3d980205159da4
3
+ metadata.gz: f57a3fbf7c5b0f3c7660e32dbd1f6361d149ee27a9eef68418ced42ab0d4416b
4
+ data.tar.gz: 0e6dcda6b904e457b75ee3091ee27f91c6790da056f289003f289f585a66148b
5
5
  SHA512:
6
- metadata.gz: 9e802267d5860c496738492218e4fd8260ff9c84546d63c11254d170f14ee1c8c9bebd24a943b24e8bcd5c7013db8a3d55844aba286d06f5959a717fe289da34
7
- data.tar.gz: 53747660f38c403952debd623ad81dbce990e51fa8098d63480762c3de40531cc2602b903c943c91e01f9678433b6340b77529345ad487f53b4fb2c04ca4bfe6
6
+ metadata.gz: bdd45abe6b12a83b16d04a63fca16fa3052552909ac1a16de73623706d0b2ee8dd84a958baec8e60e14c6d4b4037c6832ec55abddda3acb94b971cd5bbbbe206
7
+ data.tar.gz: 9761d2d5b4169f87fbbc46555ac74ab8d3d43ad80260aadd07bc6a77aa7d0c6ea564854bef42f70b02a383a1647610a1770af8d200a43881a91001386bec2f3d
@@ -1,5 +1,7 @@
1
- # Auto-generated by Cimas: Do not edit it manually!
2
- # See https://github.com/metanorma/cimas
1
+ # Trusted publishing (OIDC) release — matches lutaml-model/.github/workflows/release.yml.
2
+ # metanorma/ci switches to OIDC when the rubygems-api-key secret is omitted;
3
+ # each gem must be registered for trusted publishing on rubygems.org
4
+ # (owner=claricle, workflow=release.yml).
3
5
  name: release
4
6
 
5
7
  on:
@@ -10,19 +12,21 @@ on:
10
12
  Next release version. Possible values: x.y.z, major, minor, patch or pre|rc|etc
11
13
  required: true
12
14
  default: 'skip'
13
- push:
14
- tags: [ v* ]
15
+ repository_dispatch:
16
+ types: [ do-release ]
15
17
 
18
+ # Least-privilege ceiling for the called rubygems-release.yml: its release job
19
+ # needs contents:write (git tag push) and id-token:write (OIDC Trusted Publishing).
16
20
  permissions:
17
21
  contents: write
18
- packages: write
19
22
  id-token: write
20
23
 
24
+ concurrency:
25
+ group: release
26
+ cancel-in-progress: false
27
+
21
28
  jobs:
22
29
  release:
23
30
  uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
24
31
  with:
25
32
  next_version: ${{ github.event.inputs.next_version }}
26
- secrets:
27
- rubygems-api-key: ${{ secrets.CLARICLE_CI_RUBYGEMS_API_KEY }}
28
- pat_token: ${{ secrets.CLARICLE_CI_PAT_TOKEN }}
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vectory
4
- VERSION = "0.13.0"
4
+ VERSION = "0.13.2"
5
5
  end
data/lib/vectory.rb CHANGED
@@ -37,6 +37,7 @@ module Vectory
37
37
  autoload :Svg, "vectory/svg"
38
38
  autoload :SvgMapping, "vectory/svg_mapping"
39
39
  autoload :SvgDocument, "vectory/svg_document"
40
+ autoload :SvgIdDisambiguator, "vectory/svg_id_disambiguator"
40
41
  autoload :FileMagic, "vectory/file_magic"
41
42
  end
42
43
 
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"
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency "emfsvg", "~> 0.1", ">= 0.1.2"
31
31
  spec.add_dependency "image_size", ">= 3.2.0"
32
32
  spec.add_dependency "marcel", "~> 1.0"
33
- spec.add_dependency "leptris", "~> 1.9.361.1"
33
+ spec.add_dependency "leptris", ">= 1.9.361.1"
34
34
  spec.add_dependency "postsvg", "~> 0.3"
35
35
  spec.add_dependency "thor", "~> 1.0"
36
36
  end
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.0
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-16 00:00:00.000000000 Z
11
+ date: 2026-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -76,14 +76,14 @@ dependencies:
76
76
  name: leptris
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - "~>"
79
+ - - ">="
80
80
  - !ruby/object:Gem::Version
81
81
  version: 1.9.361.1
82
82
  type: :runtime
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - "~>"
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
88
  version: 1.9.361.1
89
89
  - !ruby/object:Gem::Dependency
@@ -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