vectory 0.2.0 → 0.4.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/README.adoc +167 -0
  4. data/lib/vectory/datauri.rb +13 -0
  5. data/lib/vectory/image.rb +4 -0
  6. data/lib/vectory/inkscape_converter.rb +82 -11
  7. data/lib/vectory/svg.rb +84 -1
  8. data/lib/vectory/svg_mapping.rb +118 -0
  9. data/lib/vectory/system_call.rb +10 -2
  10. data/lib/vectory/utils.rb +111 -0
  11. data/lib/vectory/vector.rb +25 -2
  12. data/lib/vectory/version.rb +1 -1
  13. data/lib/vectory.rb +3 -0
  14. data/spec/examples/svg/action_schemaexpg1.svg +124 -0
  15. data/spec/examples/svg/action_schemaexpg2.svg +124 -0
  16. data/spec/examples/svg/doc-ref.xml +109 -0
  17. data/spec/examples/svg/doc.xml +51 -0
  18. data/spec/examples/svg/doc2-ref.xml +59 -0
  19. data/spec/examples/svg/doc2.xml +28 -0
  20. data/spec/support/matchers.rb +12 -0
  21. data/spec/support/vectory_helper.rb +14 -0
  22. data/spec/vectory/cli_spec.rb +15 -14
  23. data/spec/vectory/datauri_spec.rb +80 -0
  24. data/spec/vectory/emf_spec.rb +27 -3
  25. data/spec/vectory/eps_spec.rb +36 -9
  26. data/spec/vectory/file_magic_spec.rb +3 -3
  27. data/spec/vectory/inkscape_converter_spec.rb +11 -6
  28. data/spec/vectory/ps_spec.rb +28 -3
  29. data/spec/vectory/svg_mapping_spec.rb +42 -0
  30. data/spec/vectory/svg_spec.rb +43 -3
  31. data/spec/vectory/vector_spec.rb +37 -0
  32. data/vectory.gemspec +0 -4
  33. metadata +22 -70
  34. /data/spec/examples/emf2eps/{img.eps → ref.eps} +0 -0
  35. /data/spec/examples/emf2ps/{img.ps → ref.ps} +0 -0
  36. /data/spec/examples/emf2svg/{img.svg → ref.svg} +0 -0
  37. /data/spec/examples/eps2emf/{img.emf → ref.emf} +0 -0
  38. /data/spec/examples/eps2ps/{img.ps → ref.ps} +0 -0
  39. /data/spec/examples/eps2svg/{img.svg → ref.svg} +0 -0
  40. /data/spec/examples/ps2emf/{img.emf → ref.emf} +0 -0
  41. /data/spec/examples/ps2eps/{img.eps → ref.eps} +0 -0
  42. /data/spec/examples/ps2svg/{img.svg → ref.svg} +0 -0
  43. /data/spec/examples/svg2emf/{img.emf → ref.emf} +0 -0
  44. /data/spec/examples/svg2eps/{img.eps → ref.eps} +0 -0
  45. /data/spec/examples/svg2ps/{img.ps → ref.ps} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c46e34eee9accc66e136b741440e46269744e0f9cf8a1125382fe3ec1959b320
4
- data.tar.gz: 95a3dcf51104cc31ab37af1b0ecc39861c7717399056b0116cfd94562a66126a
3
+ metadata.gz: 2f51d610adc333c06f92e8e28ca9722a890f12b03668bf12e96ce057d2574dea
4
+ data.tar.gz: 792bc815a0b86a6211b83268f16b9abe7a138aba0081a2d7a95242c5e9a7af82
5
5
  SHA512:
6
- metadata.gz: 8775fc8bda0a86df3bc2c4bde5ab303d861a6d5812764f8b2c40a540a0f0369a42b55cd1bac655acc3ccf37348aa976620709ae41c35d26680ae221579e416e9
7
- data.tar.gz: c880d981f9c7a26182bacf9a0ee0d0df0f5147d493f79de45bade50e813c25d258400157be70b8987f605b3d4a9b4c3217b7974d40da174503de6e074183e573
6
+ metadata.gz: 249d3f2fa7df2d216cdf5d046647507b81a55bd5db56a603a90a3890501adb10e8a03f41b272385c65a8de48c50b8b52db743ec5ddc20be88e4e5aab6ed21ca6
7
+ data.tar.gz: 5ed331050b3c4a3231cabc7ba061695f017de5ea44a3056bf01537144b12c96cf5587c0a52793d3d435d961052210aefed73a52c4afc9c3563695fca8e23c91d
data/Gemfile CHANGED
@@ -9,4 +9,6 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
- gem "rubocop", "~> 1.21"
12
+ gem "rubocop", "~> 1.22"
13
+ gem "rubocop-performance", "~> 1.10"
14
+ gem "rubocop-rails", "~> 2.9"
data/README.adoc CHANGED
@@ -27,6 +27,9 @@ Vectory relies on the following software to be installed:
27
27
  * https://inkscape.org[Inkscape]
