vectory 0.10.1 → 0.12.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/README.adoc +4 -6
- data/docs/features/conversion.adoc +11 -10
- data/docs/features/index.adoc +14 -7
- data/docs/getting-started/core-concepts.adoc +2 -1
- 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 +5 -15
- data/lib/vectory/errors.rb +0 -14
- data/lib/vectory/ps.rb +5 -15
- data/lib/vectory/svg.rb +25 -34
- 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 +1 -1
- metadata +9 -16
- 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
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vectory
|
|
4
|
-
module Conversion
|
|
5
|
-
# Ghostscript-based conversion strategy
|
|
6
|
-
#
|
|
7
|
-
# Handles PS/EPS → PDF conversions using Ghostscript.
|
|
8
|
-
# Ghostscript is used for its accurate BoundingBox preservation.
|
|
9
|
-
#
|
|
10
|
-
# @see https://www.ghostscript.com/
|
|
11
|
-
class GhostscriptStrategy < Strategy
|
|
12
|
-
# Ghostscript supports PS/EPS → PDF conversions
|
|
13
|
-
SUPPORTED_CONVERSIONS = [
|
|
14
|
-
%i[ps pdf],
|
|
15
|
-
%i[eps pdf],
|
|
16
|
-
].freeze
|
|
17
|
-
|
|
18
|
-
# Convert PS/EPS content to PDF using Ghostscript
|
|
19
|
-
#
|
|
20
|
-
# @param content [String] the PS/EPS content to convert
|
|
21
|
-
# @param input_format [Symbol] the input format (:ps or :eps)
|
|
22
|
-
# @param output_format [Symbol] the output format (must be :pdf)
|
|
23
|
-
# @param options [Hash] additional options
|
|
24
|
-
# @option options [Boolean] :eps_crop use EPSCrop for better BoundingBox handling
|
|
25
|
-
# @return [String] the PDF content
|
|
26
|
-
# @raise [Vectory::GhostscriptNotFoundError] if Ghostscript is not available
|
|
27
|
-
# @raise [Vectory::ConversionError] if conversion fails
|
|
28
|
-
def convert(content, input_format:, output_format:, **options)
|
|
29
|
-
unless output_format == :pdf
|
|
30
|
-
raise ArgumentError,
|
|
31
|
-
"Ghostscript only supports PDF output, got: #{output_format}"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
unless %i[ps eps].include?(input_format)
|
|
35
|
-
raise ArgumentError,
|
|
36
|
-
"Ghostscript only supports PS/EPS input, got: #{input_format}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
GhostscriptWrapper.convert(content,
|
|
40
|
-
eps_crop: options[:eps_crop] || false)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Check if this conversion is supported
|
|
44
|
-
#
|
|
45
|
-
# @param input_format [Symbol] the input format
|
|
46
|
-
# @param output_format [Symbol] the output format
|
|
47
|
-
# @return [Boolean] true if Ghostscript supports this conversion
|
|
48
|
-
def supports?(input_format, output_format)
|
|
49
|
-
SUPPORTED_CONVERSIONS.include?([input_format, output_format])
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Get supported conversions
|
|
53
|
-
#
|
|
54
|
-
# @return [Array<Array<Symbol>>] array of [input, output] format pairs
|
|
55
|
-
def supported_conversions
|
|
56
|
-
SUPPORTED_CONVERSIONS
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Check if Ghostscript is available
|
|
60
|
-
#
|
|
61
|
-
# @return [Boolean] true if Ghostscript can be found
|
|
62
|
-
def available?
|
|
63
|
-
GhostscriptWrapper.available?
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Get the tool name
|
|
67
|
-
#
|
|
68
|
-
# @return [String] "ghostscript"
|
|
69
|
-
def tool_name
|
|
70
|
-
"ghostscript"
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vectory
|
|
4
|
-
module Conversion
|
|
5
|
-
# Inkscape-based conversion strategy
|
|
6
|
-
#
|
|
7
|
-
# Handles conversions using Inkscape, including:
|
|
8
|
-
# - SVG ↔ EPS
|
|
9
|
-
# - SVG ↔ PS
|
|
10
|
-
# - SVG ↔ EMF
|
|
11
|
-
# - SVG ↔ PDF
|
|
12
|
-
# - EPS/PS → PDF
|
|
13
|
-
#
|
|
14
|
-
# @see https://inkscape.org/
|
|
15
|
-
class InkscapeStrategy < Strategy
|
|
16
|
-
# Inkscape supports bidirectional conversion with SVG as source/target
|
|
17
|
-
SUPPORTED_CONVERSIONS = [
|
|
18
|
-
%i[svg eps],
|
|
19
|
-
%i[svg ps],
|
|
20
|
-
%i[svg emf],
|
|
21
|
-
%i[svg pdf],
|
|
22
|
-
%i[eps svg],
|
|
23
|
-
%i[eps pdf],
|
|
24
|
-
%i[ps svg],
|
|
25
|
-
%i[ps pdf],
|
|
26
|
-
%i[pdf svg],
|
|
27
|
-
%i[emf svg],
|
|
28
|
-
].freeze
|
|
29
|
-
|
|
30
|
-
# Convert content using Inkscape
|
|
31
|
-
#
|
|
32
|
-
# @param content [String] the input content to convert
|
|
33
|
-
# @param input_format [Symbol] the input format
|
|
34
|
-
# @param output_format [Symbol] the output format
|
|
35
|
-
# @param options [Hash] additional options
|
|
36
|
-
# @option options [Boolean] :plain export plain SVG (for SVG output)
|
|
37
|
-
# @option options [Class] :output_class the class to instantiate with result
|
|
38
|
-
# @return [Vectory::Vector] the converted vector object
|
|
39
|
-
# @raise [Vectory::InkscapeNotFoundError] if Inkscape is not available
|
|
40
|
-
def convert(content, input_format:, output_format:, **options)
|
|
41
|
-
output_class = options.fetch(:output_class) do
|
|
42
|
-
format_class(output_format)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
InkscapeWrapper.convert(
|
|
46
|
-
content: content,
|
|
47
|
-
input_format: input_format,
|
|
48
|
-
output_format: output_format,
|
|
49
|
-
output_class: output_class,
|
|
50
|
-
plain: options[:plain] || false,
|
|
51
|
-
)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Check if this conversion is supported
|
|
55
|
-
#
|
|
56
|
-
# @param input_format [Symbol] the input format
|
|
57
|
-
# @param output_format [Symbol] the output format
|
|
58
|
-
# @return [Boolean] true if Inkscape supports this conversion
|
|
59
|
-
def supports?(input_format, output_format)
|
|
60
|
-
SUPPORTED_CONVERSIONS.include?([input_format, output_format])
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Get supported conversions
|
|
64
|
-
#
|
|
65
|
-
# @return [Array<Array<Symbol>>] array of [input, output] format pairs
|
|
66
|
-
def supported_conversions
|
|
67
|
-
SUPPORTED_CONVERSIONS
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Check if Inkscape is available
|
|
71
|
-
#
|
|
72
|
-
# @return [Boolean] true if Inkscape can be found in PATH
|
|
73
|
-
def available?
|
|
74
|
-
InkscapeWrapper.instance.send(:inkscape_path)
|
|
75
|
-
true
|
|
76
|
-
rescue Vectory::InkscapeNotFoundError
|
|
77
|
-
false
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Get the tool name
|
|
81
|
-
#
|
|
82
|
-
# @return [String] "inkscape"
|
|
83
|
-
def tool_name
|
|
84
|
-
"inkscape"
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Query the width of content
|
|
88
|
-
#
|
|
89
|
-
# @param content [String] the vector content
|
|
90
|
-
# @param format [Symbol] the format of the content
|
|
91
|
-
# @return [Integer] the width in pixels
|
|
92
|
-
def width(content, format)
|
|
93
|
-
InkscapeWrapper.instance.width(content, format)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Query the height of content
|
|
97
|
-
#
|
|
98
|
-
# @param content [String] the vector content
|
|
99
|
-
# @param format [Symbol] the format of the content
|
|
100
|
-
# @return [Integer] the height in pixels
|
|
101
|
-
def height(content, format)
|
|
102
|
-
InkscapeWrapper.instance.height(content, format)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
private
|
|
106
|
-
|
|
107
|
-
# Get the Vectory class for a format
|
|
108
|
-
def format_class(format)
|
|
109
|
-
case format
|
|
110
|
-
when :svg then Vectory::Svg
|
|
111
|
-
when :eps then Vectory::Eps
|
|
112
|
-
when :ps then Vectory::Ps
|
|
113
|
-
when :emf then Vectory::Emf
|
|
114
|
-
when :pdf then Vectory::Pdf
|
|
115
|
-
else
|
|
116
|
-
raise ArgumentError, "Unsupported format: #{format}"
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vectory
|
|
4
|
-
module Conversion
|
|
5
|
-
# Base class for conversion strategies
|
|
6
|
-
#
|
|
7
|
-
# Conversion strategies encapsulate the logic for converting between
|
|
8
|
-
# different vector formats using external tools (Inkscape, Ghostscript, etc.)
|
|
9
|
-
#
|
|
10
|
-
# @abstract Subclasses must implement the {#convert} method
|
|
11
|
-
class Strategy
|
|
12
|
-
# Convert content from one format to another
|
|
13
|
-
#
|
|
14
|
-
# @param content [String] the input content to convert
|
|
15
|
-
# @param input_format [Symbol] the input format (e.g., :svg, :eps, :ps)
|
|
16
|
-
# @param output_format [Symbol] the desired output format (e.g., :svg, :pdf, :eps)
|
|
17
|
-
# @param options [Hash] additional options for the conversion
|
|
18
|
-
# @return [String] the converted content
|
|
19
|
-
# @raise [Vectory::ConversionError] if conversion fails
|
|
20
|
-
# @abstract
|
|
21
|
-
def convert(content, input_format:, output_format:, **options)
|
|
22
|
-
raise NotImplementedError,
|
|
23
|
-
"#{self.class} must implement #convert method"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Check if this strategy supports the given conversion
|
|
27
|
-
#
|
|
28
|
-
# @param input_format [Symbol] the input format
|
|
29
|
-
# @param output_format [Symbol] the output format
|
|
30
|
-
# @return [Boolean] true if this strategy supports the conversion
|
|
31
|
-
def supports?(input_format, output_format)
|
|
32
|
-
supported_conversions.include?([input_format, output_format])
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Get the list of conversions this strategy supports
|
|
36
|
-
#
|
|
37
|
-
# @return [Array<Array<Symbol>>] array of [input, output] format pairs
|
|
38
|
-
def supported_conversions
|
|
39
|
-
[]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Check if the required external tool is available
|
|
43
|
-
#
|
|
44
|
-
# @return [Boolean] true if the tool is available
|
|
45
|
-
def available?
|
|
46
|
-
raise NotImplementedError,
|
|
47
|
-
"#{self.class} must implement #available? method"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Get the name of the external tool used by this strategy
|
|
51
|
-
#
|
|
52
|
-
# @return [String] the tool name
|
|
53
|
-
def tool_name
|
|
54
|
-
self.class.name.split("::").last.gsub(/Strategy$/, "").downcase
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
data/lib/vectory/conversion.rb
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vectory
|
|
4
|
-
# Conversion module provides strategy-based conversion interface
|
|
5
|
-
#
|
|
6
|
-
# This module encapsulates different conversion strategies for converting
|
|
7
|
-
# between vector formats using external tools like Inkscape and Ghostscript.
|
|
8
|
-
#
|
|
9
|
-
# @example Convert SVG to EPS using Inkscape
|
|
10
|
-
# Vectory::Conversion.convert(svg_content, from: :svg, to: :eps)
|
|
11
|
-
#
|
|
12
|
-
# @example Get available strategies for a conversion
|
|
13
|
-
# Vectory::Conversion.strategies_for(:svg, :eps)
|
|
14
|
-
module Conversion
|
|
15
|
-
# Autoload strategy classes
|
|
16
|
-
autoload :Strategy, "vectory/conversion/strategy"
|
|
17
|
-
autoload :InkscapeStrategy, "vectory/conversion/inkscape_strategy"
|
|
18
|
-
autoload :GhostscriptStrategy, "vectory/conversion/ghostscript_strategy"
|
|
19
|
-
class << self
|
|
20
|
-
# Convert content from one format to another
|
|
21
|
-
#
|
|
22
|
-
# Automatically selects the appropriate strategy based on the input/output formats.
|
|
23
|
-
#
|
|
24
|
-
# @param content [String] the content to convert
|
|
25
|
-
# @param from [Symbol] the input format
|
|
26
|
-
# @param to [Symbol] the output format
|
|
27
|
-
# @param options [Hash] additional options passed to the strategy
|
|
28
|
-
# @return [Vectory::Vector, String] the converted result
|
|
29
|
-
# @raise [Vectory::ConversionError] if no strategy supports the conversion
|
|
30
|
-
def convert(content, from:, to:, **options)
|
|
31
|
-
strategy = find_strategy(from, to)
|
|
32
|
-
|
|
33
|
-
unless strategy
|
|
34
|
-
supported = supported_conversions.map do |a, b|
|
|
35
|
-
"#{a} → #{b}"
|
|
36
|
-
end.join(", ")
|
|
37
|
-
raise Vectory::ConversionError,
|
|
38
|
-
"No strategy found for #{from} → #{to} conversion. " \
|
|
39
|
-
"Supported: #{supported}"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
strategy.convert(content, input_format: from, output_format: to,
|
|
43
|
-
**options)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Get all available strategies
|
|
47
|
-
#
|
|
48
|
-
# @return [Array<Vectory::Conversion::Strategy>] all registered strategies
|
|
49
|
-
def strategies
|
|
50
|
-
@strategies ||= [
|
|
51
|
-
InkscapeStrategy.new,
|
|
52
|
-
GhostscriptStrategy.new,
|
|
53
|
-
]
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Get strategies that support a specific conversion
|
|
57
|
-
#
|
|
58
|
-
# @param input_format [Symbol] the input format
|
|
59
|
-
# @param output_format [Symbol] the output format
|
|
60
|
-
# @return [Array<Vectory::Conversion::Strategy>] matching strategies
|
|
61
|
-
def strategies_for(input_format, output_format)
|
|
62
|
-
strategies.select { |s| s.supports?(input_format, output_format) }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Check if a conversion is supported
|
|
66
|
-
#
|
|
67
|
-
# @param input_format [Symbol] the input format
|
|
68
|
-
# @param output_format [Symbol] the output format
|
|
69
|
-
# @return [Boolean] true if any strategy supports this conversion
|
|
70
|
-
def supports?(input_format, output_format)
|
|
71
|
-
strategies_for(input_format, output_format).any?
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Get all supported conversions
|
|
75
|
-
#
|
|
76
|
-
# @return [Array<Array<Symbol>>] array of [input, output] format pairs
|
|
77
|
-
def supported_conversions
|
|
78
|
-
@supported_conversions ||= strategies.flat_map(&:supported_conversions).uniq
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Check if a specific tool is available
|
|
82
|
-
#
|
|
83
|
-
# @param tool [Symbol, String] the tool name (:inkscape, :ghostscript, etc.)
|
|
84
|
-
# @return [Boolean] true if the tool is available
|
|
85
|
-
def tool_available?(tool)
|
|
86
|
-
strategy = strategies.find { |s| s.tool_name == tool.to_s.downcase }
|
|
87
|
-
strategy&.available? || false
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
private
|
|
91
|
-
|
|
92
|
-
# Find a strategy for the given conversion
|
|
93
|
-
#
|
|
94
|
-
# @param input_format [Symbol] the input format
|
|
95
|
-
# @param output_format [Symbol] the output format
|
|
96
|
-
# @return [Vectory::Conversion::Strategy, nil] the strategy or nil if not found
|
|
97
|
-
def find_strategy(input_format, output_format)
|
|
98
|
-
strategies.find do |strategy|
|
|
99
|
-
strategy.supports?(input_format, output_format) && strategy.available?
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "tempfile"
|
|
4
|
-
require "fileutils"
|
|
5
|
-
require "ukiryu"
|
|
6
|
-
|
|
7
|
-
module Vectory
|
|
8
|
-
# GhostscriptWrapper converts PS and EPS files to PDF using Ghostscript
|
|
9
|
-
#
|
|
10
|
-
# Uses Ukiryu for platform-adaptive command execution.
|
|
11
|
-
class GhostscriptWrapper
|
|
12
|
-
SUPPORTED_INPUT_FORMATS = %w[ps eps].freeze
|
|
13
|
-
|
|
14
|
-
class << self
|
|
15
|
-
def available?
|
|
16
|
-
ghostscript_tool
|
|
17
|
-
true
|
|
18
|
-
rescue GhostscriptNotFoundError
|
|
19
|
-
false
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def version
|
|
23
|
-
return nil unless available?
|
|
24
|
-
|
|
25
|
-
tool = ghostscript_tool
|
|
26
|
-
tool.version
|
|
27
|
-
rescue StandardError
|
|
28
|
-
nil
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def convert(content, options = {})
|
|
32
|
-
raise GhostscriptNotFoundError unless available?
|
|
33
|
-
|
|
34
|
-
eps_crop = options.fetch(:eps_crop, false)
|
|
35
|
-
input_ext = eps_crop ? ".eps" : ".ps"
|
|
36
|
-
|
|
37
|
-
# Create temporary input file
|
|
38
|
-
input_file = Tempfile.new(["gs_input", input_ext])
|
|
39
|
-
output_file = Tempfile.new(["gs_output", ".pdf"])
|
|
40
|
-
|
|
41
|
-
begin
|
|
42
|
-
# Write content and close the input file so GhostScript can read it
|
|
43
|
-
input_file.binmode
|
|
44
|
-
input_file.write(content)
|
|
45
|
-
input_file.flush
|
|
46
|
-
input_file.close
|
|
47
|
-
|
|
48
|
-
# Close output file so GhostScript can write to it
|
|
49
|
-
output_file.close
|
|
50
|
-
|
|
51
|
-
# Get the tool and execute
|
|
52
|
-
tool = ghostscript_tool
|
|
53
|
-
params = build_convert_params(input_file.path, output_file.path,
|
|
54
|
-
eps_crop: eps_crop)
|
|
55
|
-
|
|
56
|
-
result = tool.execute(:convert,
|
|
57
|
-
execution_timeout: Configuration.instance.timeout,
|
|
58
|
-
**params)
|
|
59
|
-
|
|
60
|
-
unless result.success?
|
|
61
|
-
raise ConversionError,
|
|
62
|
-
"GhostScript conversion failed. " \
|
|
63
|
-
"Command: #{result.command}, " \
|
|
64
|
-
"Exit status: #{result.status}, " \
|
|
65
|
-
"stdout: '#{result.stdout.strip}', " \
|
|
66
|
-
"stderr: '#{result.stderr.strip}'"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
unless File.exist?(output_file.path)
|
|
70
|
-
raise ConversionError,
|
|
71
|
-
"GhostScript did not create output file: #{output_file.path}"
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
output_content = File.binread(output_file.path)
|
|
75
|
-
|
|
76
|
-
# Check if the PDF is valid (should be more than just the header)
|
|
77
|
-
if output_content.size < 100
|
|
78
|
-
raise ConversionError,
|
|
79
|
-
"GhostScript created invalid PDF (#{output_content.size} bytes). " \
|
|
80
|
-
"Command: #{result.command}, " \
|
|
81
|
-
"stdout: '#{result.stdout.strip}', " \
|
|
82
|
-
"stderr: '#{result.stderr.strip}'"
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
output_content
|
|
86
|
-
ensure
|
|
87
|
-
# Clean up temp files
|
|
88
|
-
input_file.close unless input_file.closed?
|
|
89
|
-
input_file.unlink
|
|
90
|
-
output_file.close unless output_file.closed?
|
|
91
|
-
output_file.unlink
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Convert PDF content to PostScript
|
|
96
|
-
#
|
|
97
|
-
# This is useful as a fallback when Inkscape's PDF import fails.
|
|
98
|
-
# Ghostscript can reliably convert PDF to EPS, and Inkscape can then
|
|
99
|
-
# import the EPS file.
|
|
100
|
-
#
|
|
101
|
-
# @param pdf_content [String] the PDF content to convert
|
|
102
|
-
# @return [String] the EPS content
|
|
103
|
-
# @raise [Vectory::ConversionError] if conversion fails
|
|
104
|
-
# @raise [Vectory::GhostscriptNotFoundError] if Ghostscript is not available
|
|
105
|
-
def pdf_to_eps(pdf_content)
|
|
106
|
-
raise GhostscriptNotFoundError unless available?
|
|
107
|
-
|
|
108
|
-
input_file = Tempfile.new(["pdf_input", ".pdf"])
|
|
109
|
-
output_file = Tempfile.new(["eps_output", ".eps"])
|
|
110
|
-
|
|
111
|
-
begin
|
|
112
|
-
input_file.binmode
|
|
113
|
-
input_file.write(pdf_content)
|
|
114
|
-
input_file.flush
|
|
115
|
-
input_file.close
|
|
116
|
-
output_file.close
|
|
117
|
-
|
|
118
|
-
tool = ghostscript_tool
|
|
119
|
-
params = {
|
|
120
|
-
inputs: [input_file.path],
|
|
121
|
-
device: :eps2write,
|
|
122
|
-
output: output_file.path,
|
|
123
|
-
batch: true,
|
|
124
|
-
no_pause: true,
|
|
125
|
-
quiet: true,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
result = tool.execute(:convert,
|
|
129
|
-
execution_timeout: Configuration.instance.timeout,
|
|
130
|
-
**params)
|
|
131
|
-
|
|
132
|
-
unless result.success?
|
|
133
|
-
raise ConversionError,
|
|
134
|
-
"GhostScript PDF to EPS conversion failed. " \
|
|
135
|
-
"Command: #{result.command}, " \
|
|
136
|
-
"Exit status: #{result.status}, " \
|
|
137
|
-
"stdout: '#{result.stdout.strip}', " \
|
|
138
|
-
"stderr: '#{result.stderr.strip}'"
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
unless File.exist?(output_file.path)
|
|
142
|
-
raise ConversionError,
|
|
143
|
-
"GhostScript did not create output file: #{output_file.path}"
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
File.binread(output_file.path)
|
|
147
|
-
ensure
|
|
148
|
-
input_file.close unless input_file.closed?
|
|
149
|
-
input_file.unlink
|
|
150
|
-
output_file.close unless output_file.closed?
|
|
151
|
-
output_file.unlink
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
private
|
|
156
|
-
|
|
157
|
-
# Get the Ghostscript tool from Ukiryu
|
|
158
|
-
def ghostscript_tool
|
|
159
|
-
Ukiryu::Tool.get("ghostscript")
|
|
160
|
-
rescue Ukiryu::Errors::ToolNotFoundError => e
|
|
161
|
-
# Tool not found - raise the original GhostscriptNotFoundError
|
|
162
|
-
raise GhostscriptNotFoundError, "Ghostscript not available: #{e.message}"
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Build convert parameters for Ukiryu
|
|
166
|
-
def build_convert_params(input_path, output_path, options = {})
|
|
167
|
-
params = {
|
|
168
|
-
inputs: [input_path],
|
|
169
|
-
device: :pdfwrite,
|
|
170
|
-
output: output_path,
|
|
171
|
-
batch: true,
|
|
172
|
-
no_pause: true,
|
|
173
|
-
quiet: true,
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
# Add EPS crop option
|
|
177
|
-
params[:eps_crop] = true if options[:eps_crop]
|
|
178
|
-
|
|
179
|
-
params
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
end
|