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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +59 -0
  3. data/.github/workflows/links.yml +99 -0
  4. data/.github/workflows/rake.yml +5 -1
  5. data/.github/workflows/release.yml +7 -3
  6. data/.gitignore +5 -0
  7. data/.rubocop.yml +11 -3
  8. data/.rubocop_todo.yml +252 -0
  9. data/Gemfile +4 -2
  10. data/README.adoc +23 -1
  11. data/Rakefile +13 -0
  12. data/docs/Gemfile +18 -0
  13. data/docs/_config.yml +179 -0
  14. data/docs/features/conversion.adoc +205 -0
  15. data/docs/features/external-dependencies.adoc +305 -0
  16. data/docs/features/format-detection.adoc +173 -0
  17. data/docs/features/index.adoc +205 -0
  18. data/docs/getting-started/core-concepts.adoc +214 -0
  19. data/docs/getting-started/index.adoc +37 -0
  20. data/docs/getting-started/installation.adoc +318 -0
  21. data/docs/getting-started/quick-start.adoc +160 -0
  22. data/docs/guides/error-handling.adoc +400 -0
  23. data/docs/guides/index.adoc +197 -0
  24. data/docs/index.adoc +146 -0
  25. data/docs/lychee.toml +25 -0
  26. data/docs/reference/api.adoc +355 -0
  27. data/docs/reference/index.adoc +189 -0
  28. data/docs/understanding/architecture.adoc +277 -0
  29. data/docs/understanding/index.adoc +148 -0
  30. data/docs/understanding/inkscape-wrapper.adoc +270 -0
  31. data/lib/vectory/capture.rb +165 -37
  32. data/lib/vectory/cli.rb +2 -0
  33. data/lib/vectory/configuration.rb +177 -0
  34. data/lib/vectory/conversion/ghostscript_strategy.rb +77 -0
  35. data/lib/vectory/conversion/inkscape_strategy.rb +124 -0
  36. data/lib/vectory/conversion/strategy.rb +58 -0
  37. data/lib/vectory/conversion.rb +104 -0
  38. data/lib/vectory/datauri.rb +1 -1
  39. data/lib/vectory/emf.rb +17 -5
  40. data/lib/vectory/eps.rb +45 -3
  41. data/lib/vectory/errors.rb +25 -0
  42. data/lib/vectory/file_magic.rb +2 -2
  43. data/lib/vectory/ghostscript_wrapper.rb +160 -0
  44. data/lib/vectory/image_resize.rb +2 -2
  45. data/lib/vectory/inkscape_wrapper.rb +205 -0
  46. data/lib/vectory/pdf.rb +76 -0
  47. data/lib/vectory/platform.rb +105 -0
  48. data/lib/vectory/ps.rb +47 -3
  49. data/lib/vectory/svg.rb +46 -3
  50. data/lib/vectory/svg_document.rb +40 -24
  51. data/lib/vectory/system_call.rb +36 -9
  52. data/lib/vectory/vector.rb +3 -23
  53. data/lib/vectory/version.rb +1 -1
  54. data/lib/vectory.rb +16 -11
  55. metadata +34 -3
  56. data/lib/vectory/inkscape_converter.rb +0 -141
