vectory 0.1.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.
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