28
28
  * https://www.ghostscript.com/[Ghostscript]
29
29
 
30
+ NOTE: Inkscape 1.3.1 does not work properly with EPS/PS on Windows. To avoid
31
+ this issue, the 1.3.0 version of Inkscape can be used.
32
+
30
33
 
31
34
  === Gem install
32
35
 
@@ -157,6 +160,131 @@ Vectory::Eps.from_path("img.eps").to_uri.content
157
160
  ----
158
161
 
159
162
 
163
+ ==== SVG mapping (for the metanorma project)
164
+
165
+ Vectory can integrate SVG files into XML or HTML, respecting internal id and
166
+ link references:
167
+
168
+ [source,ruby]
169
+ ----
170
+ xml_string = Vectory::SvgMapping.from_path("doc.xml").call
171
+ ----
172
+
173
+ In order to do that an initial XML should support the `svgmap` tag with links
174
+ mapping. For example, it can convert XML like this:
175
+
176
+ [source,xml]
177
+ ----
178
+ <svgmap id="_4072bdcb-5895-4821-b636-5795b96787cb">
179
+ <figure><image src="action_schemaexpg1.svg"/></figure>
180
+ <target href="mn://action_schema">
181
+ <xref target="ref1">Computer</xref>
182
+ </target>
183
+ <target href="http://www.example.com">
184
+ <link target="http://www.example.com">Phone</link><
185
+ /target>
186
+ </svgmap>
187
+ ----
188
+
189
+ .action_schemaexpg1.svg
190
+ [source,xml]
191
+ ----
192
+ <?xml version="1.0" encoding="utf-8"?>
193
+ <!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
194
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
195
+ viewBox="0 0 595.28 841.89" style="enable-background:new 0 0 595.28 841.89;" xml:space="preserve">
196
+ <style type="text/css">
197
+ #Layer_1 { fill:none }
198
+ svg[id = 'Layer_1'] { fill:none }
199
+ .st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
200
+ </style>
201
+ <image style="overflow:visible;" width="368" height="315" xlink:href="data:image/gif;base64,R0lG..ommited to save space" transform="matrix(1 0 0 1 114 263.8898)">
202
+ </image>
203
+ <a xlink:href="mn://action_schema" xlink:dummy="Layer_1">
204
+ <rect x="123.28" y="273.93" class="st0" width="88.05" height="41.84"/>
205
+ </a>
206
+ <a xlink:href="mn://basic_attribute_schema" >
207
+ <rect x="324.69" y="450.52" class="st0" width="132.62" height="40.75"/>
208
+ </a>
209
+ <a xlink:href="mn://support_resource_schema" >
210
+ <rect x="324.69" y="528.36" class="st0" width="148.16" height="40.75"/>
211
+ </a>
212
+ </svg>
213
+ ----
214
+
215
+ into XML containing inline SVG tags. Notice changes in the `id` attributes and
216
+ the `a` tags:
217
+
218
+ [source,xml]
219
+ ----
220
+ <figure>
221
+ <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1_000000001' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
222
+ <style> ..ommited to save space </style>
223
+ <image> ..ommited </image>
224
+ <a xlink:href='#ref1' xlink:dummy='Layer_1_000000001'>
225
+ <rect x='123.28' y='273.93' class='st0' width='88.05' height='41.84'/>
226
+ </a>
227
+ <a xlink:href='mn://basic_attribute_schema'>
228
+ <rect x='324.69' y='450.52' class='st0' width='132.62' height='40.75'/>
229
+ </a>
230
+ <a xlink:href='mn://support_resource_schema'>
231
+ <rect x='324.69' y='528.36' class='st0' width='148.16' height='40.75'/>
232
+ </a>
233
+ </svg>
234
+ </figure>
235
+ ----
236
+
237
+ It also supports SVG in a form of an inline tag:
238
+
239
+ [source,xml]
240
+ ----
241
+ <svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
242
+ <figure>
243
+ <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
244
+ <a href="mn://action_schema" >
245
+ <rect x="123.28" y="273.93" class="st0" width="88.05" height="41.84"/>
246
+ </a>
247
+ <a href="mn://basic_attribute_schema" >
248
+ <rect x="324.69" y="450.52" class="st0" width="132.62" height="40.75"/>
249
+ </a>
250
+ <a xlink:href="mn://support_resource_schema" >
251
+ <rect x="324.69" y="528.36" class="st0" width="148.16" height="40.75"/>
252
+ </a>
253
+ </svg>
254
+ </figure>
255
+ <target href="mn://action_schema">
256
+ <xref target="ref1">Computer</xref>
257
+ </target>
258
+ <target href="http://www.example.com">
259
+ <link target="http://www.example.com">Phone</link>
260
+ </target>
261
+ </svgmap>
262
+ ----
263
+
264
+ and datauri:
265
+
266
+ [source,xml]
267
+ ----
268
+ <svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
269
+ <figure>
270
+ <image src='data:image/svg+xml;base64,PD94..ommited to save space' id='__ISO_17301-1_2016' mimetype='image/svg+xml' height='auto' width='auto' alt='Workmap1'/>
271
+ </figure>
272
+ <target href="href1.htm">
273
+ <xref target="ref1">Computer</xref>
274
+ </target>
275
+ <target href="mn://basic_attribute_schema">
276
+ <link target="http://www.example.com">Phone</link>
277
+ </target>
278
+ <target href="mn://support_resource_schema">
279
+ <eref type="express" bibitemid="express_action_schema" citeas="">
280
+ <localityStack><locality type="anchor"><referenceFrom>action_schema.basic</referenceFrom></locality></localityStack>
281
+ Coffee
282
+ </eref>
283
+ </target>
284
+ </svgmap>
285
+ ----
286
+
287
+
160
288
  ==== File system operations
