vectory 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) 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/README.adoc +157 -1
  6. data/Rakefile +4 -0
  7. data/bin/vectory +9 -0
  8. data/emf.emf +1 -0
  9. data/lib/vectory/cli.rb +140 -0
  10. data/lib/vectory/datauri.rb +48 -0
  11. data/lib/vectory/emf.rb +31 -0
  12. data/lib/vectory/eps.rb +31 -0
  13. data/lib/vectory/file_magic.rb +53 -0
  14. data/lib/vectory/image.rb +23 -0
  15. data/lib/vectory/inkscape_converter.rb +72 -0
  16. data/lib/vectory/ps.rb +25 -0
  17. data/lib/vectory/svg.rb +27 -0
  18. data/lib/vectory/system_call.rb +49 -0
  19. data/lib/vectory/utils.rb +54 -0
  20. data/lib/vectory/vector.rb +74 -0
  21. data/lib/vectory/version.rb +1 -1
  22. data/lib/vectory.rb +34 -1
  23. data/spec/examples/emf2eps/img.emf +0 -0
  24. data/spec/examples/emf2eps/img.emf.datauri +1 -0
  25. data/spec/examples/emf2eps/img.eps +88 -0
  26. data/spec/examples/emf2ps/img.emf +0 -0
  27. data/spec/examples/emf2ps/img.ps +125 -0
  28. data/spec/examples/emf2svg/img.emf +0 -0
  29. data/spec/examples/emf2svg/img.svg +9 -0
  30. data/spec/examples/eps2emf/img.emf +0 -0
  31. data/spec/examples/eps2emf/img.eps +199 -0
  32. data/spec/examples/eps2emf/img.eps.datauri +1 -0
  33. data/spec/examples/eps2ps/img.eps +199 -0
  34. data/spec/examples/eps2ps/img.ps +549 -0
  35. data/spec/examples/eps2svg/img.eps +199 -0
  36. data/spec/examples/eps2svg/img.svg +173 -0
  37. data/spec/examples/eps_but_svg_extension.svg +199 -0
  38. data/spec/examples/img.jpg +0 -0
  39. data/spec/examples/ps2emf/img.emf +0 -0
  40. data/spec/examples/ps2emf/img.ps +549 -0
  41. data/spec/examples/ps2emf/img.ps.datauri +1 -0
  42. data/spec/examples/ps2eps/img.eps +844 -0
  43. data/spec/examples/ps2eps/img.ps +549 -0
  44. data/spec/examples/ps2svg/img.ps +549 -0
  45. data/spec/examples/ps2svg/img.svg +476 -0
  46. data/spec/examples/svg2emf/img.emf +0 -0
  47. data/spec/examples/svg2emf/img.svg +1 -0
  48. data/spec/examples/svg2emf/img.svg.datauri +1 -0
  49. data/spec/examples/svg2eps/img.eps +88 -0
  50. data/spec/examples/svg2eps/img.svg +1 -0
  51. data/spec/examples/svg2ps/img.ps +125 -0
  52. data/spec/examples/svg2ps/img.svg +1 -0
  53. data/spec/spec_helper.rb +7 -0
  54. data/spec/support/matchers.rb +39 -0
  55. data/spec/support/text_matcher.rb +63 -0
  56. data/spec/support/vectory_helper.rb +17 -0
  57. data/spec/vectory/cli_spec.rb +214 -0
  58. data/spec/vectory/datauri_spec.rb +101 -0
  59. data/spec/vectory/emf_spec.rb +38 -0
  60. data/spec/vectory/eps_spec.rb +40 -0
  61. data/spec/vectory/file_magic_spec.rb +24 -0
  62. data/spec/vectory/inkscape_converter_spec.rb +38 -0
  63. data/spec/vectory/ps_spec.rb +33 -0
  64. data/spec/vectory/svg_spec.rb +33 -0
  65. data/spec/vectory/vector_spec.rb +60 -0
  66. data/tmp/.keep +0 -0
  67. data/vectory.gemspec +10 -3
  68. metadata +163 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd1ad8c12e153372b1c44c17aa68f499734f0e88b2023a8a48a6ad1572dbac75
