vectory 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/README.adoc +150 -0
  4. data/lib/vectory/image.rb +4 -0
  5. data/lib/vectory/inkscape_converter.rb +35 -8
  6. data/lib/vectory/svg.rb +84 -1
  7. data/lib/vectory/svg_mapping.rb +118 -0
  8. data/lib/vectory/system_call.rb +11 -2
  9. data/lib/vectory/utils.rb +111 -0
  10. data/lib/vectory/vector.rb +3 -2
  11. data/lib/vectory/version.rb +1 -1
  12. data/lib/vectory.rb +1 -0
  13. data/spec/examples/svg/action_schemaexpg1.svg +124 -0
  14. data/spec/examples/svg/action_schemaexpg2.svg +124 -0
  15. data/spec/examples/svg/doc-ref.xml +109 -0
  16. data/spec/examples/svg/doc.xml +51 -0
  17. data/spec/examples/svg/doc2-ref.xml +59 -0
  18. data/spec/examples/svg/doc2.xml +28 -0
  19. data/spec/support/vectory_helper.rb +14 -0
  20. data/spec/vectory/cli_spec.rb +12 -12
  21. data/spec/vectory/emf_spec.rb +3 -3
  22. data/spec/vectory/eps_spec.rb +3 -3
  23. data/spec/vectory/file_magic_spec.rb +3 -3
  24. data/spec/vectory/inkscape_converter_spec.rb +11 -6
  25. data/spec/vectory/ps_spec.rb +3 -3
  26. data/spec/vectory/svg_mapping_spec.rb +42 -0
  27. data/spec/vectory/svg_spec.rb +11 -3
  28. data/vectory.gemspec +0 -4
  29. metadata +22 -70
  30. /data/spec/examples/emf2eps/{img.eps → ref.eps} +0 -0
  31. /data/spec/examples/emf2ps/{img.ps → ref.ps} +0 -0
  32. /data/spec/examples/emf2svg/{img.svg → ref.svg} +0 -0
  33. /data/spec/examples/eps2emf/{img.emf → ref.emf} +0 -0
  34. /data/spec/examples/eps2ps/{img.ps → ref.ps} +0 -0
  35. /data/spec/examples/eps2svg/{img.svg → ref.svg} +0 -0
  36. /data/spec/examples/ps2emf/{img.emf → ref.emf} +0 -0
  37. /data/spec/examples/ps2eps/{img.eps → ref.eps} +0 -0
  38. /data/spec/examples/ps2svg/{img.svg → ref.svg} +0 -0
  39. /data/spec/examples/svg2emf/{img.emf → ref.emf} +0 -0
  40. /data/spec/examples/svg2eps/{img.eps → ref.eps} +0 -0
  41. /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: 865823bf5ac58c0780cdffc76abf7d396c2b481bcf292c807e419c0983f44646
4
+ data.tar.gz: cd29b9a6f1c3a851e37fa207427474a60656ad8a1ba6f27d954d08d2ded47f5d
5
5
  SHA512:
6
- metadata.gz: 8775fc8bda0a86df3bc2c4bde5ab303d861a6d5812764f8b2c40a540a0f0369a42b55cd1bac655acc3ccf37348aa976620709ae41c35d26680ae221579e416e9
7
- data.tar.gz: c880d981f9c7a26182bacf9a0ee0d0df0f5147d493f79de45bade50e813c25d258400157be70b8987f605b3d4a9b4c3217b7974d40da174503de6e074183e573
6
+ metadata.gz: 01ee6626bca7c7a61f14dffa639355171e052637f9b1c58d4dfb39721a7b7a815e6b11cd5c1deb76b4faecba37abf873e45661c16ebd2eaf7434cb9031a6deff
7
+ data.tar.gz: 6706861380815c36d273445cc970db3b01693024208596c8072e9661ab301908db5c6c81e7d707826b501263aac8dbcc4b65f553fc62857a773cb45404d53de4
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
@@ -202,6 +330,28 @@ vector.initial_path # => "storage/images/img.eps"
202
330
  ----
203
331
 
204
332
 