161
289
 
162
290
  An image object contains information where it is written. It can be obtained
@@ -201,6 +329,45 @@ purposes.
201
329
  vector.initial_path # => "storage/images/img.eps"
202
330
  ----
203
331
 
332
+ ==== Additional properties
333
+
334
+ The following additional properties are supported:
335
+
336
+ [source,ruby]
337
+ ----
338
+ Datauri#mime
339
+ Datauri#height
340
+ Datauri#width
341
+ Vector (Eps, Ps, Svg, Emf)
342
+ Vector#mime
343
+ Vector#size
344
+ Vector#file_size
345
+ Vector#height
346
+ Vector#width
347
+ ----
348
+
349
+
350
+ == Development
351
+
352
+ === Releasing
353
+
354
+ Releasing is done automatically with GitHub Actions. Just bump and tag with
355
+ `gem-release`.
356
+
357
+ For a patch release (0.0.x) use:
358
+
359
+ [source,sh]
360
+ ----
361
+ gem bump --version patch --tag --push
362
+ ----
363
+
364
+ For a minor release (0.x.0) use:
365
+
366
+ [source,sh]
367
+ ----
368
+ gem bump --version minor --tag --push
369
+ ----
370
+
204
371
 
205
372
  == Contributing
206
373
 
@@ -18,6 +18,19 @@ module Vectory
18
18
  new("data:#{mimetype};base64,#{data}")
19
19
  end
20
20
 
21
+ def mime
22
+ match = parse_datauri(@content)
23
+ match[:mimetype]
24
+ end
25
+
26
+ def height
27
+ to_vector.height
28
+ end
29
+
30
+ def width
31
+ to_vector.width
32
+ end
33
+
21
34
  def to_vector
22
35
  match = parse_datauri(@content)
23
36
  content = Base64.strict_decode64(match[:data])
data/lib/vectory/image.rb CHANGED
@@ -19,5 +19,9 @@ module Vectory
19
19
  def initialize(content)
20
20
  @content = content
21
21
  end
22
+
23
+ private
24
+
25
+ attr_writer :content
22
26
  end