4
- data.tar.gz: 65f684b929a644409e2ac52a825e01dd2b56b1f74bc4b9db7e6dc41eb8107dfa
3
+ metadata.gz: c46e34eee9accc66e136b741440e46269744e0f9cf8a1125382fe3ec1959b320
4
+ data.tar.gz: 95a3dcf51104cc31ab37af1b0ecc39861c7717399056b0116cfd94562a66126a
5
5
  SHA512:
6
- metadata.gz: 15b53a73c27c63f3205f536a8a75d39a898ff3fffa0704096636003198ebda42a4ec016b62615d4ae949bbcc766721fe1da99e04c826c8b1d21b299fe7a0cfb6
7
- data.tar.gz: 9db936c9a9e3a17faec138d5dd05eaa70ca016fa399135f5aad68f3138f5fbfe131fb01a9c29ce2cd8c80a8218726f81f9f8b149a4d1b973b88f55e8269f6478
6
+ metadata.gz: 8775fc8bda0a86df3bc2c4bde5ab303d861a6d5812764f8b2c40a540a0f0369a42b55cd1bac655acc3ccf37348aa976620709ae41c35d26680ae221579e416e9
7
+ data.tar.gz: c880d981f9c7a26182bacf9a0ee0d0df0f5147d493f79de45bade50e813c25d258400157be70b8987f605b3d4a9b4c3217b7974d40da174503de6e074183e573
@@ -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/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,7 @@ 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]
23
29
 
24
30
 
25
31
  === Gem install
@@ -43,7 +49,157 @@ Where,
43
49
 
44
50
  `format`:: the desired output format (one of: `svg`, `eps`, `ps`, `emf`)
45
51
  `input-file-name`:: file path to the input file