333
+ == Development
334
+
335
+ === Releasing
336
+
337
+ Releasing is done automatically with GitHub Actions. Just bump and tag with
338
+ `gem-release`.
339
+
340
+ For a patch release (0.0.x) use:
341
+
342
+ [source,sh]
343
+ ----
344
+ gem bump --version patch --tag --push
345
+ ----
346
+
347
+ For a minor release (0.x.0) use:
348
+
349
+ [source,sh]
350
+ ----
351
+ gem bump --version minor --tag --push
352
+ ----
353
+
354
+
205
355
  == Contributing
206
356
 
207
357
  Bug reports and pull requests are welcome on GitHub at:
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
@@ -19,8 +19,8 @@ module Vectory
19
19
 
20
20
  call = SystemCall.new(cmd).call
21
21
 
22
- output_path = "#{uri}.#{output_extension}"
23
- raise_conversion_error(call) unless File.exist?(output_path)
22
+ output_path = find_output(uri, output_extension)
23
+ raise_conversion_error(call) unless output_path
24
24
 
25
25
  # and return Vectory::Utils::datauri(file)
26
26
 
@@ -36,19 +36,46 @@ module Vectory
36
36
  end
37
37
 
38
38
  def inkscape_path
39
- cmd = "inkscape"
40
- exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
39
+ @inkscape_path ||= find_inkscape
40
+ end
41
+
42
+ def find_inkscape
43
+ cmds.each do |cmd|
44
+ extensions.each do |ext|
45
+ paths.each do |path|
46
+ exe = File.join(path, "#{cmd}#{ext}")
41
47
 
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)
48
+ return exe if File.executable?(exe) && !File.directory?(exe)
49
+ end
46
50
  end
47
51
  end
48
52
 
49
53
  nil
50
54
  end
51
55
 
56
+ def cmds
57
+ ["inkscapecom", "inkscape"]
58
+ end
59
+
60
+ def extensions
61
+ ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
62
+ end
63
+
64
+ def paths
65
+ ENV["PATH"].split(File::PATH_SEPARATOR)
66
+ end
67
+
68
+ def find_output(source_path, output_extension)
69
+ basenames = [File.basename(source_path, ".*"),
70
+ File.basename(source_path)]
71
+
72
+ paths = basenames.map do |basename|
73
+ "#{File.join(File.dirname(source_path), basename)}.#{output_extension}"
74
+ end
75
+
76
+ paths.find { |p| File.exist?(p) }
77
+ end
78
+
52
79
  def raise_conversion_error(call)
53
80
  raise Vectory::ConversionError,
54
81
  "Could not convert with Inkscape. " \
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,13 @@ 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
+ Vectory.ui.error(result.inspect)
37
+ @stdout = result[:stdout]
38
+ @stderr = result[:stderr]
39
+ @status = result[:status]
31
40
  rescue Errno::ENOENT => e
32
41
  raise SystemCallError, e.inspect
33
42
  end
data/lib/vectory/utils.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "marcel"
4
+ require "timeout"
5
+
3
6
  module Vectory
4
7
  class Utils
5
8
  # Extracted from https://github.com/metanorma/metanorma-utils/blob/v1.5.2/lib/utils/image.rb
@@ -49,6 +52,114 @@ module Vectory
49
52
  def absolute_path?(uri)
50
53
  %r{^/}.match?(uri) || %r{^[A-Z]:/}.match?(uri)
51
54
  end
