vectory 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +1 -1
  3. data/.github/workflows/release.yml +24 -0
  4. data/.gitignore +4 -0
  5. data/Gemfile +3 -1
  6. data/README.adoc +307 -1
  7. data/Rakefile +4 -0
  8. data/bin/vectory +9 -0
  9. data/emf.emf +1 -0
  10. data/lib/vectory/cli.rb +140 -0
  11. data/lib/vectory/datauri.rb +48 -0
  12. data/lib/vectory/emf.rb +31 -0
  13. data/lib/vectory/eps.rb +31 -0
  14. data/lib/vectory/file_magic.rb +53 -0
  15. data/lib/vectory/image.rb +27 -0
  16. data/lib/vectory/inkscape_converter.rb +99 -0
  17. data/lib/vectory/ps.rb +25 -0
  18. data/lib/vectory/svg.rb +110 -0
  19. data/lib/vectory/svg_mapping.rb +118 -0
  20. data/lib/vectory/system_call.rb +58 -0
  21. data/lib/vectory/utils.rb +165 -0
  22. data/lib/vectory/vector.rb +75 -0
  23. data/lib/vectory/version.rb +1 -1
  24. data/lib/vectory.rb +35 -1
  25. data/spec/examples/emf2eps/img.emf +0 -0
  26. data/spec/examples/emf2eps/img.emf.datauri +1 -0
  27. data/spec/examples/emf2eps/ref.eps +88 -0
  28. data/spec/examples/emf2ps/img.emf +0 -0
  29. data/spec/examples/emf2ps/ref.ps +125 -0
  30. data/spec/examples/emf2svg/img.emf +0 -0
  31. data/spec/examples/emf2svg/ref.svg +9 -0
  32. data/spec/examples/eps2emf/img.eps +199 -0
  33. data/spec/examples/eps2emf/img.eps.datauri +1 -0
  34. data/spec/examples/eps2emf/ref.emf +0 -0
  35. data/spec/examples/eps2ps/img.eps +199 -0
  36. data/spec/examples/eps2ps/ref.ps +549 -0
  37. data/spec/examples/eps2svg/img.eps +199 -0
  38. data/spec/examples/eps2svg/ref.svg +173 -0
  39. data/spec/examples/eps_but_svg_extension.svg +199 -0
  40. data/spec/examples/img.jpg +0 -0
  41. data/spec/examples/ps2emf/img.ps +549 -0
  42. data/spec/examples/ps2emf/img.ps.datauri +1 -0
  43. data/spec/examples/ps2emf/ref.emf +0 -0
  44. data/spec/examples/ps2eps/img.ps +549 -0
  45. data/spec/examples/ps2eps/ref.eps +844 -0
  46. data/spec/examples/ps2svg/img.ps +549 -0
  47. data/spec/examples/ps2svg/ref.svg +476 -0
  48. data/spec/examples/svg/action_schemaexpg1.svg +124 -0
  49. data/spec/examples/svg/action_schemaexpg2.svg +124 -0
  50. data/spec/examples/svg/doc-ref.xml +109 -0
  51. data/spec/examples/svg/doc.xml +51 -0
  52. data/spec/examples/svg/doc2-ref.xml +59 -0
  53. data/spec/examples/svg/doc2.xml +28 -0
  54. data/spec/examples/svg2emf/img.svg +1 -0
  55. data/spec/examples/svg2emf/img.svg.datauri +1 -0
  56. data/spec/examples/svg2emf/ref.emf +0 -0
  57. data/spec/examples/svg2eps/img.svg +1 -0
  58. data/spec/examples/svg2eps/ref.eps +88 -0
  59. data/spec/examples/svg2ps/img.svg +1 -0
  60. data/spec/examples/svg2ps/ref.ps +125 -0
  61. data/spec/spec_helper.rb +7 -0
  62. data/spec/support/matchers.rb +39 -0
  63. data/spec/support/text_matcher.rb +63 -0
  64. data/spec/support/vectory_helper.rb +31 -0
  65. data/spec/vectory/cli_spec.rb +214 -0
  66. data/spec/vectory/datauri_spec.rb +101 -0
  67. data/spec/vectory/emf_spec.rb +38 -0
  68. data/spec/vectory/eps_spec.rb +40 -0
  69. data/spec/vectory/file_magic_spec.rb +24 -0
  70. data/spec/vectory/inkscape_converter_spec.rb +43 -0
  71. data/spec/vectory/ps_spec.rb +33 -0
  72. data/spec/vectory/svg_mapping_spec.rb +42 -0
  73. data/spec/vectory/svg_spec.rb +41 -0
  74. data/spec/vectory/vector_spec.rb +60 -0
  75. data/tmp/.keep +0 -0
  76. data/vectory.gemspec +6 -3
  77. metadata +116 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd1ad8c12e153372b1c44c17aa68f499734f0e88b2023a8a48a6ad1572dbac75
