vectory 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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