46
- `output-file-name`:: file path to the desired output file (with the file extension)
52
+ `output-file-name`:: file path to the desired output file (with the
53
+ file extension) (default: writes to a current directory by the input filename
54
+ with an extension changed to a desired format)
55
+
56
+
57
+ === Using the Ruby library
58
+
59
+ Some examples:
60
+
61
+ Take EMF as a path to a file and return SVG as a string:
62
+
63
+ [source,ruby]
64
+ ----
65
+ path = "path/to/file.emf"
66
+
67
+ Vectory::Emf.from_path(path).to_svg.content
68
+ ----
69
+
70
+ Take EPS as a string and return EMF as a path to a file:
71
+
72
+ [source,ruby]
73
+ ----
74
+ # NOTE: content is shortened for readability
75
+ content = "%!PS-Adobe-3.0 EPSF-3.0\n ... %%Trailer"
76
+
77
+ Vectory::Eps.from_content(content).to_emf.write.path
78
+ ----
79
+
80
+ Take SVG as a datauri and return EMF as a datauri:
81
+
82
+ [source,ruby]
83
+ ----
84
+ # NOTE: datauri is shortened for readability
85
+ uri = "data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0 ... GRkYiLz48L3N2Zz4="
86
+
87
+ Vectory::Datauri.new(uri).to_vector.to_emf.to_uri.content
88
+ ----
89
+
90
+
91
+ ==== What is supported?
92
+
93
+ There are several vector classes which support conversion between each other:
94
+
95
+ [source,ruby]
96
+ ----
97
+ Vectory::Eps
98
+ Vectory::Ps
99
+ Vectory::Emf
100
+ Vectory::Svg
101
+ ----
102
+
103
+ Each of them can be instantiated in several ways:
104
+
105
+ [source,ruby]
106
+ ----
107
+ Vectory::Eps.from_path("images/img.eps")
108
+ Vectory::Eps.from_content("%!PS-Adobe-3.0...")
109
+ ----
110
+
111
+ Converting to other formats:
112
+
113
+ [source,ruby]
114
+ ----
115
+ Vectory::Eps.from_content(content).to_ps
116
+ Vectory::Eps.from_content(content).to_emf
117
+ Vectory::Eps.from_content(content).to_svg
118
+ ----
119
+
120
+ Several ways of getting content of an object:
121
+
122
+ [source,ruby]
123
+ ----
124
+ Vectory::Eps.from_content(content).to_svg.content
125
+ Vectory::Eps.from_content(content).to_svg.to_uri.content # as datauri
126
+ Vectory::Eps.from_content(content).to_svg.write.path
127
+ ----
128
+
129
+
130
+ ==== Datauri
131
+
132
+ Also there is the `Vectory::Datauri` class which represents vectory images in
133
+ the datauri format.
134
+
135
+ Convert an SVG datauri to a plain SVG:
136
+
137
+ [source,ruby]
138
+ ----
139
+ # NOTE: datauri is shortened for readability
140
+ uri = "data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0 ... GRkYiLz48L3N2Zz4="
141
+ Vectory::Datauri.new(uri).to_vector.content
142
+ ----
143
+
144
+ Convert an EPS file to its datauri representation:
145
+
146
+ [source,ruby]
147
+ ----
148
+ eps = Vectory::Eps.from_path("img.eps")
149
+ Vectory::Datauri.from_vector(eps).content
150
+ ----
151
+
152
+ There is also a simplified API for this case:
153
+
154
+ [source,ruby]
155
+ ----
156
+ Vectory::Eps.from_path("img.eps").to_uri.content
157
+ ----
158
+
159
+
160
+ ==== File system operations
161
+
162
+ An image object contains information where it is written. It can be obtained
163
+ with the `#path` API:
164
+
165
+ [source,ruby]
166
+ ----
167
+ vector = Vectory::Eps.from_path("img.eps")
168
+ vector.path
169
+ ----
170
+
171
+ Before the first write it raises the `NotWrittenToDiskError` error:
172
+
173
+ [source,ruby]
174
+ ----
175
+ vector.path # => raise NotWrittenToDiskError
176
+ ----
177
+
178
+ After writing it returns a path of the image on a disk:
179
+
180
+ [source,ruby]
181
+ ----
182
+ vector.write
183
+ vector.path # => "/tmp/xxx/yyy"
184
+ ----
185
+
186
+ By default it writes to a temporary directory but it can be changed by
187
+ providing an argument with a desired path:
188
+
189
+ [source,ruby]
190
+ ----
191
+ vector.write("images/img.eps")
192
+ vector.path # => "images/img.eps"
193
+ ----
194
+
195
+ Since an image can be initially read from a disk, it also keeps an initial
196
+ path. To avoid accidental overwrite, this path is used only for read-only
197
+ purposes.
198
+
199
+ [source,ruby]
200
+ ----
201
+ vector.initial_path # => "storage/images/img.eps"
202
+ ----
47
203
 
48
204
 
49
205
  == 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