4
- data.tar.gz: 65f684b929a644409e2ac52a825e01dd2b56b1f74bc4b9db7e6dc41eb8107dfa
3
+ metadata.gz: 865823bf5ac58c0780cdffc76abf7d396c2b481bcf292c807e419c0983f44646
4
+ data.tar.gz: cd29b9a6f1c3a851e37fa207427474a60656ad8a1ba6f27d954d08d2ded47f5d
5
5
  SHA512:
6
- metadata.gz: 15b53a73c27c63f3205f536a8a75d39a898ff3fffa0704096636003198ebda42a4ec016b62615d4ae949bbcc766721fe1da99e04c826c8b1d21b299fe7a0cfb6
7
- data.tar.gz: 9db936c9a9e3a17faec138d5dd05eaa70ca016fa399135f5aad68f3138f5fbfe131fb01a9c29ce2cd8c80a8218726f81f9f8b149a4d1b973b88f55e8269f6478
6
+ metadata.gz: 01ee6626bca7c7a61f14dffa639355171e052637f9b1c58d4dfb39721a7b7a815e6b11cd5c1deb76b4faecba37abf873e45661c16ebd2eaf7434cb9031a6deff
7
+ data.tar.gz: 6706861380815c36d273445cc970db3b01693024208596c8072e9661ab301908db5c6c81e7d707826b501263aac8dbcc4b65f553fc62857a773cb45404d53de4
@@ -10,6 +10,6 @@ on:
10
10
 
11
11
  jobs:
12
12
  rake:
13
- uses: metanorma/ci/.github/workflows/generic-rake.yml@main
13
+ uses: metanorma/ci/.github/workflows/inkscape-rake.yml@main
14
14
  secrets:
15
15
  pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
@@ -0,0 +1,24 @@
1
+ # Auto-generated by Cimas: Do not edit it manually!
2
+ # See https://github.com/metanorma/cimas
3
+ name: release
4
+
5
+ on:
6
+ workflow_dispatch:
7
+ inputs:
8
+ next_version:
9
+ description: |
10
+ Next release version. Possible values: x.y.z, major, minor, patch or pre|rc|etc
11
+ required: true
12
+ default: 'skip'
13
+ push:
14
+ tags: [ v* ]
15
+
16
+ jobs:
17
+ release:
18
+ uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
19
+ with:
20
+ next_version: ${{ github.event.inputs.next_version }}
21
+ secrets:
22
+ rubygems-api-key: ${{ secrets.METANORMA_CI_RUBYGEMS_API_KEY }}
23
+ pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
24
+
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /.bundle/
2
+ Gemfile.lock
3
+ .rspec_status
4
+ .rubocop-*
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
@@ -11,6 +11,11 @@ image:https://img.shields.io/github/commits-since/metanorma/vectory/latest.svg["
11
11
  Vectory is a Ruby gem that performs pairwise vector image conversions for common
12
12
  vector image formats (EPS, PS, EMF, SVG).
13
13
 
14
+ [quote]
15
+ ____
16
+ Vectory shall give you a glorious vectory over EPS files.
17
+ ____
18
+
14
19
 
15
20
  == Installation
16
21
 
@@ -20,6 +25,10 @@ Vectory relies on the following software to be installed:
20
25
 
21
26
  * https://github.com/metanorma/emf2svg-ruby[emf2svg-ruby]
22
27
  * https://inkscape.org[Inkscape]
28
+ * https://www.ghostscript.com/[Ghostscript]
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.
23
32
 
24
33
 
25
34
  === Gem install
@@ -43,7 +52,304 @@ Where,
43
52
 
