vectory 0.1.0 → 0.2.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 (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