vectory 0.10.2 → 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.
data/lib/vectory/pdf.rb DELETED
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vectory
4
- class Pdf < Vector
5
- attr_accessor :original_height, :original_width
6
-
7
- def self.default_extension
8
- "pdf"
9
- end
10
-
11
- def self.mimetype
12
- "application/pdf"
13
- end
14
-
15
- def to_svg
16
- svg = convert_to_svg
17
-
18
- # If we have original dimensions from EPS/PS, adjust the SVG
19
- if original_height && original_width
20
- adjusted_content = adjust_svg_dimensions(svg.content, original_width,
21
- original_height)
22
- svg = Svg.new(adjusted_content, svg.initial_path)
23
- end
24
-
25
- svg
26
- end
27
-
28
- def to_eps
29
- with_inkscape_pdf_fallback(:eps, Eps)
30
- end
31
-
32
- def to_ps
33
- with_inkscape_pdf_fallback(:ps, Ps)
34
- end
35
-
36
- def to_emf
37
- with_inkscape_pdf_fallback(:emf, Emf)
38
- end
39
-
40
- private
41
-
42
- # Execute a conversion with fallback for Inkscape PDF import issues
43
- #
44
- # Inkscape 1.4.x on Windows and macOS has a PDF import bug where it
45
- # may fail to create output files. This method catches any conversion
46
- # error and retries via the PDF → EPS → target format path.
47
- #
48
- # @param output_format [Symbol] the target format (:svg, :eps, :ps, :emf)
49
- # @param output_class [Class] the output class (Svg, Eps, Ps, Emf)
50
- # @param plain [Boolean] whether to use plain SVG format (only for SVG)
51
- # @return [Vector] the converted output
52
- # @raise [Vectory::ConversionError] if both methods fail
53
- def with_inkscape_pdf_fallback(output_format, output_class, plain: false)
54
- InkscapeWrapper.convert(
55
- content: content,
56
- input_format: :pdf,
57
- output_format: output_format,
58
- output_class: output_class,
59
- plain: plain,
60
- )
61
- rescue Vectory::ConversionError => e
62
- log_conversion_failure(e, output_format)
63
-
64
- # Try fallback: PDF → EPS (Ghostscript) → target format (Inkscape)
65
- begin
66
- warn "[VECTORY] Attempting fallback: PDF → EPS → #{output_format.upcase}" if fallback_logging_enabled?
67
- eps_content = GhostscriptWrapper.pdf_to_eps(content)
68
- warn "[VECTORY] PDF → EPS succeeded, now trying EPS → #{output_format.upcase}" if fallback_logging_enabled?
69
- InkscapeWrapper.convert(
70
- content: eps_content,
71
- input_format: :eps,
72
- output_format: output_format,
73
- output_class: output_class,
74
- plain: plain,
75
- )
76
- rescue StandardError => fallback_error
77
- # Wrap non-Vectory errors in ConversionError for consistent error handling
78
- error_to_raise = if fallback_error.is_a?(Vectory::Error)
79
- fallback_error
80
- else
81
- ConversionError.new(
82
- "PDF fallback conversion failed: #{fallback_error.message}",
83
- )
84
- end
85
- warn "[VECTORY] Fallback also failed: #{fallback_error.message[0..100]}" if fallback_logging_enabled?
86
- raise error_to_raise
87
- end
88
- end
89
-
90
- # Convert PDF to SVG using fallback mechanism
91
- def convert_to_svg
92
- with_inkscape_pdf_fallback(:svg, Svg, plain: true)
93
- end
94
-
95
- def log_conversion_failure(error, output_format)
96
- return unless fallback_logging_enabled?
97
-
98
- warn "[VECTORY] PDF → #{output_format.upcase} direct conversion failed:"
99
- warn "[VECTORY] Error: #{error.message[0..200]}"
100
- end
101
-
102
- def fallback_logging_enabled?
103
- ENV["VECTORY_DEBUG"] || ENV["CI"]
104
- end
105
-
106
- def adjust_svg_dimensions(svg_content, width, height)
107
- # Replace width and height attributes in SVG root element
108
- svg_content.gsub(/(<svg[^>]*\s)width="[^"]*"/, "\\1width=\"#{width}\"")
109
- .gsub(/(<svg[^>]*\s)height="[^"]*"/, "\\1height=\"#{height}\"")
110
- .gsub(/(<svg[^>]*\s)viewBox="[^"]*"/) do |match|
111
- # Adjust viewBox to match new dimensions
112
- "#{match.split('viewBox')[0]}viewBox=\"0 0 #{width} #{height}\""
113
- end
114
- end
115
- end
116
- end
@@ -1,105 +0,0 @@
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