44
53
  `format`:: the desired output format (one of: `svg`, `eps`, `ps`, `emf`)
45
54
  `input-file-name`:: file path to the input file
46
- `output-file-name`:: file path to the desired output file (with the file extension)
55
+ `output-file-name`:: file path to the desired output file (with the
56
+ file extension) (default: writes to a current directory by the input filename
57
+ with an extension changed to a desired format)
58
+
59
+
60
+ === Using the Ruby library
61
+
62
+ Some examples:
63
+
64
+ Take EMF as a path to a file and return SVG as a string:
65
+
66
+ [source,ruby]
67
+ ----
68
+ path = "path/to/file.emf"
69
+
70
+ Vectory::Emf.from_path(path).to_svg.content
71
+ ----
72
+
73
+ Take EPS as a string and return EMF as a path to a file:
74
+
75
+ [source,ruby]
76
+ ----
77
+ # NOTE: content is shortened for readability
78
+ content = "%!PS-Adobe-3.0 EPSF-3.0\n ... %%Trailer"
79
+
80
+ Vectory::Eps.from_content(content).to_emf.write.path
81
+ ----
82
+
83
+ Take SVG as a datauri and return EMF as a datauri:
84
+
85
+ [source,ruby]
86
+ ----
87
+ # NOTE: datauri is shortened for readability
88
+ uri = "data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0 ... GRkYiLz48L3N2Zz4="
89
+
90
+ Vectory::Datauri.new(uri).to_vector.to_emf.to_uri.content
91
+ ----
92
+
93
+
94
+ ==== What is supported?
95
+
96
+ There are several vector classes which support conversion between each other:
97
+
98
+ [source,ruby]
99
+ ----
100
+ Vectory::Eps
101
+ Vectory::Ps
102
+ Vectory::Emf
103
+ Vectory::Svg
104
+ ----
105
+
106
+ Each of them can be instantiated in several ways:
107
+
108
+ [source,ruby]
109
+ ----
110
+ Vectory::Eps.from_path("images/img.eps")
111
+ Vectory::Eps.from_content("%!PS-Adobe-3.0...")
112
+ ----
113
+
114
+ Converting to other formats:
115
+
116
+ [source,ruby]
117
+ ----
118
+ Vectory::Eps.from_content(content).to_ps
119
+ Vectory::Eps.from_content(content).to_emf
120
+ Vectory::Eps.from_content(content).to_svg
121
+ ----
122
+
123
+ Several ways of getting content of an object:
124
+
125
+ [source,ruby]
126
+ ----
127
+ Vectory::Eps.from_content(content).to_svg.content
128
+ Vectory::Eps.from_content(content).to_svg.to_uri.content # as datauri
129
+ Vectory::Eps.from_content(content).to_svg.write.path
130
+ ----
131
+
132
+
133
+ ==== Datauri
134
+
135
+ Also there is the `Vectory::Datauri` class which represents vectory images in
136
+ the datauri format.
137
+
138
+ Convert an SVG datauri to a plain SVG:
139
+
140
+ [source,ruby]
141
+ ----
142
+ # NOTE: datauri is shortened for readability
143
+ uri = "data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0 ... GRkYiLz48L3N2Zz4="
144
+ Vectory::Datauri.new(uri).to_vector.content
145
+ ----
146
+
147
+ Convert an EPS file to its datauri representation:
148
+
149
+ [source,ruby]
150
+ ----
151
+ eps = Vectory::Eps.from_path("img.eps")
152
+ Vectory::Datauri.from_vector(eps).content
153
+ ----
154
+
155
+ There is also a simplified API for this case:
156
+
157
+ [source,ruby]
158
+ ----
159
+ Vectory::Eps.from_path("img.eps").to_uri.content
160
+ ----
161
+
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
+
288
+ ==== File system operations
289
+
290
+ An image object contains information where it is written. It can be obtained
291
+ with the `#path` API:
292
+
293
+ [source,ruby]
294
+ ----
295
+ vector = Vectory::Eps.from_path("img.eps")
296
+ vector.path
297
+ ----
298
+
299
+ Before the first write it raises the `NotWrittenToDiskError` error:
300
+
301
+ [source,ruby]
302
+ ----
303
+ vector.path # => raise NotWrittenToDiskError
304
+ ----
305
+
306
+ After writing it returns a path of the image on a disk:
307
+
308
+ [source,ruby]
309
+ ----
310
+ vector.write
311
+ vector.path # => "/tmp/xxx/yyy"
312
+ ----
313
+
314
+ By default it writes to a temporary directory but it can be changed by
315
+ providing an argument with a desired path:
316
+
317
+ [source,ruby]
318
+ ----
319
+ vector.write("images/img.eps")
320
+ vector.path # => "images/img.eps"
321
+ ----
322
+
323
+ Since an image can be initially read from a disk, it also keeps an initial
324
+ path. To avoid accidental overwrite, this path is used only for read-only
325
+ purposes.
326
+
327
+ [source,ruby]
328
+ ----
329
+ vector.initial_path # => "storage/images/img.eps"
330
+ ----
331
+
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
+ ----
47
353
 