23
27
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "singleton"
4
+ require "tmpdir"
4
5
  require_relative "system_call"
5
6
 
6
7
  module Vectory
@@ -12,43 +13,78 @@ module Vectory
12
13
  end
13
14
 
14
15
  def convert(uri, output_extension, option)
15
- exe = inkscape_path_or_raise_error(uri)
16
+ exe = inkscape_path_or_raise_error
16
17
  uri = external_path uri
17
18
  exe = external_path exe
18
19
  cmd = %(#{exe} #{option} #{uri})
19
20
 
20
21
  call = SystemCall.new(cmd).call
21
22
 
22
- output_path = "#{uri}.#{output_extension}"
23
- raise_conversion_error(call) unless File.exist?(output_path)
23
+ output_path = find_output(uri, output_extension)
24
+ raise_conversion_error(call) unless output_path
24
25
 
25
26
  # and return Vectory::Utils::datauri(file)
26
27
 
27
28
  output_path
28
29
  end
29
30
 
31
+ def height(content, format)
32
+ query_integer(content, format, "--query-height")
33
+ end
34
+
35
+ def width(content, format)
36
+ query_integer(content, format, "--query-width")
37
+ end
38
+
30
39
  private
31
40
 
32
- def inkscape_path_or_raise_error(path)
41
+ def inkscape_path_or_raise_error
33
42
  inkscape_path or raise(InkscapeNotFoundError,
34
43
  "Inkscape missing in PATH, unable to " \
35
- "convert image #{path}. Aborting.")
44
+ "convert image. Aborting.")
36
45
  end
37
46
 
38
47
  def inkscape_path
39
- cmd = "inkscape"
40
- exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
48
+ @inkscape_path ||= find_inkscape
49
+ end
41
50
 
42
- ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
43
- exts.each do |ext|
44
- exe = File.join(path, "#{cmd}#{ext}")
45
- return exe if File.executable?(exe) && !File.directory?(exe)
51
+ def find_inkscape
52
+ cmds.each do |cmd|
53
+ extensions.each do |ext|
54
+ paths.each do |path|
55
+ exe = File.join(path, "#{cmd}#{ext}")
56
+
57
+ return exe if File.executable?(exe) && !File.directory?(exe)
58
+ end
46
59
  end
47
60
  end
48
61
 
49
62
  nil
50
63
  end
51
64
 
65
+ def cmds
66
+ ["inkscapecom", "inkscape"]
67
+ end
68
+
69
+ def extensions
70
+ ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
71
+ end
72
+
73
+ def paths
74
+ ENV["PATH"].split(File::PATH_SEPARATOR)
75
+ end
76
+
77
+ def find_output(source_path, output_extension)
78
+ basenames = [File.basename(source_path, ".*"),
79
+ File.basename(source_path)]
80
+
81
+ paths = basenames.map do |basename|
82
+ "#{File.join(File.dirname(source_path), basename)}.#{output_extension}"
83
+ end
84
+
85
+ paths.find { |p| File.exist?(p) }
86
+ end
87
+
52
88
  def raise_conversion_error(call)
53
89
  raise Vectory::ConversionError,
54
90
  "Could not convert with Inkscape. " \
@@ -68,5 +104,40 @@ module Vectory
68
104
  path
69
105
  end
70
106
  end
107
+
108
+ def query_integer(content, format, options)
109
+ query(content, format, options).to_f.round
110
+ end
111
+
112
+ def query(content, format, options)
113
+ exe = inkscape_path_or_raise_error
114
+
115
+ with_file(content, format) do |path|
116
+ cmd = "#{external_path(exe)} #{options} #{external_path(path)}"
117
+
118
+ call = SystemCall.new(cmd).call
119
+ raise_query_error(call) if call.stdout.empty?
120
+
121
+ call.stdout
122
+ end
123
+ end
124
+
125
+ def with_file(content, extension)
126
+ Dir.mktmpdir do |dir|
127
+ path = File.join(dir, "image.#{extension}")
128
+ File.binwrite(path, content)
129
+
130
+ yield path
131
+ end
132
+ end
133
+
134
+ def raise_query_error(call)
135
+ raise Vectory::InkscapeQueryError,
136
+ "Could not query with Inkscape. " \
137
+ "Inkscape cmd: '#{call.cmd}',\n" \
138
+ "status: '#{call.status}',\n" \
139
+ "stdout: '#{call.stdout.strip}',\n" \
140
+ "stderr: '#{call.stderr.strip}'."
141
+ end
71
142
  end