@@ -0,0 +1,53 @@
1
+ module Vectory
2
+ class FileMagic
3
+ EPS_30_MAGIC = "\x25\x21\x50\x53\x2d\x41\x64\x6f\x62\x65\x2d\x33\x2e\x30" \
4
+ "\x20\x45\x50\x53\x46\x2d\x33\x2e\x30"
5
+ .force_encoding("BINARY") # "%!PS-Adobe-3.0 EPSF-3.0"
6
+
7
+ EPS_31_MAGIC = "\x25\x21\x50\x53\x2d\x41\x64\x6f\x62\x65\x2d\x33\x2e\x31" \
8
+ "\x20\x45\x50\x53\x46\x2d\x33\x2e\x30"
9
+ .force_encoding("BINARY") # "%!PS-Adobe-3.1 EPSF-3.0"
10
+
11
+ PS_MAGIC = "\x25\x21\x50\x53\x2d\x41\x64\x6f\x62\x65\x2d\x33\x2e\x30"
12
+ .force_encoding("BINARY") # "%!PS-Adobe-3.0"
13
+
14
+ EMF_MAGIC = "\x01\x00\x00\x00".force_encoding("BINARY")
15
+
16
+ def self.detect(path)
17
+ new(path).detect
18
+ end
19
+
20
+ def initialize(path)
21
+ @path = path
22
+ end
23
+
24
+ def detect
25
+ beginning = File.read(@path, 100, mode: "rb")
26
+
27
+ eps_slice = beginning.byteslice(0, EPS_30_MAGIC.size)
28
+ Vectory.ui.debug("File magic is '#{to_bytes(beginning)}' of '#{@path}'.")
29
+
30
+ return :eps if [EPS_30_MAGIC, EPS_31_MAGIC].include?(eps_slice)
31
+
32
+ ps_slice = beginning.byteslice(0, PS_MAGIC.size)
33
+ return :ps if ps_slice == PS_MAGIC
34
+
35
+ emf_slice = beginning.byteslice(0, EMF_MAGIC.size)
36
+ return :emf if emf_slice == EMF_MAGIC
37
+
38
+ return :svg if contain_svg_tag?
39
+ end
40
+
41
+ private
42
+
43
+ def to_bytes(str)
44
+ str.unpack("c*").map { |e| "\\x#{e.to_s(16).rjust(2, '0')}" }.join
45
+ end
46
+
47
+ def contain_svg_tag?
48
+ content = File.read(@path, 4096)
49
+
50
+ return :svg if content.include?("<svg")
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+
5
+ module Vectory
6
+ class Image
7
+ class << self
8
+ def from_path(path)
9
+ new(File.read(path, mode: "rb"))
10
+ end
11
+
12
+ def from_content(content)
13
+ new(content)
14
+ end
15
+ end
16
+
17
+ attr_reader :content
18
+
19
+ def initialize(content)
20
+ @content = content
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require_relative "system_call"
5
+
6
+ module Vectory
7
+ class InkscapeConverter
8
+ include Singleton
9
+
10
+ def self.convert(uri, output_extension, option)
11
+ instance.convert(uri, output_extension, option)
12
+ end
13
+
14
+ def convert(uri, output_extension, option)
15
+ exe = inkscape_path_or_raise_error(uri)
16
+ uri = external_path uri
17
+ exe = external_path exe
18
+ cmd = %(#{exe} #{option} #{uri})
19
+
20
+ call = SystemCall.new(cmd).call
21
+
22
+ output_path = "#{uri}.#{output_extension}"
23
+ raise_conversion_error(call) unless File.exist?(output_path)
24
+
25
+ # and return Vectory::Utils::datauri(file)
26
+
27
+ output_path
28
+ end
29
+
30
+ private
31
+
32
+ def inkscape_path_or_raise_error(path)
33
+ inkscape_path or raise(InkscapeNotFoundError,
34
+ "Inkscape missing in PATH, unable to " \
35
+ "convert image #{path}. Aborting.")
36
+ end
37
+
38
+ def inkscape_path
39
+ cmd = "inkscape"
40
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
41
+
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)
46
+ end
47
+ end
48
+
49
+ nil
50
+ end
51
+
52
+ def raise_conversion_error(call)
53
+ raise Vectory::ConversionError,
54
+ "Could not convert with Inkscape. " \
55
+ "Inkscape cmd: '#{call.cmd}',\n" \
56
+ "status: '#{call.status}',\n" \
57
+ "stdout: '#{call.stdout.strip}',\n" \
58
+ "stderr: '#{call.stderr.strip}'."
59
+ end
60
+
61
+ def external_path(path)
62
+ win = !!((RUBY_PLATFORM =~ /(win|w)(32|64)$/) ||
63
+ (RUBY_PLATFORM =~ /mswin|mingw/))
64
+ if win
65
+ path.gsub!(%{/}, "\\")
66
+ path[/\s/] ? "\"#{path}\"" : path
67
+ else
68
+ path
69
+ end
70
+ end
71
+ end
72
+ end
data/lib/vectory/ps.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vectory
4
+ class Ps < Vector
5
+ def self.default_extension
6
+ "ps"
7
+ end
8
+
9
+ def self.mimetype
10
+ "application/postscript"
11
+ end
12
+
13
+ def to_eps
14
+ convert_with_inkscape("--export-type=eps", Eps)
15
+ end
16
+
17
+ def to_emf
18
+ convert_with_inkscape("--export-type=emf", Emf)
19
+ end
20
+
21
+ def to_svg
22
+ convert_with_inkscape("--export-type=svg", Svg)
23
+ end
24
+ end
25
+ end