vectory 0.10.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.adoc +4 -6
- data/lib/vectory/cli.rb +0 -4
- data/lib/vectory/configuration.rb +3 -31
- data/lib/vectory/emf.rb +25 -12
- data/lib/vectory/eps.rb +2 -14
- data/lib/vectory/errors.rb +0 -14
- data/lib/vectory/ps.rb +2 -14
- data/lib/vectory/svg.rb +31 -36
- data/lib/vectory/svg_document.rb +4 -4
- data/lib/vectory/svg_mapping.rb +33 -22
- data/lib/vectory/vector.rb +6 -2
- data/lib/vectory/version.rb +1 -1
- data/lib/vectory.rb +0 -10
- data/scripts/regenerate_fixtures.rb +33 -0
- data/vectory.gemspec +2 -3
- metadata +8 -29
- data/lib/vectory/conversion/ghostscript_strategy.rb +0 -74
- data/lib/vectory/conversion/inkscape_strategy.rb +0 -121
- data/lib/vectory/conversion/strategy.rb +0 -58
- data/lib/vectory/conversion.rb +0 -104
- data/lib/vectory/ghostscript_wrapper.rb +0 -183
- data/lib/vectory/inkscape_wrapper.rb +0 -226
- data/lib/vectory/pdf.rb +0 -116
- data/lib/vectory/platform.rb +0 -105
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66b15c386fa1dcdd0fc02473b9062f3eca19ed5b1bc40f2facec9577a4495fd4
|
|
4
|
+
data.tar.gz: d5bc9be14ab660ddbbba77548a205a472eb4215bb848c57d5e3d980205159da4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e802267d5860c496738492218e4fd8260ff9c84546d63c11254d170f14ee1c8c9bebd24a943b24e8bcd5c7013db8a3d55844aba286d06f5959a717fe289da34
|
|
7
|
+
data.tar.gz: 53747660f38c403952debd623ad81dbce990e51fa8098d63480762c3de40531cc2602b903c943c91e01f9678433b6340b77529345ad487f53b4fb2c04ca4bfe6
|
data/Gemfile
CHANGED
|
@@ -9,6 +9,7 @@ gem "canon", "~> 0.1.7"
|
|
|
9
9
|
gem "connection_pool", "~> 2.0"
|
|
10
10
|
gem "openssl", "~> 3.0"
|
|
11
11
|
gem "rake"
|
|
12
|
+
gem "nokogiri" # dev-only: exercises SvgMapping's duck-typed document-object input (metanorma passes Nokogiri docs)
|
|
12
13
|
gem "rspec"
|
|
13
14
|
gem "rubocop"
|
|
14
15
|
gem "rubocop-performance"
|
data/README.adoc
CHANGED
|
@@ -21,14 +21,12 @@ ____
|
|
|
21
21
|
|
|
22
22
|
=== Prerequisites
|
|
23
23
|
|
|
24
|
-
Vectory
|
|
24
|
+
Vectory ships as a pure-Ruby gem. Format support is provided by these Ruby gems (installed automatically):
|
|
25
25
|
|
|
26
|
-
* https://github.com/claricle/emfsvg[emfsvg]
|
|
27
|
-
* https://
|
|
28
|
-
* https://www.ghostscript.com/[Ghostscript]
|
|
26
|
+
* https://github.com/claricle/emfsvg[emfsvg] (EMF <-> SVG)
|
|
27
|
+
* https://github.com/claricle/postsvg[postsvg] (PS/EPS <-> SVG)
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
this issue, the 1.3.0 version of Inkscape can be used.
|
|
29
|
+
No external binaries are required.
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
=== Gem install
|
data/lib/vectory/cli.rb
CHANGED
|
@@ -9,14 +9,10 @@ module Vectory
|
|
|
9
9
|
STATUS_UNSUPPORTED_OUTPUT_FORMAT_ERROR = 3
|
|
10
10
|
STATUS_CONVERSION_ERROR = 4
|
|
11
11
|
STATUS_SYSTEM_CALL_ERROR = 5
|
|
12
|
-
STATUS_INKSCAPE_NOT_FOUND_ERROR = 6
|
|
13
12
|
STATUS_SAME_FORMAT_ERROR = 7
|
|
14
|
-
STATUS_GHOSTSCRIPT_NOT_FOUND_ERROR = 8
|
|
15
13
|
|
|
16
14
|
MAP_ERROR_TO_STATUS = {
|
|
17
15
|
Vectory::ConversionError => STATUS_CONVERSION_ERROR,
|
|
18
|
-
Vectory::InkscapeNotFoundError => STATUS_INKSCAPE_NOT_FOUND_ERROR,
|
|
19
|
-
Vectory::GhostscriptNotFoundError => STATUS_GHOSTSCRIPT_NOT_FOUND_ERROR,
|
|
20
16
|
Vectory::SystemCallError => STATUS_SYSTEM_CALL_ERROR,
|
|
21
17
|
}.freeze
|
|
22
18
|
|
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
module Vectory
|
|
4
4
|
# Configuration for Vectory
|
|
5
5
|
#
|
|
6
|
-
# Provides centralized configuration for
|
|
6
|
+
# Provides centralized configuration for timeouts, caching, etc.
|
|
7
7
|
# Can be loaded from environment variables or a configuration file.
|
|
8
8
|
#
|
|
9
|
-
# @example Set custom Inkscape path
|
|
10
|
-
# Vectory::Configuration.instance.inkscape_path = "/path/to/inkscape"
|
|
11
|
-
#
|
|
12
9
|
# @example Load from environment variables
|
|
13
|
-
# # Set VECTORY_INKSCAPE_PATH environment variable
|
|
14
10
|
# Vectory::Configuration.load_from_environment
|
|
15
11
|
class Configuration
|
|
16
12
|
# Default timeout for external tool execution (seconds)
|
|
@@ -22,8 +18,8 @@ module Vectory
|
|
|
22
18
|
# Default temporary directory
|
|
23
19
|
DEFAULT_TEMP_DIR = nil # Use system default
|
|
24
20
|
|
|
25
|
-
attr_accessor :
|
|
26
|
-
:
|
|
21
|
+
attr_accessor :timeout, :cache_ttl, :cache_enabled, :temp_dir,
|
|
22
|
+
:verbose_logging
|
|
27
23
|
|
|
28
24
|
# Get the singleton instance
|
|
29
25
|
#
|
|
@@ -46,15 +42,11 @@ module Vectory
|
|
|
46
42
|
@cache_enabled = true
|
|
47
43
|
@temp_dir = DEFAULT_TEMP_DIR
|
|
48
44
|
@verbose_logging = false
|
|
49
|
-
@inkscape_path = nil
|
|
50
|
-
@ghostscript_path = nil
|
|
51
45
|
end
|
|
52
46
|
|
|
53
47
|
# Load configuration from environment variables
|
|
54
48
|
#
|
|
55
49
|
# Supported environment variables:
|
|
56
|
-
# - VECTORY_INKSCAPE_PATH: Path to Inkscape executable
|
|
57
|
-
# - VECTORY_GHOSTSCRIPT_PATH: Path to Ghostscript executable
|
|
58
50
|
# - VECTORY_TIMEOUT: Timeout for external tools (default: 120)
|
|
59
51
|
# - VECTORY_CACHE_TTL: Cache TTL in seconds (default: 300)
|
|
60
52
|
# - VECTORY_CACHE_ENABLED: Enable/disable caching (default: true)
|
|
@@ -65,8 +57,6 @@ module Vectory
|
|
|
65
57
|
def self.load_from_environment
|
|
66
58
|
config = instance
|
|
67
59
|
|
|
68
|
-
config.inkscape_path = ENV["VECTORY_INKSCAPE_PATH"] if ENV["VECTORY_INKSCAPE_PATH"]
|
|
69
|
-
config.ghostscript_path = ENV["VECTORY_GHOSTSCRIPT_PATH"] if ENV["VECTORY_GHOSTSCRIPT_PATH"]
|
|
70
60
|
config.timeout = ENV["VECTORY_TIMEOUT"]&.to_i || config.timeout
|
|
71
61
|
config.cache_ttl = ENV["VECTORY_CACHE_TTL"]&.to_i || config.cache_ttl
|
|
72
62
|
config.cache_enabled = ENV["VECTORY_CACHE_ENABLED"] != "false"
|
|
@@ -86,8 +76,6 @@ module Vectory
|
|
|
86
76
|
config_data = YAML.load_file(path)
|
|
87
77
|
|
|
88
78
|
config = instance
|
|
89
|
-
config.inkscape_path = config_data["inkscape_path"] if config_data["inkscape_path"]
|
|
90
|
-
config.ghostscript_path = config_data["ghostscript_path"] if config_data["ghostscript_path"]
|
|
91
79
|
config.timeout = config_data["timeout"] || config.timeout
|
|
92
80
|
config.cache_ttl = config_data["cache_ttl"] || config.cache_ttl
|
|
93
81
|
config.cache_enabled = config_data.fetch("cache_enabled",
|
|
@@ -104,8 +92,6 @@ module Vectory
|
|
|
104
92
|
# @return [Hash] the configuration as a hash
|
|
105
93
|
def to_h
|
|
106
94
|
{
|
|
107
|
-
inkscape_path: @inkscape_path,
|
|
108
|
-
ghostscript_path: @ghostscript_path,
|
|
109
95
|
timeout: @timeout,
|
|
110
96
|
cache_ttl: @cache_ttl,
|
|
111
97
|
cache_enabled: @cache_enabled,
|
|
@@ -114,20 +100,6 @@ module Vectory
|
|
|
114
100
|
}
|
|
115
101
|
end
|
|
116
102
|
|
|
117
|
-
# Get the Inkscape path (custom or auto-detected)
|
|
118
|
-
#
|
|
119
|
-
# @return [String, nil] the configured Inkscape path or nil if not set
|
|
120
|
-
def effective_inkscape_path
|
|
121
|
-
@inkscape_path
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Get the Ghostscript path (custom or auto-detected)
|
|
125
|
-
#
|
|
126
|
-
# @return [String, nil] the configured Ghostscript path or nil if not set
|
|
127
|
-
def effective_ghostscript_path
|
|
128
|
-
@ghostscript_path
|
|
129
|
-
end
|
|
130
|
-
|
|
131
103
|
# Check if caching is enabled
|
|
132
104
|
#
|
|
133
105
|
# @return [Boolean] true if caching is enabled
|
data/lib/vectory/emf.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "emf"
|
|
3
4
|
require "emfsvg"
|
|
4
5
|
|
|
5
6
|
module Vectory
|
|
@@ -32,21 +33,33 @@ module Vectory
|
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def to_eps
|
|
35
|
-
|
|
36
|
-
content: content,
|
|
37
|
-
input_format: :emf,
|
|
38
|
-
output_format: :eps,
|
|
39
|
-
output_class: Eps,
|
|
40
|
-
)
|
|
36
|
+
to_svg.to_eps
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
def to_ps
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
to_svg.to_ps
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def height
|
|
44
|
+
bounds = parse_header_bounds
|
|
45
|
+
return super unless bounds
|
|
46
|
+
|
|
47
|
+
bounds.bottom - bounds.top
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def width
|
|
51
|
+
bounds = parse_header_bounds
|
|
52
|
+
return super unless bounds
|
|
53
|
+
|
|
54
|
+
bounds.right - bounds.left
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def parse_header_bounds
|
|
60
|
+
::Emf.parse(content).header.bounds
|
|
61
|
+
rescue StandardError
|
|
62
|
+
nil
|
|
50
63
|
end
|
|
51
64
|
end
|
|
52
65
|
end
|
data/lib/vectory/eps.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Vectory
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def to_ps
|
|
25
|
-
|
|
25
|
+
to_svg.to_ps
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def to_svg
|
|
@@ -30,19 +30,7 @@ module Vectory
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def to_emf
|
|
33
|
-
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def to_pdf
|
|
37
|
-
pdf_content = GhostscriptWrapper.convert(content, eps_crop: true)
|
|
38
|
-
pdf = Pdf.new(pdf_content)
|
|
39
|
-
# Pass original BoundingBox dimensions to preserve them in conversions
|
|
40
|
-
bbox = parse_bounding_box
|
|
41
|
-
if bbox
|
|
42
|
-
pdf.original_width = bbox[:urx] - bbox[:llx]
|
|
43
|
-
pdf.original_height = bbox[:ury] - bbox[:lly]
|
|
44
|
-
end
|
|
45
|
-
pdf
|
|
33
|
+
to_svg.to_emf
|
|
46
34
|
end
|
|
47
35
|
|
|
48
36
|
def height
|
data/lib/vectory/errors.rb
CHANGED
|
@@ -3,20 +3,6 @@
|
|
|
3
3
|
module Vectory
|
|
4
4
|
class ConversionError < Error; end
|
|
5
5
|
|
|
6
|
-
class InkscapeNotFoundError < Error
|
|
7
|
-
def initialize(msg = nil)
|
|
8
|
-
super(msg || "Inkscape not found in PATH. Please install Inkscape.")
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
class GhostscriptNotFoundError < Error
|
|
13
|
-
def initialize(msg = nil)
|
|
14
|
-
super(msg || "Ghostscript not found in PATH. Please install Ghostscript.")
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
class InkscapeQueryError < Error; end
|
|
19
|
-
|
|
20
6
|
class InvalidFormatError < Error
|
|
21
7
|
def initialize(format, supported_formats)
|
|
22
8
|
super("Invalid format '#{format}'. Supported formats: #{supported_formats.join(', ')}")
|
data/lib/vectory/ps.rb
CHANGED
|
@@ -22,29 +22,17 @@ module Vectory
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def to_eps
|
|
25
|
-
|
|
25
|
+
to_svg.to_eps
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def to_emf
|
|
29
|
-
|
|
29
|
+
to_svg.to_emf
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def to_svg
|
|
33
33
|
Svg.from_content(Postsvg.convert(content))
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def to_pdf
|
|
37
|
-
pdf_content = GhostscriptWrapper.convert(content, eps_crop: false)
|
|
38
|
-
pdf = Pdf.new(pdf_content)
|
|
39
|
-
# Pass original BoundingBox dimensions to preserve them in conversions
|
|
40
|
-
bbox = parse_bounding_box
|
|
41
|
-
if bbox
|
|
42
|
-
pdf.original_width = bbox[:urx] - bbox[:llx]
|
|
43
|
-
pdf.original_height = bbox[:ury] - bbox[:lly]
|
|
44
|
-
end
|
|
45
|
-
pdf
|
|
46
|
-
end
|
|
47
|
-
|
|
48
36
|
def height
|
|
49
37
|
bbox = parse_bounding_box
|
|
50
38
|
return super unless bbox
|
data/lib/vectory/svg.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "leptris"
|
|
4
|
+
require "postsvg"
|
|
4
5
|
|
|
5
6
|
module Vectory
|
|
6
7
|
class Svg < Vector
|
|
@@ -36,57 +37,51 @@ module Vectory
|
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def to_eps
|
|
39
|
-
|
|
40
|
-
content: content,
|
|
41
|
-
input_format: :svg,
|
|
42
|
-
output_format: :eps,
|
|
43
|
-
output_class: Eps,
|
|
44
|
-
)
|
|
40
|
+
Eps.from_content(Postsvg.to_eps(content))
|
|
45
41
|
end
|
|
46
42
|
|
|
47
43
|
def to_ps
|
|
48
|
-
|
|
49
|
-
content: content,
|
|
50
|
-
input_format: :svg,
|
|
51
|
-
output_format: :ps,
|
|
52
|
-
output_class: Ps,
|
|
53
|
-
)
|
|
44
|
+
Ps.from_content(Postsvg.to_ps(content))
|
|
54
45
|
end
|
|
55
46
|
|
|
56
47
|
def height
|
|
57
|
-
|
|
58
|
-
doc = Nokogiri::XML(content)
|
|
59
|
-
svg_element = doc.at_xpath("//svg:svg",
|
|
60
|
-
"svg" => SVG_NS) || doc.at_xpath("//svg")
|
|
61
|
-
|
|
62
|
-
if svg_element && svg_element["height"]
|
|
63
|
-
svg_element["height"].to_f.round
|
|
64
|
-
else
|
|
65
|
-
# Fall back to Inkscape query if no height attribute
|
|
66
|
-
super
|
|
67
|
-
end
|
|
48
|
+
dim_from_attr("height") || dim_from_viewbox(3) || super
|
|
68
49
|
end
|
|
69
50
|
|
|
70
51
|
def width
|
|
71
|
-
|
|
72
|
-
doc = Nokogiri::XML(content)
|
|
73
|
-
svg_element = doc.at_xpath("//svg:svg",
|
|
74
|
-
"svg" => SVG_NS) || doc.at_xpath("//svg")
|
|
75
|
-
|
|
76
|
-
if svg_element && svg_element["width"]
|
|
77
|
-
svg_element["width"].to_f.round
|
|
78
|
-
else
|
|
79
|
-
# Fall back to Inkscape query if no width attribute
|
|
80
|
-
super
|
|
81
|
-
end
|
|
52
|
+
dim_from_attr("width") || dim_from_viewbox(2) || super
|
|
82
53
|
end
|
|
83
54
|
|
|
84
55
|
private
|
|
85
56
|
|
|
57
|
+
def svg_root
|
|
58
|
+
doc = Leptris::XML.parse(content)
|
|
59
|
+
doc.at_xpath("//svg:svg", "svg" => SVG_NS) || doc.at_xpath("//svg")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def dim_from_attr(name)
|
|
63
|
+
value = svg_root&.[](name)
|
|
64
|
+
value&.to_f&.round
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def dim_from_viewbox(index)
|
|
68
|
+
vb = svg_root&.[]("viewBox")
|
|
69
|
+
return nil unless vb
|
|
70
|
+
|
|
71
|
+
parts = vb.split
|
|
72
|
+
return nil unless parts.length == 4
|
|
73
|
+
|
|
74
|
+
parts[index].to_f.round
|
|
75
|
+
end
|
|
76
|
+
|
|
86
77
|
def content=(content)
|
|
87
78
|
# non-root node inserts the xml tag which breaks markup when placed in
|
|
88
79
|
# another xml document
|
|
89
|
-
document =
|
|
80
|
+
document = begin
|
|
81
|
+
Leptris::XML.parse(content).root
|
|
82
|
+
rescue Leptris::XML::ParseError
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
90
85
|
unless document
|
|
91
86
|
raise ParsingError, "Could not parse '#{content&.slice(0, 30)}'"
|
|
92
87
|
end
|
data/lib/vectory/svg_document.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "leptris"
|
|
2
2
|
|
|
3
3
|
module Vectory
|
|
4
4
|
# Represents and processes an SVG document within XML.
|
|
@@ -21,7 +21,7 @@ module Vectory
|
|
|
21
21
|
class << self
|
|
22
22
|
# Updates ID references in CSS style statements within a document.
|
|
23
23
|
#
|
|
24
|
-
# @param document [
|
|
24
|
+
# @param document [Leptris::XML::Document] The SVG document
|
|
25
25
|
# @param ids [Array<String>] IDs to update
|
|
26
26
|
# @param suffix [Integer, String] The suffix to apply (integers are zero-padded)
|
|
27
27
|
# @return [void]
|
|
@@ -56,7 +56,7 @@ module Vectory
|
|
|
56
56
|
|
|
57
57
|
# Updates ID attributes in SVG elements.
|
|
58
58
|
#
|
|
59
|
-
# @param document [
|
|
59
|
+
# @param document [Leptris::XML::Document] The SVG document
|
|
60
60
|
# @param ids [Array<String>] IDs to update
|
|
61
61
|
# @param suffix [Integer, String] The suffix to apply (integers are zero-padded)
|
|
62
62
|
# @return [void]
|
|
@@ -76,7 +76,7 @@ module Vectory
|
|
|
76
76
|
#
|
|
77
77
|
# @param content [String] SVG XML content
|
|
78
78
|
def initialize(content)
|
|
79
|
-
@document =
|
|
79
|
+
@document = Leptris::XML.parse(content)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# Returns the processed SVG content as XML string.
|
data/lib/vectory/svg_mapping.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "leptris"
|
|
4
|
+
|
|
1
5
|
module Vectory
|
|
2
6
|
# Processes SVG mapping in XML documents.
|
|
3
7
|
#
|
|
@@ -7,6 +11,11 @@ module Vectory
|
|
|
7
11
|
# - Applying ID suffixes for uniqueness in multi-document scenarios
|
|
8
12
|
# - Simplifying svgmap structure for final output
|
|
9
13
|
#
|
|
14
|
+
# The document is processed on a leptris tree: pass XML content, or any
|
|
15
|
+
# document object responding to #to_xml (serialized once on entry).
|
|
16
|
+
# #call returns the processed Leptris document; callers working on
|
|
17
|
+
# their own DOM (e.g. Nokogiri) re-serialize and graft the result back.
|
|
18
|
+
#
|
|
10
19
|
# == ID Suffixing
|
|
11
20
|
#
|
|
12
21
|
# SVG IDs are suffixed in two stages to ensure uniqueness:
|
|
@@ -30,7 +39,7 @@ module Vectory
|
|
|
30
39
|
class SvgMapping
|
|
31
40
|
# Namespace helper for XML namespace handling
|
|
32
41
|
class Namespace
|
|
33
|
-
# @param xmldoc [
|
|
42
|
+
# @param xmldoc [Leptris::XML::Document] The XML document
|
|
34
43
|
def initialize(xmldoc)
|
|
35
44
|
@namespace = xmldoc.root.namespace
|
|
36
45
|
end
|
|
@@ -61,7 +70,7 @@ module Vectory
|
|
|
61
70
|
# @param path [String] Path to the XML file
|
|
62
71
|
# @return [Vectory::SvgMapping] New mapping instance
|
|
63
72
|
def self.from_path(path)
|
|
64
|
-
new(
|
|
73
|
+
new(File.read(path))
|
|
65
74
|
end
|
|
66
75
|
|
|
67
76
|
# Creates an SvgMapping from an XML string.
|
|
@@ -69,25 +78,27 @@ module Vectory
|
|
|
69
78
|
# @param xml [String] XML content
|
|
70
79
|
# @return [Vectory::SvgMapping] New mapping instance
|
|
71
80
|
def self.from_xml(xml)
|
|
72
|
-
new(
|
|
81
|
+
new(xml)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
84
|
# Initializes a new SvgMapping processor.
|
|
76
85
|
#
|
|
77
|
-
# @param doc [
|
|
86
|
+
# @param doc [String, #to_xml] XML content, or a document object
|
|
87
|
+
# (serialized once on entry)
|
|
78
88
|
# @param local_directory [String] Directory for resolving relative file paths
|
|
79
89
|
# @param id_suffix [String, nil] Optional suffix derived from document/container
|
|
80
90
|
# identity. Applied before the index suffix to provide cross-document
|
|
81
91
|
# uniqueness. Example: "_ISO_17301-1_2016"
|
|
82
92
|
def initialize(doc, local_directory = "", id_suffix: nil)
|
|
83
|
-
|
|
93
|
+
xml = doc.respond_to?(:to_xml) ? doc.to_xml : doc
|
|
94
|
+
@doc = Leptris::XML.parse(xml)
|
|
84
95
|
@local_directory = local_directory
|
|
85
96
|
@id_suffix = id_suffix
|
|
86
97
|
end
|
|
87
98
|
|
|
88
99
|
# Processes all svgmap elements in the document.
|
|
89
100
|
#
|
|
90
|
-
# @return [
|
|
101
|
+
# @return [Leptris::XML::Document] The processed document
|
|
91
102
|
def call
|
|
92
103
|
@namespace = Namespace.new(@doc)
|
|
93
104
|
|
|
@@ -109,7 +120,7 @@ module Vectory
|
|
|
109
120
|
|
|
110
121
|
# Processes a single svgmap element.
|
|
111
122
|
#
|
|
112
|
-
# @param svgmap [
|
|
123
|
+
# @param svgmap [Leptris::XML::Element] The svgmap element to process
|
|
113
124
|
# @param index [Integer] Position of this svgmap in the document (0-indexed)
|
|
114
125
|
def process_svgmap(svgmap, index)
|
|
115
126
|
image = extract_image_tag(svgmap)
|
|
@@ -125,19 +136,19 @@ module Vectory
|
|
|
125
136
|
|
|
126
137
|
# Extracts the image element (SVG inline or image with src) from svgmap.
|
|
127
138
|
#
|
|
128
|
-
# @param svgmap [
|
|
129
|
-
# @return [
|
|
139
|
+
# @param svgmap [Leptris::XML::Element] The svgmap element
|
|
140
|
+
# @return [Leptris::XML::Element, nil] The image/svg element or nil
|
|
130
141
|
def extract_image_tag(svgmap)
|
|
131
|
-
image = svgmap.
|
|
142
|
+
image = svgmap.at_xpath(@namespace.ns(".//image"))
|
|
132
143
|
return image if image && image["src"] && !image["src"].empty?
|
|
133
144
|
|
|
134
|
-
svgmap.
|
|
145
|
+
svgmap.at_xpath(".//m:svg", "m" => SVG_NS)
|
|
135
146
|
end
|
|
136
147
|
|
|
137
148
|
# Generates processed SVG content for an svgmap.
|
|
138
149
|
#
|
|
139
|
-
# @param image [
|
|
140
|
-
# @param svgmap [
|
|
150
|
+
# @param image [Leptris::XML::Element] The image/svg element
|
|
151
|
+
# @param svgmap [Leptris::XML::Element] The parent svgmap element
|
|
141
152
|
# @param index [Integer] Index suffix to apply
|
|
142
153
|
# @return [String, nil] Processed SVG content or nil if invalid
|
|
143
154
|
def generate_content(image, svgmap, index)
|
|
@@ -153,7 +164,7 @@ module Vectory
|
|
|
153
164
|
|
|
154
165
|
# Builds an SvgDocument from an image element.
|
|
155
166
|
#
|
|
156
|
-
# @param image [
|
|
167
|
+
# @param image [Leptris::XML::Element] The image element
|
|
157
168
|
# @return [Vectory::SvgDocument, nil] The SVG document or nil if invalid
|
|
158
169
|
def build_svg_document(image)
|
|
159
170
|
vector = build_vector(image)
|
|
@@ -164,7 +175,7 @@ module Vectory
|
|
|
164
175
|
|
|
165
176
|
# Builds a Vector from an image element.
|
|
166
177
|
#
|
|
167
|
-
# @param image [
|
|
178
|
+
# @param image [Leptris::XML::Element] The image element
|
|
168
179
|
# @return [Vectory::Vector, nil] The vector or nil if invalid
|
|
169
180
|
def build_vector(image)
|
|
170
181
|
return Vectory::Svg.from_content(image.to_xml) if image.name == "svg"
|
|
@@ -182,7 +193,7 @@ module Vectory
|
|
|
182
193
|
|
|
183
194
|
# Builds a mapping from target hrefs to their link targets.
|
|
184
195
|
#
|
|
185
|
-
# @param svgmap [
|
|
196
|
+
# @param svgmap [Leptris::XML::Element] The svgmap element
|
|
186
197
|
# @return [Hash{String => String}] Mapping of href to target
|
|
187
198
|
def from_targets_to_links_map(svgmap)
|
|
188
199
|
targets = svgmap.xpath(@namespace.ns("./target"))
|
|
@@ -199,13 +210,13 @@ module Vectory
|
|
|
199
210
|
|
|
200
211
|
# Extracts link target from a target element.
|
|
201
212
|
#
|
|
202
|
-
# @param target_tag [
|
|
213
|
+
# @param target_tag [Leptris::XML::Element] The target element
|
|
203
214
|
# @return [String, nil] The link target or nil
|
|
204
215
|
def link_target(target_tag)
|
|
205
|
-
xref = target_tag.
|
|
216
|
+
xref = target_tag.at_xpath(@namespace.ns("./xref"))
|
|
206
217
|
return "##{xref['target']}" if xref
|
|
207
218
|
|
|
208
|
-
link = target_tag.
|
|
219
|
+
link = target_tag.at_xpath(@namespace.ns("./link"))
|
|
209
220
|
return unless link
|
|
210
221
|
|
|
211
222
|
link["target"]
|
|
@@ -214,11 +225,11 @@ module Vectory
|
|
|
214
225
|
# Simplifies svgmap after processing.
|
|
215
226
|
# Removes svgmap wrapper if no more target elements with eref.
|
|
216
227
|
#
|
|
217
|
-
# @param svgmap [
|
|
228
|
+
# @param svgmap [Leptris::XML::Element] The svgmap element
|
|
218
229
|
def simplify_svgmap(svgmap)
|
|
219
|
-
return if svgmap.
|
|
230
|
+
return if svgmap.at_xpath(@namespace.ns("./target/eref"))
|
|
220
231
|
|
|
221
|
-
svgmap.replace(svgmap.
|
|
232
|
+
svgmap.replace(svgmap.at_xpath(@namespace.ns("./figure")))
|
|
222
233
|
end
|
|
223
234
|
end
|
|
224
235
|
end
|
data/lib/vectory/vector.rb
CHANGED
|
@@ -46,11 +46,15 @@ module Vectory
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def height
|
|
49
|
-
|
|
49
|
+
raise Vectory::NotImplementedError,
|
|
50
|
+
"#height should be implemented in a subclass when no intrinsic " \
|
|
51
|
+
"dimension source is available."
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def width
|
|
53
|
-
|
|
55
|
+
raise Vectory::NotImplementedError,
|
|
56
|
+
"#width should be implemented in a subclass when no intrinsic " \
|
|
57
|
+
"dimension source is available."
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
def to_uri
|
data/lib/vectory/version.rb
CHANGED
data/lib/vectory.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# External dependencies
|
|
4
4
|
require "logger"
|
|
5
|
-
require "ukiryu"
|
|
6
5
|
|
|
7
6
|
# Define base error class and additional error classes
|
|
8
7
|
# (used in class bodies like cli.rb, so can't be autoloaded)
|
|
@@ -25,14 +24,6 @@ require_relative "vectory/version"
|
|
|
25
24
|
module Vectory
|
|
26
25
|
# Core utilities
|
|
27
26
|
autoload :Utils, "vectory/utils"
|
|
28
|
-
autoload :Platform, "vectory/platform"
|
|
29
|
-
|
|
30
|
-
# Wrappers
|
|
31
|
-
autoload :GhostscriptWrapper, "vectory/ghostscript_wrapper"
|
|
32
|
-
autoload :InkscapeWrapper, "vectory/inkscape_wrapper"
|
|
33
|
-
|
|
34
|
-
# Conversion system
|
|
35
|
-
autoload :Conversion, "vectory/conversion"
|
|
36
27
|
|
|
37
28
|
# Format classes
|
|
38
29
|
autoload :Configuration, "vectory/configuration"
|
|
@@ -40,7 +31,6 @@ module Vectory
|
|
|
40
31
|
autoload :ImageResize, "vectory/image_resize"
|
|
41
32
|
autoload :Datauri, "vectory/datauri"
|
|
42
33
|
autoload :Vector, "vectory/vector"
|
|
43
|
-
autoload :Pdf, "vectory/pdf"
|
|
44
34
|
autoload :Eps, "vectory/eps"
|
|
45
35
|
autoload :Ps, "vectory/ps"
|
|
46
36
|
autoload :Emf, "vectory/emf"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# One-shot fixture regeneration script.
|
|
5
|
+
# Run from the vectory root: `bundle exec ruby scripts/regenerate_fixtures.rb`
|
|
6
|
+
|
|
7
|
+
require_relative "../lib/vectory"
|
|
8
|
+
|
|
9
|
+
Pairs = [
|
|
10
|
+
["spec/examples/emf2eps/img.emf", :to_eps, "spec/examples/emf2eps/ref.eps"],
|
|
11
|
+
["spec/examples/emf2ps/img.emf", :to_ps, "spec/examples/emf2ps/ref.ps"],
|
|
12
|
+
["spec/examples/eps2emf/img.eps", :to_emf, "spec/examples/eps2emf/ref.emf"],
|
|
13
|
+
["spec/examples/eps2ps/img.eps", :to_ps, "spec/examples/eps2ps/ref.ps"],
|
|
14
|
+
["spec/examples/eps2svg/img.eps", :to_svg, "spec/examples/eps2svg/ref.svg"],
|
|
15
|
+
["spec/examples/ps2emf/img.ps", :to_emf, "spec/examples/ps2emf/ref.emf"],
|
|
16
|
+
["spec/examples/ps2eps/img.ps", :to_eps, "spec/examples/ps2eps/ref.eps"],
|
|
17
|
+
["spec/examples/ps2svg/img.ps", :to_svg, "spec/examples/ps2svg/ref.svg"],
|
|
18
|
+
["spec/examples/svg2emf/img.svg", :to_emf, "spec/examples/svg2emf/ref.emf"],
|
|
19
|
+
["spec/examples/svg2eps/img.svg", :to_eps, "spec/examples/svg2eps/ref.eps"],
|
|
20
|
+
["spec/examples/svg2ps/img.svg", :to_ps, "spec/examples/svg2ps/ref.ps"],
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
Pairs.each do |input, method, output|
|
|
24
|
+
ext = File.extname(input).delete(".")
|
|
25
|
+
klass_name = { "emf" => "Emf", "eps" => "Eps", "ps" => "Ps",
|
|
26
|
+
"svg" => "Svg" }.fetch(ext)
|
|
27
|
+
klass = Vectory.const_get(klass_name)
|
|
28
|
+
result = klass.from_path(input).public_send(method)
|
|
29
|
+
File.binwrite(output, result.content)
|
|
30
|
+
puts "[OK] #{input} -> #{output} (#{result.content.bytesize} bytes)"
|
|
31
|
+
rescue => e
|
|
32
|
+
puts "[FAIL] #{input} -> #{output}: #{e.class}: #{e.message}"
|
|
33
|
+
end
|
data/vectory.gemspec
CHANGED
|
@@ -30,8 +30,7 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.add_dependency "emfsvg", "~> 0.1", ">= 0.1.2"
|
|
31
31
|
spec.add_dependency "image_size", ">= 3.2.0"
|
|
32
32
|
spec.add_dependency "marcel", "~> 1.0"
|
|
33
|
-
spec.add_dependency "
|
|
34
|
-
spec.add_dependency "postsvg", "~> 0.
|
|
33
|
+
spec.add_dependency "leptris", "~> 1.9.361.1"
|
|
34
|
+
spec.add_dependency "postsvg", "~> 0.3"
|
|
35
35
|
spec.add_dependency "thor", "~> 1.0"
|
|
36
|
-
spec.add_dependency "ukiryu", "~> 0.3.0"
|
|
37
36
|
end
|