vectory 0.8.0 → 0.8.1
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/.github/workflows/docs.yml +59 -0
- data/.github/workflows/links.yml +99 -0
- data/.github/workflows/rake.yml +5 -1
- data/.github/workflows/release.yml +7 -3
- data/.gitignore +5 -0
- data/.rubocop.yml +11 -3
- data/.rubocop_todo.yml +252 -0
- data/Gemfile +4 -2
- data/README.adoc +23 -1
- data/Rakefile +13 -0
- data/docs/Gemfile +18 -0
- data/docs/_config.yml +179 -0
- data/docs/features/conversion.adoc +205 -0
- data/docs/features/external-dependencies.adoc +305 -0
- data/docs/features/format-detection.adoc +173 -0
- data/docs/features/index.adoc +205 -0
- data/docs/getting-started/core-concepts.adoc +214 -0
- data/docs/getting-started/index.adoc +37 -0
- data/docs/getting-started/installation.adoc +318 -0
- data/docs/getting-started/quick-start.adoc +160 -0
- data/docs/guides/error-handling.adoc +400 -0
- data/docs/guides/index.adoc +197 -0
- data/docs/index.adoc +146 -0
- data/docs/lychee.toml +25 -0
- data/docs/reference/api.adoc +355 -0
- data/docs/reference/index.adoc +189 -0
- data/docs/understanding/architecture.adoc +277 -0
- data/docs/understanding/index.adoc +148 -0
- data/docs/understanding/inkscape-wrapper.adoc +270 -0
- data/lib/vectory/capture.rb +165 -37
- data/lib/vectory/cli.rb +2 -0
- data/lib/vectory/configuration.rb +177 -0
- data/lib/vectory/conversion/ghostscript_strategy.rb +77 -0
- data/lib/vectory/conversion/inkscape_strategy.rb +124 -0
- data/lib/vectory/conversion/strategy.rb +58 -0
- data/lib/vectory/conversion.rb +104 -0
- data/lib/vectory/datauri.rb +1 -1
- data/lib/vectory/emf.rb +17 -5
- data/lib/vectory/eps.rb +45 -3
- data/lib/vectory/errors.rb +25 -0
- data/lib/vectory/file_magic.rb +2 -2
- data/lib/vectory/ghostscript_wrapper.rb +160 -0
- data/lib/vectory/image_resize.rb +2 -2
- data/lib/vectory/inkscape_wrapper.rb +205 -0
- data/lib/vectory/pdf.rb +76 -0
- data/lib/vectory/platform.rb +105 -0
- data/lib/vectory/ps.rb +47 -3
- data/lib/vectory/svg.rb +46 -3
- data/lib/vectory/svg_document.rb +40 -24
- data/lib/vectory/system_call.rb +36 -9
- data/lib/vectory/vector.rb +3 -23
- data/lib/vectory/version.rb +1 -1
- data/lib/vectory.rb +16 -11
- metadata +34 -3
- data/lib/vectory/inkscape_converter.rb +0 -141
data/lib/vectory/pdf.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "vector"
|
|
4
|
+
require_relative "inkscape_wrapper"
|
|
5
|
+
|
|
6
|
+
module Vectory
|
|
7
|
+
class Pdf < Vector
|
|
8
|
+
attr_accessor :original_height, :original_width
|
|
9
|
+
|
|
10
|
+
def self.default_extension
|
|
11
|
+
"pdf"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.mimetype
|
|
15
|
+
"application/pdf"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_svg
|
|
19
|
+
svg = InkscapeWrapper.convert(
|
|
20
|
+
content: content,
|
|
21
|
+
input_format: :pdf,
|
|
22
|
+
output_format: :svg,
|
|
23
|
+
output_class: Svg,
|
|
24
|
+
plain: true,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# If we have original dimensions from EPS/PS, adjust the SVG
|
|
28
|
+
if original_height && original_width
|
|
29
|
+
adjusted_content = adjust_svg_dimensions(svg.content, original_width,
|
|
30
|
+
original_height)
|
|
31
|
+
svg = Svg.new(adjusted_content, svg.initial_path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
svg
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_eps
|
|
38
|
+
InkscapeWrapper.convert(
|
|
39
|
+
content: content,
|
|
40
|
+
input_format: :pdf,
|
|
41
|
+
output_format: :eps,
|
|
42
|
+
output_class: Eps,
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_ps
|
|
47
|
+
InkscapeWrapper.convert(
|
|
48
|
+
content: content,
|
|
49
|
+
input_format: :pdf,
|
|
50
|
+
output_format: :ps,
|
|
51
|
+
output_class: Ps,
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_emf
|
|
56
|
+
InkscapeWrapper.convert(
|
|
57
|
+
content: content,
|
|
58
|
+
input_format: :pdf,
|
|
59
|
+
output_format: :emf,
|
|
60
|
+
output_class: Emf,
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def adjust_svg_dimensions(svg_content, width, height)
|
|
67
|
+
# Replace width and height attributes in SVG root element
|
|
68
|
+
svg_content.gsub(/(<svg[^>]*\s)width="[^"]*"/, "\\1width=\"#{width}\"")
|
|
69
|
+
.gsub(/(<svg[^>]*\s)height="[^"]*"/, "\\1height=\"#{height}\"")
|
|
70
|
+
.gsub(/(<svg[^>]*\s)viewBox="[^"]*"/) do |match|
|
|
71
|
+
# Adjust viewBox to match new dimensions
|
|
72
|
+
"#{match.split('viewBox')[0]}viewBox=\"0 0 #{width} #{height}\""
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vectory
|
|
4
|
+
# Platform abstraction for centralized OS-specific behavior
|
|
5
|
+
#
|
|
6
|
+
# This class provides a single source of truth for platform detection
|
|
7
|
+
# and platform-specific path handling, eliminating duplicated logic
|
|
8
|
+
# across InkscapeWrapper, GhostscriptWrapper, and other classes.
|
|
9
|
+
#
|
|
10
|
+
# @example Check if running on Windows
|
|
11
|
+
# Vectory::Platform.windows? # => true or false
|
|
12
|
+
#
|
|
13
|
+
# @example Format a path for execution on the current platform
|
|
14
|
+
# Vectory::Platform.path_for_execution("C:/Program Files/Inkscape/inkscape.exe")
|
|
15
|
+
# # On Windows: "C:\\Program Files\\Inkscape\\inkscape.exe"
|
|
16
|
+
# # On Unix: "C:/Program Files/Inkscape/inkscape.exe"
|
|
17
|
+
class Platform
|
|
18
|
+
class << self
|
|
19
|
+
# Detect if running on Windows
|
|
20
|
+
#
|
|
21
|
+
# @return [Boolean] true if on Windows platform
|
|
22
|
+
def windows?
|
|
23
|
+
Gem.win_platform?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Detect if running on macOS
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean] true if on macOS platform
|
|
29
|
+
def macos?
|
|
30
|
+
RbConfig::CONFIG["host_os"].include?("darwin")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Detect if running on Linux
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean] true if on Linux platform
|
|
36
|
+
def linux?
|
|
37
|
+
RbConfig::CONFIG["host_os"].include?("linux")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Format a file path for execution on the current platform
|
|
41
|
+
#
|
|
42
|
+
# On Windows, converts forward slashes to backslashes and quotes paths with spaces.
|
|
43
|
+
# On Unix-like systems, returns the path unchanged.
|
|
44
|
+
#
|
|
45
|
+
# @param path [String] the file path to format
|
|
46
|
+
# @return [String] platform-formatted path
|
|
47
|
+
def path_for_execution(path)
|
|
48
|
+
return path unless path
|
|
49
|
+
|
|
50
|
+
formatted_path = windows? ? path.gsub("/", "\\") : path
|
|
51
|
+
|
|
52
|
+
# Quote paths with spaces to prevent shell parsing issues
|
|
53
|
+
formatted_path[/\s/] ? "\"#{formatted_path}\"" : formatted_path
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Get the PATH environment variable as an array
|
|
57
|
+
#
|
|
58
|
+
# Handles different PATH separators on Windows (;) vs Unix (:)
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<String>] array of directory paths
|
|
61
|
+
def executable_search_paths
|
|
62
|
+
@executable_search_paths ||= begin
|
|
63
|
+
path_sep = windows? ? ";" : ":"
|
|
64
|
+
(ENV["PATH"] || "").split(path_sep)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check if a command is available in the system PATH
|
|
69
|
+
#
|
|
70
|
+
# @param command [String] the command to check
|
|
71
|
+
# @return [Boolean] true if command is found in PATH
|
|
72
|
+
def command_available?(command)
|
|
73
|
+
executable_search_paths.any? do |dir|
|
|
74
|
+
executable_path = File.join(dir, command)
|
|
75
|
+
File.executable?(executable_path) && !File.directory?(executable_path)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Get the appropriate shell command extension for the platform
|
|
80
|
+
#
|
|
81
|
+
# @return [String, nil] ".exe" on Windows, nil on Unix
|
|
82
|
+
def command_extension
|
|
83
|
+
windows? ? ".exe" : nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get the default shell for the platform
|
|
87
|
+
#
|
|
88
|
+
# @return [String] shell command (e.g., "cmd.exe" on Windows, "sh" on Unix)
|
|
89
|
+
def default_shell
|
|
90
|
+
if windows?
|
|
91
|
+
"cmd.exe"
|
|
92
|
+
else
|
|
93
|
+
"sh"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Reset cached values (primarily for testing)
|
|
98
|
+
#
|
|
99
|
+
# @api private
|
|
100
|
+
def reset_cache
|
|
101
|
+
@executable_search_paths = nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
data/lib/vectory/ps.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "ghostscript_wrapper"
|
|
4
|
+
require_relative "pdf"
|
|
5
|
+
|
|
3
6
|
module Vectory
|
|
4
7
|
class Ps < Vector
|
|
5
8
|
def self.default_extension
|
|
@@ -20,15 +23,56 @@ module Vectory
|
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
def to_eps
|
|
23
|
-
|
|
26
|
+
to_pdf.to_eps
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
def to_emf
|
|
27
|
-
|
|
30
|
+
to_pdf.to_emf
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
def to_svg
|
|
31
|
-
|
|
34
|
+
to_pdf.to_svg
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_pdf
|
|
38
|
+
pdf_content = GhostscriptWrapper.convert(content, eps_crop: false)
|
|
39
|
+
pdf = Pdf.new(pdf_content)
|
|
40
|
+
# Pass original BoundingBox dimensions to preserve them in conversions
|
|
41
|
+
bbox = parse_bounding_box
|
|
42
|
+
if bbox
|
|
43
|
+
pdf.original_width = bbox[:urx] - bbox[:llx]
|
|
44
|
+
pdf.original_height = bbox[:ury] - bbox[:lly]
|
|
45
|
+
end
|
|
46
|
+
pdf
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def height
|
|
50
|
+
bbox = parse_bounding_box
|
|
51
|
+
return super unless bbox
|
|
52
|
+
|
|
53
|
+
bbox[:ury] - bbox[:lly]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def width
|
|
57
|
+
bbox = parse_bounding_box
|
|
58
|
+
return super unless bbox
|
|
59
|
+
|
|
60
|
+
bbox[:urx] - bbox[:llx]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def parse_bounding_box
|
|
66
|
+
# Look for %%BoundingBox: llx lly urx ury
|
|
67
|
+
match = content.match(/^%%BoundingBox:\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)/m)
|
|
68
|
+
return nil unless match
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
llx: match[1].to_f,
|
|
72
|
+
lly: match[2].to_f,
|
|
73
|
+
urx: match[3].to_f,
|
|
74
|
+
ury: match[4].to_f,
|
|
75
|
+
}
|
|
32
76
|
end
|
|
33
77
|
end
|
|
34
78
|
end
|
data/lib/vectory/svg.rb
CHANGED
|
@@ -32,15 +32,58 @@ module Vectory
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def to_emf
|
|
35
|
-
|
|
35
|
+
InkscapeWrapper.convert(
|
|
36
|
+
content: content,
|
|
37
|
+
input_format: :svg,
|
|
38
|
+
output_format: :emf,
|
|
39
|
+
output_class: Emf,
|
|
40
|
+
)
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
def to_eps
|
|
39
|
-
|
|
44
|
+
InkscapeWrapper.convert(
|
|
45
|
+
content: content,
|
|
46
|
+
input_format: :svg,
|
|
47
|
+
output_format: :eps,
|
|
48
|
+
output_class: Eps,
|
|
49
|
+
)
|
|
40
50
|
end
|
|
41
51
|
|
|
42
52
|
def to_ps
|
|
43
|
-
|
|
53
|
+
InkscapeWrapper.convert(
|
|
54
|
+
content: content,
|
|
55
|
+
input_format: :svg,
|
|
56
|
+
output_format: :ps,
|
|
57
|
+
output_class: Ps,
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def height
|
|
62
|
+
# Try to read height from SVG attributes first
|
|
63
|
+
doc = Nokogiri::XML(content)
|
|
64
|
+
svg_element = doc.at_xpath("//svg:svg",
|
|
65
|
+
"svg" => SVG_NS) || doc.at_xpath("//svg")
|
|
66
|
+
|
|
67
|
+
if svg_element && svg_element["height"]
|
|
68
|
+
svg_element["height"].to_f.round
|
|
69
|
+
else
|
|
70
|
+
# Fall back to Inkscape query if no height attribute
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def width
|
|
76
|
+
# Try to read width from SVG attributes first
|
|
77
|
+
doc = Nokogiri::XML(content)
|
|
78
|
+
svg_element = doc.at_xpath("//svg:svg",
|
|
79
|
+
"svg" => SVG_NS) || doc.at_xpath("//svg")
|
|
80
|
+
|
|
81
|
+
if svg_element && svg_element["width"]
|
|
82
|
+
svg_element["width"].to_f.round
|
|
83
|
+
else
|
|
84
|
+
# Fall back to Inkscape query if no width attribute
|
|
85
|
+
super
|
|
86
|
+
end
|
|
44
87
|
end
|
|
45
88
|
|
|
46
89
|
private
|
data/lib/vectory/svg_document.rb
CHANGED
|
@@ -4,6 +4,44 @@ module Vectory
|
|
|
4
4
|
class SvgDocument
|
|
5
5
|
SVG_NS = "http://www.w3.org/2000/svg".freeze
|
|
6
6
|
|
|
7
|
+
class << self
|
|
8
|
+
# Update instances of id in style statements in a nokogiri document
|
|
9
|
+
def update_ids_css(document, ids, suffix)
|
|
10
|
+
suffix = suffix.is_a?(Integer) ? sprintf("%09d", suffix) : suffix
|
|
11
|
+
document.xpath(".//m:style", "m" => SVG_NS).each do |s|
|
|
12
|
+
c = s.children.to_xml
|
|
13
|
+
s.children = update_ids_css_string(c, ids, suffix)
|
|
14
|
+
end
|
|
15
|
+
document.xpath(".//*[@style]").each do |s|
|
|
16
|
+
s["style"] = update_ids_css_string(s["style"], ids, suffix)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Update instances of id in style statements in the string style
|
|
21
|
+
def update_ids_css_string(style, ids, suffix)
|
|
22
|
+
ids.each do |i|
|
|
23
|
+
style = style.gsub(%r[##{i}\b],
|
|
24
|
+
sprintf("#%<id>s_%<suffix>s", id: i,
|
|
25
|
+
suffix: suffix))
|
|
26
|
+
.gsub(%r(\[id\s*=\s*['"]?#{i}['"]?\]),
|
|
27
|
+
sprintf("[id='%<id>s_%<suffix>s']", id: i,
|
|
28
|
+
suffix: suffix))
|
|
29
|
+
end
|
|
30
|
+
style
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update_ids_attrs(document, ids, suffix)
|
|
34
|
+
suffix = suffix.is_a?(Integer) ? sprintf("%09d", suffix) : suffix
|
|
35
|
+
document.xpath(". | .//*[@*]").each do |a|
|
|
36
|
+
a.attribute_nodes.each do |x|
|
|
37
|
+
val = x.value.sub(/^#/, "")
|
|
38
|
+
ids.include?(val) and x.value += "_#{suffix}"
|
|
39
|
+
x.value = x.value.sub(%r{url\(#([^()]+)\)}, "url(#\\1_#{suffix})")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
7
45
|
def initialize(content)
|
|
8
46
|
@document = Nokogiri::XML(content)
|
|
9
47
|
end
|
|
@@ -33,8 +71,8 @@ module Vectory
|
|
|
33
71
|
ids = collect_ids
|
|
34
72
|
return if ids.empty?
|
|
35
73
|
|
|
36
|
-
update_ids_attrs(ids, suffix)
|
|
37
|
-
update_ids_css(ids, suffix)
|
|
74
|
+
self.class.update_ids_attrs(@document.root, ids, suffix)
|
|
75
|
+
self.class.update_ids_css(@document.root, ids, suffix)
|
|
38
76
|
|
|
39
77
|
self
|
|
40
78
|
end
|
|
@@ -50,27 +88,5 @@ module Vectory
|
|
|
50
88
|
def collect_ids
|
|
51
89
|
@document.xpath("./@id | .//@id").map(&:value)
|
|
52
90
|
end
|
|
53
|
-
|
|
54
|
-
def update_ids_attrs(ids, suffix)
|
|
55
|
-
@document.xpath(". | .//*[@*]").each do |a|
|
|
56
|
-
a.attribute_nodes.each do |x|
|
|
57
|
-
ids.include?(x.value) and x.value += sprintf("_%09d", suffix)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def update_ids_css(ids, suffix)
|
|
63
|
-
@document.xpath("//m:style", "m" => SVG_NS).each do |s|
|
|
64
|
-
c = s.children.to_xml
|
|
65
|
-
ids.each do |i|
|
|
66
|
-
c = c.gsub(%r[##{i}\b],
|
|
67
|
-
sprintf("#%<id>s_%<suffix>09d", id: i, suffix: suffix))
|
|
68
|
-
.gsub(%r(\[id\s*=\s*['"]?#{i}['"]?\]),
|
|
69
|
-
sprintf("[id='%<id>s_%<suffix>09d']", id: i, suffix: suffix))
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
s.children = c
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
91
|
end
|
|
76
92
|
end
|
data/lib/vectory/system_call.rb
CHANGED
|
@@ -7,15 +7,16 @@ module Vectory
|
|
|
7
7
|
|
|
8
8
|
attr_reader :status, :stdout, :stderr, :cmd
|
|
9
9
|
|
|
10
|
-
def initialize(cmd, timeout = TIMEOUT)
|
|
10
|
+
def initialize(cmd, timeout = TIMEOUT, env: {})
|
|
11
11
|
@cmd = cmd
|
|
12
12
|
@timeout = timeout
|
|
13
|
+
@env = env
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def call
|
|
16
17
|
log_cmd(@cmd)
|
|
17
18
|
|
|
18
|
-
execute(@cmd)
|
|
19
|
+
execute(@cmd, @env)
|
|
19
20
|
|
|
20
21
|
log_result
|
|
21
22
|
|
|
@@ -30,14 +31,26 @@ module Vectory
|
|
|
30
31
|
Vectory.ui.debug("Cmd: '#{cmd}'")
|
|
31
32
|
end
|
|
32
33
|
|
|
33
|
-
def execute(cmd)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
def execute(cmd, env)
|
|
35
|
+
# Build spawn options with environment variables and timeout settings
|
|
36
|
+
spawn_opts = {}
|
|
37
|
+
spawn_opts.merge!(env) if env.any?
|
|
38
|
+
spawn_opts[:timeout] = @timeout
|
|
39
|
+
spawn_opts[:signal] = :TERM # Use TERM on Unix for graceful shutdown
|
|
40
|
+
spawn_opts[:kill_after] = 2
|
|
41
|
+
|
|
42
|
+
# Pass command and options directly (without splatting)
|
|
43
|
+
# Capture.with_timeout expects: cmd, options_hash
|
|
44
|
+
# It will handle extracting the options correctly
|
|
45
|
+
result = if cmd.is_a?(Array)
|
|
46
|
+
Capture.with_timeout(*cmd, spawn_opts)
|
|
47
|
+
else
|
|
48
|
+
Capture.with_timeout(cmd, spawn_opts)
|
|
49
|
+
end
|
|
50
|
+
@stdout = result[:stdout] || ""
|
|
51
|
+
@stderr = result[:stderr] || ""
|
|
40
52
|
@status = result[:status]
|
|
53
|
+
@timed_out = result[:timeout]
|
|
41
54
|
rescue Errno::ENOENT => e
|
|
42
55
|
raise SystemCallError, e.inspect
|
|
43
56
|
end
|
|
@@ -49,6 +62,20 @@ module Vectory
|
|
|
49
62
|
end
|
|
50
63
|
|
|
51
64
|
def raise_error
|
|
65
|
+
if @timed_out
|
|
66
|
+
raise SystemCallError,
|
|
67
|
+
"Command timed out after #{@timeout} seconds: #{@cmd},\n " \
|
|
68
|
+
"stdout: '#{@stdout.strip}',\n " \
|
|
69
|
+
"stderr: '#{@stderr.strip}'"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if @status.nil?
|
|
73
|
+
raise SystemCallError,
|
|
74
|
+
"Failed to run #{@cmd} (no status available),\n " \
|
|
75
|
+
"stdout: '#{@stdout.strip}',\n " \
|
|
76
|
+
"stderr: '#{@stderr.strip}'"
|
|
77
|
+
end
|
|
78
|
+
|
|
52
79
|
raise SystemCallError,
|
|
53
80
|
"Failed to run #{@cmd},\n " \
|
|
54
81
|
"status: #{@status.exitstatus},\n " \
|
data/lib/vectory/vector.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "tempfile"
|
|
4
|
-
require_relative "
|
|
4
|
+
require_relative "inkscape_wrapper"
|
|
5
5
|
|
|
6
6
|
module Vectory
|
|
7
7
|
class Vector < Image
|
|
@@ -47,28 +47,17 @@ module Vectory
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def height
|
|
50
|
-
|
|
50
|
+
InkscapeWrapper.instance.height(content, self.class.default_extension)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def width
|
|
54
|
-
|
|
54
|
+
InkscapeWrapper.instance.width(content, self.class.default_extension)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def to_uri
|
|
58
58
|
Datauri.from_vector(self)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def convert_with_inkscape(inkscape_options, target_class)
|
|
62
|
-
with_file(self.class.default_extension) do |input_path|
|
|
63
|
-
output_extension = target_class.default_extension
|
|
64
|
-
output_path = InkscapeConverter.instance.convert(input_path,
|
|
65
|
-
output_extension,
|
|
66
|
-
inkscape_options)
|
|
67
|
-
|
|
68
|
-
target_class.from_path(output_path)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
61
|
def write(path = nil)
|
|
73
62
|
target_path = path || @path || tmp_path
|
|
74
63
|
File.binwrite(target_path, content)
|
|
@@ -83,15 +72,6 @@ module Vectory
|
|
|
83
72
|
|
|
84
73
|
private
|
|
85
74
|
|
|
86
|
-
def with_file(input_extension)
|
|
87
|
-
Dir.mktmpdir do |dir|
|
|
88
|
-
input_path = File.join(dir, "image.#{input_extension}")
|
|
89
|
-
File.binwrite(input_path, content)
|
|
90
|
-
|
|
91
|
-
yield input_path
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
75
|
def tmp_path
|
|
96
76
|
dir = Dir.mktmpdir
|
|
97
77
|
filename = "image.#{self.class.default_extension}"
|
data/lib/vectory/version.rb
CHANGED
data/lib/vectory.rb
CHANGED
|
@@ -3,10 +3,21 @@
|
|
|
3
3
|
require "logger"
|
|
4
4
|
require_relative "vectory/version"
|
|
5
5
|
require_relative "vectory/utils"
|
|
6
|
+
|
|
7
|
+
module Vectory
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require_relative "vectory/errors"
|
|
12
|
+
require_relative "vectory/platform"
|
|
13
|
+
require_relative "vectory/configuration"
|
|
14
|
+
require_relative "vectory/conversion"
|
|
6
15
|
require_relative "vectory/image"
|
|
7
16
|
require_relative "vectory/image_resize"
|
|
8
17
|
require_relative "vectory/datauri"
|
|
9
18
|
require_relative "vectory/vector"
|
|
19
|
+
require_relative "vectory/ghostscript_wrapper"
|
|
20
|
+
require_relative "vectory/pdf"
|
|
10
21
|
require_relative "vectory/eps"
|
|
11
22
|
require_relative "vectory/ps"
|
|
12
23
|
require_relative "vectory/emf"
|
|
@@ -14,16 +25,8 @@ require_relative "vectory/svg"
|
|
|
14
25
|
require_relative "vectory/svg_mapping"
|
|
15
26
|
|
|
16
27
|
module Vectory
|
|
17
|
-
class Error < StandardError; end
|
|
18
|
-
|
|
19
|
-
class ConversionError < Error; end
|
|
20
|
-
|
|
21
28
|
class SystemCallError < Error; end
|
|
22
29
|
|
|
23
|
-
class InkscapeNotFoundError < Error; end
|
|
24
|
-
|
|
25
|
-
class InkscapeQueryError < Error; end
|
|
26
|
-
|
|
27
30
|
class NotImplementedError < Error; end
|
|
28
31
|
|
|
29
32
|
class NotWrittenToDiskError < Error; end
|
|
@@ -31,9 +34,11 @@ module Vectory
|
|
|
31
34
|
class ParsingError < Error; end
|
|
32
35
|
|
|
33
36
|
def self.ui
|
|
34
|
-
@ui ||= Logger.new(
|
|
35
|
-
logger.level = ENV[
|
|
36
|
-
logger.formatter = proc { |
|
|
37
|
+
@ui ||= Logger.new($stdout).tap do |logger|
|
|
38
|
+
logger.level = ENV["VECTORY_LOG"] || Logger::WARN
|
|
39
|
+
logger.formatter = proc { |_severity, _datetime, _progname, msg|
|
|
40
|
+
"#{msg}\n"
|
|
41
|
+
}
|
|
37
42
|
end
|
|
38
43
|
end
|
|
39
44
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vectory
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -105,25 +105,56 @@ extensions: []
|
|
|
105
105
|
extra_rdoc_files: []
|
|
106
106
|
files:
|
|
107
107
|
- ".github/workflows/automerge.yml"
|
|
108
|
+
- ".github/workflows/docs.yml"
|
|
109
|
+
- ".github/workflows/links.yml"
|
|
108
110
|
- ".github/workflows/rake.yml"
|
|
109
111
|
- ".github/workflows/release.yml"
|
|
110
112
|
- ".gitignore"
|
|
111
113
|
- ".hound.yml"
|
|
112
114
|
- ".rubocop.yml"
|
|
115
|
+
- ".rubocop_todo.yml"
|
|
113
116
|
- Gemfile
|
|
114
117
|
- README.adoc
|
|
115
118
|
- Rakefile
|
|
119
|
+
- docs/Gemfile
|
|
120
|
+
- docs/_config.yml
|
|
121
|
+
- docs/features/conversion.adoc
|
|
122
|
+
- docs/features/external-dependencies.adoc
|
|
123
|
+
- docs/features/format-detection.adoc
|
|
124
|
+
- docs/features/index.adoc
|
|
125
|
+
- docs/getting-started/core-concepts.adoc
|
|
126
|
+
- docs/getting-started/index.adoc
|
|
127
|
+
- docs/getting-started/installation.adoc
|
|
128
|
+
- docs/getting-started/quick-start.adoc
|
|
129
|
+
- docs/guides/error-handling.adoc
|
|
130
|
+
- docs/guides/index.adoc
|
|
131
|
+
- docs/index.adoc
|
|
132
|
+
- docs/lychee.toml
|
|
133
|
+
- docs/reference/api.adoc
|
|
134
|
+
- docs/reference/index.adoc
|
|
135
|
+
- docs/understanding/architecture.adoc
|
|
136
|
+
- docs/understanding/index.adoc
|
|
137
|
+
- docs/understanding/inkscape-wrapper.adoc
|
|
116
138
|
- exe/vectory
|
|
117
139
|
- lib/vectory.rb
|
|
118
140
|
- lib/vectory/capture.rb
|
|
119
141
|
- lib/vectory/cli.rb
|
|
142
|
+
- lib/vectory/configuration.rb
|
|
143
|
+
- lib/vectory/conversion.rb
|
|
144
|
+
- lib/vectory/conversion/ghostscript_strategy.rb
|
|
145
|
+
- lib/vectory/conversion/inkscape_strategy.rb
|
|
146
|
+
- lib/vectory/conversion/strategy.rb
|
|
120
147
|
- lib/vectory/datauri.rb
|
|
121
148
|
- lib/vectory/emf.rb
|
|
122
149
|
- lib/vectory/eps.rb
|
|
150
|
+
- lib/vectory/errors.rb
|
|
123
151
|
- lib/vectory/file_magic.rb
|
|
152
|
+
- lib/vectory/ghostscript_wrapper.rb
|
|
124
153
|
- lib/vectory/image.rb
|
|
125
154
|
- lib/vectory/image_resize.rb
|
|
126
|
-
- lib/vectory/
|
|
155
|
+
- lib/vectory/inkscape_wrapper.rb
|
|
156
|
+
- lib/vectory/pdf.rb
|
|
157
|
+
- lib/vectory/platform.rb
|
|
127
158
|
- lib/vectory/ps.rb
|
|
128
159
|
- lib/vectory/svg.rb
|
|
129
160
|
- lib/vectory/svg_document.rb
|