72
143
  end
data/lib/vectory/svg.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "nokogiri"
4
+
3
5
  module Vectory
4
6
  class Svg < Vector
5
- SVG = { "m" => "http://www.w3.org/2000/svg" }.freeze
7
+ SVG_NS = "http://www.w3.org/2000/svg"
6
8
 
7
9
  def self.default_extension
8
10
  "svg"
@@ -12,6 +14,10 @@ module Vectory
12
14
  "image/svg+xml"
13
15
  end
14
16
 
17
+ def content
18
+ @document&.to_xml || @content
19
+ end
20
+
15
21
  def to_emf
16
22
  convert_with_inkscape("--export-type=emf", Emf)
17
23
  end
@@ -23,5 +29,82 @@ module Vectory
23
29
  def to_ps
24
30
  convert_with_inkscape("--export-type=ps", Ps)
25
31
  end
32
+
33
+ def namespace(suffix, links, xpath_to_remove)
34
+ remap_links(links)
35
+ suffix_ids(suffix)
36
+ remove_xpath(xpath_to_remove)
37
+ end
38
+
39
+ def remap_links(map)
40
+ document.xpath(".//m:a", "m" => SVG_NS).each do |a|
41
+ href_attrs = ["xlink:href", "href"]
42
+ href_attrs.each do |p|
43
+ a[p] and x = map[File.expand_path(a[p])] and a[p] = x
44
+ end
45
+ end
46
+
47
+ self
48
+ end
49
+
50
+ def suffix_ids(suffix)
51
+ ids = collect_ids
52
+ return if ids.empty?
53
+
54
+ update_ids_attrs(ids, suffix)
55
+ update_ids_css(ids, suffix)
56
+
57
+ self
58
+ end
59
+
60
+ def remove_xpath(xpath)
61
+ document.xpath(xpath).remove
62
+
63
+ self
64
+ end
65
+
66
+ private
67
+
68
+ def content=(content)
69
+ if @document
70
+ @document = Nokogiri::XML(content)
71
+ else
72
+ @content = content
73
+ end
74
+ end
75
+
76
+ def document
77
+ @document ||= begin
78
+ doc = Nokogiri::XML(@content)
79
+ @content = nil
80
+ doc
81
+ end
82
+ end
83
+
84
+ def collect_ids
85
+ document.xpath("./@id | .//@id").map(&:value)
86
+ end
87
+
88
+ def update_ids_attrs(ids, suffix)
89
+ document.xpath(". | .//*[@*]").each do |a|
90
+ a.attribute_nodes.each do |x|
91
+ ids.include?(x.value) and x.value += sprintf("_%09d", suffix)
92
+ end
93
+ end
94
+ end
95
+
96
+ def update_ids_css(ids, suffix)
97
+ document.xpath("//m:style", "m" => SVG_NS).each do |s|
98
+ c = s.children.to_xml
99
+ ids.each do |i|
100
+ c = c.gsub(%r[##{i}\b],
101
+ sprintf("#%<id>s_%<suffix>09d", id: i, suffix: suffix))
102
+ .gsub(%r(\[id\s*=\s*['"]?#{i}['"]?\]),
103
+ sprintf("[id='%<id>s_%<suffix>09d']", id: i, suffix: suffix))
104
+ end
105
+
106
+ s.children = c
107
+ end
108
+ end
26
109
  end
27
110
  end
@@ -0,0 +1,118 @@
1
+ require_relative "svg"
2
+
3
+ module Vectory
4
+ class SvgMapping
5
+ class Namespace
6
+ def initialize(xmldoc)
7
+ @namespace = xmldoc.root.namespace
8
+ end
9
+
10
+ def ns(path)
11
+ return path if @namespace.nil?
12
+
13
+ path.gsub(%r{/([a-zA-z])}, "/xmlns:\\1")
14
+ .gsub(%r{::([a-zA-z])}, "::xmlns:\\1")
15
+ .gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]* ?=)}, "[xmlns:\\1")
16
+ .gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]*\])}, "[xmlns:\\1")
17
+ end
18
+ end
19
+
20
+ SVG_NS = "http://www.w3.org/2000/svg".freeze
21
+ PROCESSING_XPATH =
22
+ "processing-instruction()|.//processing-instruction()".freeze
23
+
24
+ def self.from_path(path)
25
+ new(File.read(path))
26
+ end
27
+
28
+ def initialize(xml, local_directory = "")
29
+ @xml = xml
30
+ @local_directory = local_directory
31
+ end
32
+
33
+ def call
34
+ xmldoc = Nokogiri::XML(@xml)
35
+ @namespace = Namespace.new(xmldoc)
36
+
37
+ xmldoc.xpath(@namespace.ns("//svgmap")).each_with_index do |svgmap, index|
38
+ process_svgmap(svgmap, index)
39
+ end
40
+
41
+ xmldoc.to_xml
42
+ end
43
+
44
+ private
45
+
46
+ def process_svgmap(svgmap, suffix)
47
+ image = extract_image_tag(svgmap)
48
+ return unless image
49
+
50
+ content = generate_content(image, svgmap, suffix)
51
+ return unless content
52
+
53
+ image.replace(content)
54
+
55
+ simplify_svgmap(svgmap)
56
+ end
57
+
58
+ def extract_image_tag(svgmap)
59
+ image = svgmap.at(@namespace.ns(".//image"))
60
+ return image if image && image["src"] && !image["src"].empty?
61
+
62
+ svgmap.at(".//m:svg", "m" => SVG_NS)
63
+ end
64
+
65
+ def generate_content(image, svgmap, suffix)
66
+ vector = build_vector(image)
67
+ return unless vector
68
+
69
+ links_map = from_targets_to_links_map(svgmap)
70
+ vector.namespace(suffix, links_map, PROCESSING_XPATH)
71
+
72
+ vector.content
73
+ end
74
+
75
+ def build_vector(image)
76
+ return Vectory::Svg.from_content(image.to_xml) if image.name == "svg"
77
+
78
+ return unless image.name == "image"
79
+
80
+ src = image["src"]
81
+ return Vectory::Datauri.new(src).to_vector if /^data:/.match?(src)
82
+
83
+ path = @local_directory.empty? ? src : File.join(@local_directory, src)
84
+ return unless File.exist?(path)
85
+
86
+ Vectory::Svg.from_path(path)
87
+ end
88
+
89
+ def from_targets_to_links_map(svgmap)
90
+ targets = svgmap.xpath(@namespace.ns("./target"))
91
+ targets.each_with_object({}) do |target_tag, m|
92
+ target = link_target(target_tag)
93
+ next unless target
94
+
95
+ href = File.expand_path(target_tag["href"])
96
+ m[href] = target
97
+
98
+ target_tag.remove
99
+ end
100
+ end
101
+
102
+ def link_target(target_tag)
103
+ xref = target_tag.at(@namespace.ns("./xref"))
104
+ return "##{xref['target']}" if xref
105
+
106
+ link = target_tag.at(@namespace.ns("./link"))
107
+ return unless link
108
+
109
+ link["target"]
110
+ end
111
+
112
+ def simplify_svgmap(svgmap)
113
+ return if svgmap.at(@namespace.ns("./target/eref"))
114
+
115
+ svgmap.replace(svgmap.at(@namespace.ns("./figure")))
116
+ end
117
+ end
118
+ end
@@ -2,10 +2,13 @@ require "open3"
2
2
 
3
3
  module Vectory
4
4
  class SystemCall
5
+ TIMEOUT = 60
6
+
5
7
  attr_reader :status, :stdout, :stderr, :cmd
6
8
 
7
- def initialize(cmd)
9
+ def initialize(cmd, timeout = TIMEOUT)
8
10
  @cmd = cmd
11
+ @timeout = timeout
9
12
  end
10
13
 
11
14
  def call
@@ -27,7 +30,12 @@ module Vectory
27
30
  end
28
31
 
29
32
  def execute(cmd)
30
- @stdout, @stderr, @status = Open3.capture3(cmd)
33
+ result = Utils.capture3_with_timeout(cmd,
34
+ timeout: @timeout,
35
+ kill_after: @timeout)
36
+ @stdout = result[:stdout]
37
+ @stderr = result[:stderr]
38
+ @status = result[:status]
31
39
  rescue Errno::ENOENT => e
32
40
  raise SystemCallError, e.inspect
33
41
  end