data/docs/_config.yml ADDED
@@ -0,0 +1,179 @@
1
+ # Jekyll configuration for Vectory documentation
2
+ # Uses Just the Docs theme - https://just-the-docs.com/
3
+
4
+ # Site settings
5
+ title: Vectory
6
+ description: Vector image conversion library for Ruby (EPS, PS, EMF, SVG)
7
+ url: https://claricle.github.io
8
+ baseurl: /vectory
9
+
10
+ # Repository
11
+ repository: claricle/vectory
12
+
13
+ # Theme
14
+ theme: just-the-docs
15
+ remote_theme: just-the-docs/just-the-docs@v0.7.0
16
+ color_scheme: light
17
+
18
+ # AsciiDoc support
19
+ asciidoc: {}
20
+ asciidoctor:
21
+ attributes:
22
+ idprefix: ''
23
+ idseparator: '-'
24
+ source-highlighter: rouge
25
+ icons: font
26
+ experimental: true
27
+ sectanchors: true
28
+ linkattrs: true
29
+ sectnums: true
30
+ toc: left
31
+ toclevels: 3
32
+
33
+ # Search configuration
34
+ search_enabled: true
35
+ search:
36
+ heading_level: 3
37
+ previews: 3
38
+ preview_words_before: 5
39
+ preview_words_after: 10
40
+ tokenizer_separator: /[\s/]+/
41
+ rel_url: true
42
+ button: true
43
+
44
+ # Navigation
45
+ nav_sort: case_insensitive
46
+ nav_fold: true
47
+
48
+ # External links
49
+ aux_links:
50
+ "Source":
51
+ - "https://github.com/claricle/vectory"
52
+ "Report Issues":
53
+ - "https://github.com/claricle/vectory/issues"
54
+ "RubyGems":
55
+ - "https://rubygems.org/gems/vectory"
56
+
57
+ aux_links_new_tab: true
58
+
59
+ # Back to top
60
+ back_to_top: true
61
+ back_to_top_text: "Back to top"
62
+
63
+ # Heading anchors
64
+ heading_anchors: true
65
+
66
+ # Footer
67
+ footer_content: 'Copyright &copy; 2025 Ribose. Distributed under the <a href="https://github.com/claricle/vectory/blob/main/LICENSE">MIT License</a>.'
68
+
69
+ # Footer last edit timestamp
70
+ last_edit_timestamp: true
71
+ last_edit_time_format: "%b %e %Y at %I:%M %p"
72
+
73
+ # Enable code copy button
74
+ enable_copy_code_button: true
75
+
76
+ # Callouts
77
+ callouts_level: quiet
78
+ callouts:
79
+ highlight:
80
+ color: yellow
81
+ important:
82
+ title: Important
83
+ color: blue
84
+ new:
85
+ title: New
86
+ color: green
87
+ note:
88
+ title: Note
89
+ color: purple
90
+ warning:
91
+ title: Warning
92
+ color: red
93
+
94
+ # Plugins
95
+ plugins:
96
+ - jekyll-asciidoc
97
+ - jekyll-seo-tag
98
+ - jekyll-sitemap
99
+
100
+ # Markdown settings (for any markdown files)
101
+ markdown: kramdown
102
+ kramdown:
103
+ input: GFM
104
+ hard_wrap: false
105
+ syntax_highlighter: rouge
106
+
107
+ # Collections for organizing content
108
+ collections:
109
+ # Core documentation pages (getting-started, interfaces, etc.)
110
+ pages:
111
+ permalink: "/:path/"
112
+ output: true
113
+
114
+ # Feature documentation
115
+ features:
116
+ permalink: "/:collection/:path/"
117
+ output: true
118
+
119
+ # Understanding/internal documentation
120
+ understanding:
121
+ permalink: "/:collection/:path/"
122
+ output: true
123
+
124
+ # Guides (task-oriented tutorials)
125
+ guides:
126
+ permalink: "/:collection/:path/"
127
+ output: true
128
+
129
+ # Reference documentation
130
+ reference:
131
+ permalink: "/:collection/:path/"
132
+ output: true
133
+
134
+ # # Just the Docs collection configuration
135
+ # just_the_docs:
136
+ # collections:
137
+ # pages:
138
+ # name: Pages
139
+ # nav_fold: false
140
+ # features:
141
+ # name: Features
142
+ # nav_fold: true
143
+ # understanding:
144
+ # name: Understanding
145
+ # nav_fold: true
146
+ # guides:
147
+ # name: Guides
148
+ # nav_fold: true
149
+ # reference:
150
+ # name: Reference
151
+ # nav_fold: true
152
+
153
+ # Defaults
154
+ defaults:
155
+ - scope:
156
+ path: ""
157
+ type: pages
158
+ values:
159
+ layout: default
160
+
161
+ # Include additional files
162
+ include:
163
+ - "*.adoc"
164
+
165
+ # Exclude from processing
166
+ exclude:
167
+ - Gemfile
168
+ - Gemfile.lock
169
+ - node_modules
170
+ - vendor
171
+ - .sass-cache
172
+ - .jekyll-cache
173
+ - .bundle
174
+ - README.md
175
+ - LICENSE
176
+ - .git
177
+ - .gitignore
178
+
179
+ permalink: pretty
@@ -0,0 +1,205 @@
1
+ ---
2
+ layout: default
3
+ title: Conversion Features
4
+ parent: Features
5
+ nav_order: 1
6
+ ---
7
+ = Conversion Features
8
+
9
+ How Vectory converts between vector image formats.
10
+
11
+ == Overview
12
+
13
+ Vectory supports pairwise conversion between EPS, PS, EMF, SVG, and PDF formats. Conversions are performed through external tools (Inkscape, Ghostscript, emf2svg) with a unified Ruby interface.
14
+
15
+ == Conversion Paths
16
+
17
+ === Direct Conversions (via Inkscape)
18
+
19
+ Inkscape handles direct conversions between SVG and other formats:
20
+
21
+ * **SVG → EPS**: Export SVG to Encapsulated PostScript
22
+ * **SVG → PS**: Export SVG to PostScript
23
+ * **SVG → EMF**: Export SVG to Enhanced Metafile
24
+ * **SVG → PDF**: Export SVG to PDF
25
+ * **EPS → SVG**: Import EPS and export to SVG
26
+ * **PS → SVG**: Import PS and export to SVG
27
+ * **EMF → SVG**: Import EMF and export to SVG
28
+ * **PDF → SVG**: Import PDF and export to SVG
29
+
30
+ [source,ruby]
31
+ ----
32
+ # Direct conversion examples
33
+ svg_to_eps = Vectory::Svg.from_path("diagram.svg").to_eps
34
+ eps_to_svg = Vectory::Eps.from_path("diagram.eps").to_svg
35
+ ----
36
+
37
+ === Two-Step Conversions (EPS/PS → SVG)
38
+
39
+ EPS and PS files are converted to SVG through a PDF intermediate:
40
+
41
+ 1. **EPS/PS → PDF** (via Ghostscript): Preserves BoundingBox information
42
+ 2. **PDF → SVG** (via Inkscape): Converts PDF to SVG
43
+
44
+ [source,ruby]
45
+ ----
46
+ # Two-step conversion (automatic)
47
+ eps = Vectory::Eps.from_path("diagram.eps")
48
+ svg = eps.to_svg # Internally: EPS → PDF → SVG
49
+ ----
50
+
51
+ This two-step process ensures accurate dimension preservation from EPS/PS files.
52
+
53
+ === EMF Conversions (via emf2svg)
54
+
55
+ EMF files are converted using the emf2svg gem:
56
+
57
+ [source,ruby]
58
+ ----
59
+ # EMF to SVG conversion
60
+ emf = Vectory::Emf.from_path("chart.emf")
61
+ svg = emf.to_svg # Uses emf2svg gem
62
+ ----
63
+
64
+ == Conversion Matrix
65
+
66
+ [cols="2,1a,2a"]
67
+ |===
68
+ |Source|Target|Tool Used
69
+
70
+ |SVG|EPS|Inkscape
71
+ |SVG|PS|Inkscape
72
+ |SVG|EMF|Inkscape
73
+ |SVG|PDF|Inkscape
74
+
75
+ |EPS|SVG|Ghostscript + Inkscape (two-step)
76
+ |EPS|PDF|Ghostscript
77
+ |EPS|PS|Via PDF intermediate
78
+
79
+ |PS|SVG|Ghostscript + Inkscape (two-step)
80
+ |PS|PDF|Ghostscript
81
+ |PS|EPS|Via PDF intermediate
82
+
83
+ |EMF|SVG|emf2svg gem
84
+ |EMF|EPS|Via SVG intermediate
85
+ |EMF|PS|Via SVG intermediate
86
+
87
+ |PDF|SVG|Inkscape
88
+ |PDF|EPS|Via SVG intermediate
89
+ |PDF|PS|Via SVG intermediate
90
+ |===
91
+
92
+ == Quality Considerations
93
+
94
+ === BoundingBox Preservation
95
+
96
+ Ghostscript preserves exact BoundingBox dimensions from EPS/PS files when converting to PDF:
97
+
98
+ [source,ruby]
99
+ ----
100
+ eps = Vectory::Eps.from_path("diagram.eps")
101
+ pdf = eps.to_pdf # BoundingBox preserved exactly
102
+ ----
103
+
104
+ === Font Handling
105
+
106
+ * **Ghostscript**: Embeds fonts in PDF intermediate
107
+ * **Inkscape**: May convert fonts to paths for SVG output
108
+
109
+ === Precision
110
+
111
+ Vector graphics maintain full precision through conversions. Rasterization does not occur.
112
+
113
+ == Performance
114
+
115
+ === Conversion Speed
116
+
117
+ * **Direct conversions** (via Inkscape): ~1-3 seconds for typical files
118
+ * **Two-step conversions** (EPS/PS → SVG): ~2-5 seconds (Ghostscript + Inkscape)
119
+ * **EMF conversions** (via emf2svg): ~1-2 seconds
120
+
121
+ === Caching
122
+
123
+ Vectory caches external tool information:
124
+
125
+ * **Inkscape version**: Detected once per session
126
+ * **Tool paths**: Discovered once and cached
127
+
128
+ [source,ruby]
129
+ ----
130
+ # Version detection is cached
131
+ inkscape = Vectory::InkscapeWrapper.instance
132
+ puts inkscape.version # Cached result
133
+ ----
134
+
135
+ == Common Conversion Tasks
136
+
137
+ === Batch Conversions
138
+
139
+ Convert multiple files in a directory:
140
+
141
+ [source,ruby]
142
+ ----
143
+ Dir["diagrams/*.eps"].each do |eps_file|
144
+ svg = Vectory::Eps.from_path(eps_file).to_svg
145
+ svg.write(eps_file.sub(".eps", ".svg"))
146
+ end
147
+ ----
148
+
149
+ === Content Conversion
150
+
151
+ Convert from content string without writing to disk:
152
+
153
+ [source,ruby]
154
+ ----
155
+ eps_content = File.read("diagram.eps")
156
+ svg = Vectory::Eps.from_content(eps_content).to_svg
157
+ svg_content = svg.content
158
+ ----
159
+
160
+ === Dimension-Preserving Conversion
161
+
162
+ Query dimensions before conversion:
163
+
164
+ [source,ruby]
165
+ ----
166
+ eps = Vectory::Eps.from_path("diagram.eps")
167
+ width, height = eps.dimensions
168
+ puts "Original size: #{width}x#{height}"
169
+
170
+ svg = eps.to_svg
171
+ svg_width, svg_height = svg.dimensions
172
+ puts "Converted size: #{svg_width}x#{svg_height}"
173
+ ----
174
+
175
+ == Error Handling
176
+
177
+ === Conversion Failures
178
+
179
+ [source,ruby]
180
+ ----
181
+ begin
182
+ svg = Vectory::Eps.from_path("invalid.eps").to_svg
183
+ rescue Vectory::ConversionError => e
184
+ puts "Conversion failed: #{e.message}"
185
+ puts "Diagnostics: #{e.diagnostics}"
186
+ end
187
+ ----
188
+
189
+ === Tool Not Found
190
+
191
+ [source,ruby]
192
+ ----
193
+ begin
194
+ svg = Vectory::Eps.from_path("diagram.eps").to_svg
195
+ rescue Vectory::InkscapeNotFoundError => e
196
+ puts "Inkscape not found: #{e.message}"
197
+ puts "Install Inkscape or set INKSCAPE_PATH"
198
+ end
199
+ ----
200
+
201
+ == See Also
202
+
203
+ * link:../understanding/conversion-pipeline/[Conversion Pipeline] - Internal conversion architecture
204
+ * link:../guides/error-handling/[Error Handling Guide] - Common errors and solutions
205
+ * link:../reference/api/[API Reference] - Conversion method documentation
@@ -0,0 +1,305 @@
1
+ ---
2
+ layout: default
3
+ title: External Dependencies
4
+ parent: Features
5
+ nav_order: 3
6
+ ---
7
+ = External Dependencies
8
+
9
+ Comprehensive guide to external tools and gems used by Vectory.
10
+
11
+ == Overview
12
+
13
+ Vectory relies on external tools for vector image conversion. This document explains each dependency, its purpose, and how Vectory integrates with it.
14
+
15
+ == Tool Dependencies
16
+
17
+ === Inkscape
18
+
19
+ **Purpose**: Primary conversion engine
20
+
21
+ Inkscape handles most vector format conversions:
22
+
23
+ * SVG ↔ EPS/PS/EMF/PDF conversions
24
+ * Dimension queries
25
+ * High-quality vector output
26
+
27
+ **Version Requirements**:
28
+
29
+ * **Recommended**: 1.0 or later
30
+ * **Minimum**: 0.92
31
+ * **Windows**: 1.3.0 (1.3.1+ has EPS/PS issues)
32
+
33
+ **Integration**:
34
+
35
+ [source,ruby]
36
+ ----
37
+ # Singleton pattern for efficiency
38
+ inkscape = Vectory::InkscapeWrapper.instance
39
+
40
+ # Version detection
41
+ puts inkscape.version # => "1.3.0"
42
+
43
+ # Executable path
44
+ puts inkscape.executable # => "/usr/bin/inkscape"
45
+
46
+ # Version-specific command generation
47
+ inkscape.modern? # => true for 1.x, false for 0.x
48
+ ----
49
+
50
+ **Version Compatibility**:
51
+
52
+ Inkscape 0.x and 1.x use different command-line syntax. Vectory automatically detects the version and uses appropriate commands:
53
+
54
+ [source,ruby]
55
+ ----
56
+ # Inkscape 0.x syntax
57
+ inkscape --file=input.svg --export-eps=output.eps
58
+
59
+ # Inkscape 1.x syntax
60
+ inkscape input.svg --export-type=eps --output-file=output.eps
61
+ ----
62
+
63
+ Vectory handles this automatically.
64
+
65
+ **Known Issues**:
66
+
67
+ * **Inkscape 1.3.1+ on Windows**: EPS/PS conversion issues
68
+ * **macOS**: May timeout without `DISPLAY=""` environment variable
69
+
70
+ **Installation**: See link:../../getting-started/installation/[Installation Guide]
71
+
72
+ === Ghostscript
73
+
74
+ **Purpose**: EPS/PS to PDF conversion
75
+
76
+ Ghostscript converts PostScript files to PDF while preserving:
77
+
78
+ * BoundingBox information
79
+ * Font embeddings
80
+ * Vector paths
81
+
82
+ **Version Requirements**:
83
+
84
+ * **Recommended**: 9.0 or later
85
+ * **Minimum**: 8.x (limited features)
86
+
87
+ **Integration**:
88
+
89
+ [source,ruby]
90
+ ----
91
+ # Singleton pattern
92
+ gs = Vectory::GhostscriptWrapper.instance
93
+
94
+ # Executable path
95
+ puts gs.executable # => "/usr/bin/gs"
96
+
97
+ # EPS to PDF conversion
98
+ pdf = gs.eps_to_pdf("diagram.eps", "output.pdf")
99
+ ----
100
+
101
+ **BoundingBox Preservation**:
102
+
103
+ [source,ruby]
104
+ ----
105
+ # Ghostscript preserves exact BoundingBox
106
+ eps = Vectory::Eps.from_path("diagram.eps")
107
+ pdf = eps.to_pdf # BoundingBox preserved from EPS
108
+ ----
109
+
110
+ **Installation**: See link:../../getting-started/installation/[Installation Guide]
111
+
112
+ == Gem Dependencies
113
+
114
+ === emf2svg
115
+
116
+ **Purpose**: EMF format support
117
+
118
+ **Version**: ~> 1.0
119
+
120
+ emf2svg provides EMF to SVG conversion without external tool dependencies.
121
+
122
+ **Integration**:
123
+
124
+ [source,ruby]
125
+ ----
126
+ # Automatic conversion
127
+ emf = Vectory::Emf.from_path("chart.emf")
128
+ svg = emf.to_svg # Uses emf2svg internally
129
+ ----
130
+
131
+ **Features**:
132
+
133
+ * Cross-platform EMF support
134
+ * No external tool dependency
135
+ * Native Ruby implementation
136
+
137
+ === moxml
138
+
139
+ **Purpose**: XML/HTML parsing (Metanorma integration)
140
+
141
+ **Version**: ~> 0.5
142
+
143
+ moxml parses XML/HTML for link:../../guides/metanorma-integration/[Metanorma SVG mapping].
144
+
145
+ **Integration**:
146
+
147
+ [source,ruby]
148
+ ----
149
+ # Used internally by SvgMapping
150
+ require 'vectory/svg_mapping'
151
+
152
+ mapping = Vectory::SvgMapping.new(xml_doc, base_uri)
153
+ mapping.process # Uses moxml for parsing
154
+ ----
155
+
156
+ **Features**:
157
+
158
+ * Link rewriting for embedded SVGs
159
+ * ID preservation and suffixing
160
+ * Namespace handling
161
+
162
+ == Tool Discovery
163
+
164
+ === Automatic Discovery
165
+
166
+ Vectory automatically discovers tools in system PATH:
167
+
168
+ [source,ruby]
169
+ ----
170
+ # Automatic discovery
171
+ inkscape = Vectory::InkscapeWrapper.instance
172
+ puts inkscape.executable # => "/usr/bin/inkscape" (or similar)
173
+ ----
174
+
175
+ === Custom Paths
176
+
177
+ Override with environment variables:
178
+
179
+ [source,bash]
180
+ ----
181
+ export INKSCAPE_PATH="/custom/path/to/inkscape"
182
+ export GHOSTSCRIPT_PATH="/custom/path/to/gs"
183
+ ----
184
+
185
+ Or in Ruby:
186
+
187
+ [source,ruby]
188
+ ----
189
+ ENV["INKSCAPE_PATH"] = "/custom/path/to/inkscape"
190
+ inkscape = Vectory::InkscapeWrapper.instance
191
+ ----
192
+
193
+ === Verification
194
+
195
+ Check if tools are available:
196
+
197
+ [source,ruby]
198
+ ----
199
+ # Check Inkscape
200
+ if Vectory::InkscapeWrapper.instance.available?
201
+ puts "Inkscape found"
202
+ else
203
+ puts "Inkscape not found"
204
+ end
205
+
206
+ # Check Ghostscript
207
+ if Vectory::GhostscriptWrapper.instance.available?
208
+ puts "Ghostscript found"
209
+ else
210
+ puts "Ghostscript not found"
211
+ end
212
+ ----
213
+
214
+ == Error Handling
215
+
216
+ === Tool Not Found
217
+
218
+ [source,ruby]
219
+ ----
220
+ begin
221
+ svg = Vectory::Eps.from_path("diagram.eps").to_svg
222
+ rescue Vectory::InkscapeNotFoundError => e
223
+ puts "Inkscape not found: #{e.message}"
224
+ puts "Install Inkscape or set INKSCAPE_PATH"
225
+ end
226
+ ----
227
+
228
+ === Version Incompatibility
229
+
230
+ [source,ruby]
231
+ ----
232
+ # Check version compatibility
233
+ inkscape = Vectory::InkscapeWrapper.instance
234
+ if inkscape.version < "0.92"
235
+ puts "Warning: Inkscape version is too old"
236
+ end
237
+ ----
238
+
239
+ === Tool Execution Failures
240
+
241
+ [source,ruby]
242
+ ----
243
+ begin
244
+ svg = Vectory::Eps.from_path("diagram.eps").to_svg
245
+ rescue Vectory::SystemCallError => e
246
+ puts "Tool execution failed: #{e.message}"
247
+ puts "Command: #{e.command}"
248
+ puts "Exit code: #{e.exit_code}"
249
+ end
250
+ ----
251
+
252
+ == Platform-Specific Notes
253
+
254
+ === macOS
255
+
256
+ * **Inkscape**: May timeout without `DISPLAY=""`
257
+ * **Fix**: Vectory automatically sets `DISPLAY=""` on macOS
258
+
259
+ === Windows
260
+
261
+ * **Inkscape 1.3.1+**: EPS/PS conversion issues
262
+ * **Fix**: Use Inkscape 1.3.0
263
+ * **Process management**: Uses `taskkill` instead of `Process.kill`
264
+
265
+ === Linux
266
+
267
+ * Standard Unix behavior
268
+ * Ensure proper file permissions
269
+
270
+ == CI/CD Integration
271
+
272
+ Example GitHub Actions setup:
273
+
274
+ [source,yaml]
275
+ ----
276
+ - name: Install dependencies
277
+ run: |
278
+ sudo apt-get update
279
+ sudo apt-get install -y inkscape ghostscript
280
+ env:
281
+ DISPLAY: ""
282
+
283
+ - name: Run tests
284
+ run: bundle exec rspec
285
+ ----
286
+
287
+ ## Troubleshooting
288
+
289
+ **Inkscape timeout on macOS**::
290
+ Set `DISPLAY=""` environment variable (automatic in Vectory)
291
+
292
+ **Inkscape 1.3.1+ EPS issues on Windows**::
293
+ Downgrade to Inkscape 1.3.0
294
+
295
+ **Ghostscript not found**::
296
+ Install Ghostscript or set `GHOSTSCRIPT_PATH`
297
+
298
+ **emf2svg conversion fails**::
299
+ Ensure emf2svg gem is installed: `gem install emf2svg`
300
+
301
+ == See Also
302
+
303
+ * link:../../getting-started/installation/[Installation Guide] - Detailed setup instructions
304
+ * link:../understanding/inkscape-wrapper/[Inkscape Wrapper] - Technical details
305
+ * link:../understanding/ghostscript-wrapper/[Ghostscript Wrapper] - Technical details