48
354
 
49
355
  == Contributing
data/Rakefile CHANGED
@@ -5,6 +5,10 @@ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
+ task :inkscape_version do
9
+ system('inkscape --version')
10
+ end
11
+
8
12
  task default: :spec
9
13
 
10
14
  # require "rubocop/rake_task"
data/bin/vectory ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "vectory/cli"
5
+
6
+ Vectory.ui.level = Logger::INFO
7
+
8
+ status_code = Vectory::CLI.start(ARGV)
9
+ exit status_code.is_a?(Integer) ? status_code : Vectory::CLI::STATUS_UNKNOWN_ERROR
data/emf.emf ADDED
@@ -0,0 +1 @@
1
+ AQAAAMgAAAAAAAAAAAAAAPsEAAD7BAAAAAAAAAAAAACLCgAAiwoAACBFTUYAAAEAJAQAACgAAAACAAAALgAAAGwAAAAAAAAA3ScAAH0zAADYAAAAFwEAAAAAAAAAAAAAAAAAAMBLAwDYQQQASQBuAGsAcwBjAGEAcABlACAAMQAuADAAIAAoADQAMAAzADUAYQA0AGYALAAgADIAMAAyADAALQAwADUALQAwADEAKQAgAAAAbwBkAGYALgBlAG0AZgAAAAAAAAARAAAADAAAAAEAAAAkAAAAJAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAIAAABGAAAALAAAACAAAABTY3JlZW49MTAyMDV4MTMxODFweCwgMjE2eDI3OW1tAEYAAAAwAAAAIwAAAERyYXdpbmc9MTAwLjB4MTAwLjBweCwgMjYuNXgyNi41bW0AABIAAAAMAAAAAQAAABMAAAAMAAAAAgAAABYAAAAMAAAAGAAAABgAAAAMAAAAAAAAABQAAAAMAAAADQAAACcAAAAYAAAAAQAAAAAAAAAAAJkABgAAACUAAAAMAAAAAQAAADsAAAAIAAAAGwAAABAAAACkBAAAcQIAAAUAAAA0AAAAAAAAAAAAAAD//////////wMAAACkBAAAqAMAAKgDAACkBAAAcQIAAKQEAAAFAAAANAAAAAAAAAAAAAAA//////////8DAAAAOgEAAKQEAAA/AAAAqAMAAD8AAABxAgAABQAAADQAAAAAAAAAAAAAAP//////////AwAAAD8AAAA6AQAAOgEAAD8AAABxAgAAPwAAAAUAAAA0AAAAAAAAAAAAAAD//////////wMAAACoAwAAPwAAAKQEAAA6AQAApAQAAHECAAA9AAAACAAAADwAAAAIAAAAPgAAABgAAAAAAAAAAAAAAP//////////JQAAAAwAAAAFAACAKAAAAAwAAAABAAAAJwAAABgAAAABAAAAAAAAAP///wAGAAAAJQAAAAwAAAABAAAAOwAAAAgAAAAbAAAAEAAAAJ0BAABFAQAANgAAABAAAADPAwAARQEAAAUAAAA0AAAAAAAAAAAAAAD//////////wMAAABfBAAA7QEAAGQEAADjAgAA2wMAAJEDAAAFAAAANAAAAAAAAAAAAAAA//////////8DAAAAUgMAAD4EAABhAgAAcwQAAJ0BAAAOBAAANgAAABAAAACdAQAAyQIAADYAAAAQAAAA4gIAAMkCAAA2AAAAEAAAAOICAAAaAgAANgAAABAAAACdAQAAGgIAAD0AAAAIAAAAPAAAAAgAAAA+AAAAGAAAAAAAAAAAAAAA//////////8lAAAADAAAAAUAAIAoAAAADAAAAAEAAAAOAAAAFAAAAAAAAAAAAAAAJAQAAA==
@@ -0,0 +1,140 @@
1
+ require "thor"
2
+ require_relative "../vectory"
3
+ require_relative "file_magic"
4
+
5
+ module Vectory
6
+ class CLI < Thor
7
+ STATUS_SUCCESS = 0
8
+ STATUS_UNKNOWN_ERROR = 1
9
+ STATUS_UNSUPPORTED_INPUT_FORMAT_ERROR = 2
10
+ STATUS_UNSUPPORTED_OUTPUT_FORMAT_ERROR = 3
11
+ STATUS_CONVERSION_ERROR = 4
12
+ STATUS_SYSTEM_CALL_ERROR = 5
13
+ STATUS_INKSCAPE_NOT_FOUND_ERROR = 6
14
+
15
+ MAP_ERROR_TO_STATUS = {
16
+ Vectory::ConversionError => STATUS_CONVERSION_ERROR,
17
+ Vectory::InkscapeNotFoundError => STATUS_INKSCAPE_NOT_FOUND_ERROR,
18
+ Vectory::SystemCallError => STATUS_SYSTEM_CALL_ERROR,
19
+ }.freeze
20
+
21
+ module SupportedInputFormats
22
+ EPS = :eps
23
+ PS = :ps
24
+ SVG = :svg
25
+ EMF = :emf
26
+
27
+ def self.all
28
+ constants.map { |x| const_get(x) }
29
+ end
30
+ end
31
+
32
+ module SupportedOutputFormats
33
+ EPS = "eps".freeze
34
+ PS = "ps".freeze
35
+ SVG = "svg".freeze
36
+ EMF = "emf".freeze
37
+
38
+ def self.all
39
+ constants.map { |x| const_get(x) }
40
+ end
41
+ end
42
+
43
+ def self.exit_on_failure?
44
+ false
45
+ end
46
+
47
+ desc "convert INPUT_FILE_NAME",
48
+ "Perform pairwise vector image conversions for common vector image " \
49
+ "formats (EPS, PS, EMF, SVG)"
50
+ option :format,
51
+ aliases: :f,
52
+ required: true,
53
+ desc: "the desired output format (one of: svg, eps, ps, emf)"
54
+
55
+ option :output,
56
+ aliases: :o,
57
+ desc: "file path to the desired output file (with the file " \
58
+ "extension)"
59
+ def convert(file)
60
+ unless supported_format?(options[:format])
61
+ return unsupported_format_error(options[:format])
62
+ end
63
+
64
+ input_format = detect_input_format(file)
65
+ unless supported_input_format?(input_format)
66
+ return unsupported_input_format_error
67
+ end
68
+
69
+ object = source_object(file, input_format)
70
+
71
+ convert_to_format(object, options)
72
+ end
73
+ default_task :convert
74
+
75
+ private
76
+
77
+ def supported_format?(format)
78
+ SupportedOutputFormats.all.include?(format)
79
+ end
80
+
81
+ def unsupported_format_error(format)
82
+ formats = SupportedOutputFormats.all.map { |v| "'#{v}'" }.join(", ")
83
+
84
+ Vectory.ui.error(
85
+ "Unsupported output format '#{format}'. " \
86
+ "Please choose one of: #{formats}.",
87
+ )
88
+
89
+ STATUS_UNSUPPORTED_OUTPUT_FORMAT_ERROR
90
+ end
91
+
92
+ def detect_input_format(file)
93
+ FileMagic.detect(file)
94
+ end
95
+
96
+ def supported_input_format?(format)
97
+ SupportedInputFormats.all.include?(format)
98
+ end
99
+
100
+ def unsupported_input_format_error
101
+ formats = SupportedInputFormats.all.map { |v| "'#{v}'" }.join(", ")
102
+ Vectory.ui.error(
103
+ "Could not detect input format. " \
104
+ "Please provide file of the following formats: #{formats}.",
105
+ )
106
+
107
+ STATUS_UNSUPPORTED_INPUT_FORMAT_ERROR
108
+ end
109
+
110
+ def source_object(file, format)
111
+ Vectory.const_get(format.capitalize).from_path(file)
112
+ end
113
+
114
+ def convert_to_format(object, options)
115
+ path = options[:output] || current_dir_path(object.initial_path,
116
+ options[:format])
117
+ to_format(object, options[:format]).write(path)
118
+ Vectory.ui.info("Output file was written to #{path}")
119
+
120
+ STATUS_SUCCESS
121
+ rescue Vectory::Error => e
122
+ handle_vectory_error(e)
123
+ end
124
+
125
+ def handle_vectory_error(exception)
126
+ Vectory.ui.error(exception.message)
127
+
128
+ MAP_ERROR_TO_STATUS[exception.class] || raise(exception)
129
+ end
130
+
131
+ def current_dir_path(input_path, output_format)
132
+ File.join(Dir.pwd, "#{File.basename(input_path, '.*')}.#{output_format}")
133
+ end
134
+
135
+ def to_format(object, format)
136
+ method_name = "to_#{format}"
137
+ object.public_send(method_name)
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,48 @@
1
+ require "base64"
2
+
3
+ module Vectory
4
+ class Datauri < Image
5
+ REGEX = %r{^
6
+ data:
7
+ (?<mimetype>[^;]+);
8
+ (?:charset=[^;]+;)?
9
+ base64,
10
+ (?<data>.+)
11
+ $}x.freeze
12
+
13
+ def self.from_vector(vector)
14
+ mimetype = vector.class.mimetype
15
+ content = vector.content.gsub("\r\n", "\n")
16
+ data = Base64.strict_encode64(content)
17
+
18
+ new("data:#{mimetype};base64,#{data}")
19
+ end
20
+
21
+ def to_vector
22
+ match = parse_datauri(@content)
23
+ content = Base64.strict_decode64(match[:data])
24
+ image_class = detect_image_class(match[:mimetype])
25
+
26
+ image_class.from_content(content)
27
+ end
28
+
29
+ private
30
+
31
+ def parse_datauri(uri)
32
+ match = REGEX.match(uri)
33
+ return match if match
34
+
35
+ raise ConversionError, "Could not parse datauri: '#{uri.slice(0, 30)}'."
36
+ end
37
+
38
+ def detect_image_class(image_type)
39
+ case image_type
40
+ when Eps.mimetype then return Eps
41
+ when Emf.mimetype then return Emf
42
+ when Svg.mimetype then return Svg
43
+ end
44
+
45
+ raise ConversionError, "Could not detect image type '#{image_type}'."
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "emf2svg"
4
+
5
+ module Vectory
6
+ class Emf < Vector
7
+ def self.default_extension
8
+ "emf"
9
+ end
10
+
11
+ def self.mimetype
12
+ "image/emf"
13
+ end
14
+
15
+ def to_svg
16
+ with_file("emf") do |input_path|
17
+ content = Emf2svg.from_file(input_path).sub(/<\?[^>]+>/, "")
18
+
19
+ Svg.from_content(content)
20
+ end
21
+ end
22
+
23
+ def to_eps
24
+ convert_with_inkscape("--export-type=eps", Eps)
25
+ end
26
+
27
+ def to_ps
28
+ convert_with_inkscape("--export-type=ps", Ps)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vectory
4
+ class Eps < Vector
5
+ def self.default_extension
6
+ "eps"
7
+ end
8
+
9
+ def self.mimetype
10
+ "application/postscript"
11
+ end
12
+
13
+ def to_ps
14
+ convert_with_inkscape("--export-type=ps", Ps)
15
+ end
16
+
17
+ def to_svg
18
+ convert_with_inkscape("--export-plain-svg --export-type=svg", Svg)
19
+ end
20
+
21
+ def to_emf
22
+ convert_with_inkscape("--export-type=emf", Emf)
23
+ end
24
+
25
+ private
26
+
27
+ def imgfile_suffix(uri, suffix)
28
+ "#{File.join(File.dirname(uri), File.basename(uri, '.*'))}.#{suffix}"
29
+ end
30
+ end
31
+ end