55
+
56
+ # rubocop:disable all
57
+ #
58
+ # Originally from https://gist.github.com/pasela/9392115
59
+ #
60
+ # Capture the standard output and the standard error of a command.
61
+ # Almost same as Open3.capture3 method except for timeout handling and return value.
62
+ # See Open3.capture3.
63
+ #
64
+ # result = capture3_with_timeout([env,] cmd... [, opts])
65
+ #
66
+ # The arguments env, cmd and opts are passed to Process.spawn except
67
+ # opts[:stdin_data], opts[:binmode], opts[:timeout], opts[:signal]
68
+ # and opts[:kill_after]. See Process.spawn.
69
+ #
70
+ # If opts[:stdin_data] is specified, it is sent to the command's standard input.
71
+ #
72
+ # If opts[:binmode] is true, internal pipes are set to binary mode.
73
+ #
74
+ # If opts[:timeout] is specified, SIGTERM is sent to the command after specified seconds.
75
+ #
76
+ # If opts[:signal] is specified, it is used instead of SIGTERM on timeout.
77
+ #
78
+ # If opts[:kill_after] is specified, also send a SIGKILL after specified seconds.
79
+ # it is only sent if the command is still running after the initial signal was sent.
80
+ #
81
+ # The return value is a Hash as shown below.
82
+ #
83
+ # {
84
+ # :pid => PID of the command,
85
+ # :status => Process::Status of the command,
86
+ # :stdout => the standard output of the command,
87
+ # :stderr => the standard error of the command,
88
+ # :timeout => whether the command was timed out,
89
+ # }
90
+ def capture3_with_timeout(*cmd)
91
+ spawn_opts = Hash === cmd.last ? cmd.pop.dup : {}
92
+ opts = {
93
+ :stdin_data => spawn_opts.delete(:stdin_data) || "",
94
+ :binmode => spawn_opts.delete(:binmode) || false,
95
+ :timeout => spawn_opts.delete(:timeout),
96
+ :signal => spawn_opts.delete(:signal) || :TERM,
97
+ :kill_after => spawn_opts.delete(:kill_after),
98
+ }
99
+
100
+ in_r, in_w = IO.pipe
101
+ out_r, out_w = IO.pipe
102
+ err_r, err_w = IO.pipe
103
+ in_w.sync = true
104
+
105
+ if opts[:binmode]
106
+ in_w.binmode
107
+ out_r.binmode
108
+ err_r.binmode
109
+ end
110
+
111
+ spawn_opts[:in] = in_r
112
+ spawn_opts[:out] = out_w
113
+ spawn_opts[:err] = err_w
114
+
115
+ result = {
116
+ :pid => nil,
117
+ :status => nil,
118
+ :stdout => nil,
119
+ :stderr => nil,
120
+ :timeout => false,
121
+ }
122
+
123
+ out_reader = nil
124
+ err_reader = nil
125
+ wait_thr = nil
126
+
127
+ begin
128
+ Timeout.timeout(opts[:timeout]) do
129
+ result[:pid] = spawn(*cmd, spawn_opts)
130
+ wait_thr = Process.detach(result[:pid])
131
+ in_r.close
132
+ out_w.close
133
+ err_w.close
134
+
135
+ out_reader = Thread.new { out_r.read }
136
+ err_reader = Thread.new { err_r.read }
137
+
138
+ in_w.write opts[:stdin_data]
139
+ in_w.close
140
+
141
+ result[:status] = wait_thr.value
142
+ end
143
+ rescue Timeout::Error
144
+ result[:timeout] = true
145
+ pid = spawn_opts[:pgroup] ? -result[:pid] : result[:pid]
146
+ Process.kill(opts[:signal], pid)
147
+ if opts[:kill_after]
148
+ unless wait_thr.join(opts[:kill_after])
149
+ Process.kill(:KILL, pid)
150
+ end
151
+ end
152
+ ensure
153
+ result[:status] = wait_thr.value if wait_thr
154
+ result[:stdout] = out_reader.value if out_reader
155
+ result[:stderr] = err_reader.value if err_reader
156
+ out_r.close unless out_r.closed?
157
+ err_r.close unless err_r.closed?
158
+ end
159
+
160
+ result
161
+ end
162
+ # rubocop:enable all
52
163
  end
53
164
  end
54
165
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tempfile"
3
4
  require_relative "inkscape_converter"
4
5
 
5
6
  module Vectory
@@ -44,7 +45,7 @@ module Vectory
44
45
 
45
46
  def write(path = nil)
46
47
  target_path = path || @path || tmp_path
47
- File.binwrite(target_path, @content)
48
+ File.binwrite(target_path, content)
48
49
  @path = File.expand_path(target_path)
49
50
 
50
51
  self
@@ -59,7 +60,7 @@ module Vectory
59
60
  def with_file(input_extension)
60
61
  Dir.mktmpdir do |dir|
61
62
  input_path = File.join(dir, "image.#{input_extension}")
62
- File.binwrite(input_path, @content)
63
+ File.binwrite(input_path, content)
63
64
 
64
65
  yield input_path
65
66
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vectory
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/vectory.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "vectory/eps"
10
10
  require_relative "vectory/ps"
11
11
  require_relative "vectory/emf"
12
12
  require_relative "vectory/svg"
13
+ require_relative "vectory/svg_mapping"
13
14
 
14
15
  module Vectory
15
16
  class Error < StandardError; end