vectory 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/README.adoc +150 -0
- data/lib/vectory/image.rb +4 -0
- data/lib/vectory/inkscape_converter.rb +35 -8
- data/lib/vectory/svg.rb +84 -1
- data/lib/vectory/svg_mapping.rb +118 -0
- data/lib/vectory/system_call.rb +11 -2
- data/lib/vectory/utils.rb +111 -0
- data/lib/vectory/vector.rb +3 -2
- data/lib/vectory/version.rb +1 -1
- data/lib/vectory.rb +1 -0
- data/spec/examples/svg/action_schemaexpg1.svg +124 -0
- data/spec/examples/svg/action_schemaexpg2.svg +124 -0
- data/spec/examples/svg/doc-ref.xml +109 -0
- data/spec/examples/svg/doc.xml +51 -0
- data/spec/examples/svg/doc2-ref.xml +59 -0
- data/spec/examples/svg/doc2.xml +28 -0
- data/spec/support/vectory_helper.rb +14 -0
- data/spec/vectory/cli_spec.rb +12 -12
- data/spec/vectory/emf_spec.rb +3 -3
- data/spec/vectory/eps_spec.rb +3 -3
- data/spec/vectory/file_magic_spec.rb +3 -3
- data/spec/vectory/inkscape_converter_spec.rb +11 -6
- data/spec/vectory/ps_spec.rb +3 -3
- data/spec/vectory/svg_mapping_spec.rb +42 -0
- data/spec/vectory/svg_spec.rb +11 -3
- data/vectory.gemspec +0 -4
- metadata +22 -70
- /data/spec/examples/emf2eps/{img.eps → ref.eps} +0 -0
- /data/spec/examples/emf2ps/{img.ps → ref.ps} +0 -0
- /data/spec/examples/emf2svg/{img.svg → ref.svg} +0 -0
- /data/spec/examples/eps2emf/{img.emf → ref.emf} +0 -0
- /data/spec/examples/eps2ps/{img.ps → ref.ps} +0 -0
- /data/spec/examples/eps2svg/{img.svg → ref.svg} +0 -0
- /data/spec/examples/ps2emf/{img.emf → ref.emf} +0 -0
- /data/spec/examples/ps2eps/{img.eps → ref.eps} +0 -0
- /data/spec/examples/ps2svg/{img.svg → ref.svg} +0 -0
- /data/spec/examples/svg2emf/{img.emf → ref.emf} +0 -0
- /data/spec/examples/svg2eps/{img.eps → ref.eps} +0 -0
- /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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 865823bf5ac58c0780cdffc76abf7d396c2b481bcf292c807e419c0983f44646
|
4
|
+
data.tar.gz: cd29b9a6f1c3a851e37fa207427474a60656ad8a1ba6f27d954d08d2ded47f5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01ee6626bca7c7a61f14dffa639355171e052637f9b1c58d4dfb39721a7b7a815e6b11cd5c1deb76b4faecba37abf873e45661c16ebd2eaf7434cb9031a6deff
|
7
|
+
data.tar.gz: 6706861380815c36d273445cc970db3b01693024208596c8072e9661ab301908db5c6c81e7d707826b501263aac8dbcc4b65f553fc62857a773cb45404d53de4
|
data/Gemfile
CHANGED
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,8 +19,8 @@ module Vectory
|
|
19
19
|
|
20
20
|
call = SystemCall.new(cmd).call
|
21
21
|
|
22
|
-
output_path =
|
23
|
-
raise_conversion_error(call) unless
|
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
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
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
|
data/lib/vectory/system_call.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/vectory/vector.rb
CHANGED
@@ -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,
|
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,
|
63
|
+
File.binwrite(input_path, content)
|
63
64
|
|
64
65
|
yield input_path
|
65
66
|
end
|
data/lib/vectory/version.rb
CHANGED