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
@@ -0,0 +1,277 @@
1
+ ---
2
+ layout: default
3
+ title: Architecture
4
+ parent: Understanding
5
+ nav_order: 1
6
+ ---
7
+ = Architecture Overview
8
+
9
+ Vectory's design principles and architecture.
10
+
11
+ == Purpose
12
+
13
+ This section explains Vectory's architecture, design decisions, and implementation patterns.
14
+
15
+ == Design Philosophy
16
+
17
+ Vectory follows these core principles:
18
+
19
+ * **Object-Oriented Design**: Clear class hierarchy with inheritance
20
+ * **Wrapper Pattern**: External tools abstracted behind clean interfaces
21
+ * **Singleton Pattern**: Shared tool instances for efficiency
22
+ * **Model-Based**: Object representation rather than serialization manipulation
23
+ * **Separation of Concerns**: Each class has a single responsibility
24
+
25
+ == Class Hierarchy
26
+
27
+ [source,ruby]
28
+ ----
29
+ Vectory::Image # Base class for all images
30
+
31
+ └── Vectory::Vector # Abstract base for vector formats
32
+
33
+ ├── Vectory::Eps # Encapsulated PostScript
34
+ ├── Vectory::Ps # PostScript
35
+ ├── Vectory::Emf # Enhanced Metafile
36
+ ├── Vectory::Svg # Scalable Vector Graphics
37
+ └── Vectory::Pdf # PDF (intermediate format)
38
+ ----
39
+
40
+ Each format class inherits from `Vectory::Vector` and shares common functionality.
41
+
42
+ == Design Patterns
43
+
44
+ === Wrapper Pattern
45
+
46
+ External tools are wrapped in singleton classes:
47
+
48
+ [source,ruby]
49
+ ----
50
+ # Inkscape wrapper
51
+ class InkscapeWrapper
52
+ include Singleton
53
+
54
+ def executable
55
+ @executable ||= find_inkscape
56
+ end
57
+
58
+ def version
59
+ @version ||= detect_version
60
+ end
61
+ end
62
+ ----
63
+
64
+ **Benefits**:
65
+
66
+ * Clean interface to external tools
67
+ * Centralized tool discovery
68
+ * Version detection and caching
69
+ * Error handling
70
+
71
+ === Singleton Pattern
72
+
73
+ Tool wrappers use singleton pattern:
74
+
75
+ [source,ruby]
76
+ ----
77
+ # Single instance shared across conversions
78
+ inkscape = Vectory::InkscapeWrapper.instance
79
+
80
+ # Version detected once, cached
81
+ puts inkscape.version # Cached result
82
+ ----
83
+
84
+ **Benefits**:
85
+
86
+ * Efficient resource usage
87
+ * Version detection happens once
88
+ * Consistent behavior across conversions
89
+
90
+ === Factory Methods
91
+
92
+ Each format class provides factory methods:
93
+
94
+ [source,ruby]
95
+ ----
96
+ Vectory::Eps.from_path(path) # Load from file
97
+ Vectory::Eps.from_content(content) # Load from string
98
+ Vectory::Eps.from_datauri(uri) # Load from data URI
99
+ Vectory::Eps.from_node(node) # Load from XML node
100
+ ----
101
+
102
+ **Benefits**:
103
+
104
+ * Flexible input sources
105
+ * Consistent API across formats
106
+ * Easy testing and mocking
107
+
108
+ == Conversion Pipeline
109
+
110
+ === Direct Conversions
111
+
112
+ Some conversions are direct:
113
+
114
+ [source,text]
115
+ ----
116
+ SVG → Inkscape → EPS/PS/EMF/PDF
117
+ EMF → emf2svg → SVG
118
+ ----
119
+
120
+ === Two-Step Conversions
121
+
122
+ Other conversions use intermediate formats:
123
+
124
+ [source,text]
125
+ ----
126
+ EPS/PS → Ghostscript → PDF → Inkscape → SVG
127
+ ----
128
+
129
+ This preserves BoundingBox information from EPS/PS files.
130
+
131
+ == Component Interaction
132
+
133
+ [.flowchart]
134
+ ----
135
+ User Code
136
+
137
+ ├─→ Vectory::Eps.from_path()
138
+ │ │
139
+ │ ├─→ Vectory::FileMagic.detect_format()
140
+ │ │
141
+ │ └─→ Vectory::Eps.new()
142
+
143
+ └─→ Vectory::Eps#to_svg()
144
+
145
+ ├─→ Vectory::Pdf#to_pdf() ──→ GhostscriptWrapper
146
+
147
+ └─→ Vectory::Pdf#to_svg() ──→ InkscapeWrapper
148
+ ----
149
+
150
+ == File System Operations
151
+
152
+ === Path Tracking
153
+
154
+ Each image tracks two paths:
155
+
156
+ [source,ruby]
157
+ ----
158
+ eps = Vectory::Eps.from_path("input.eps")
159
+
160
+ # Where it was loaded from (read-only)
161
+ eps.initial_path # => "input.eps"
162
+
163
+ # Where it's currently written
164
+ eps.path # Raises NotWrittenToDiskError if not written
165
+
166
+ eps.write("output.eps")
167
+ eps.path # => "output.eps"
168
+ ----
169
+
170
+ === Temporary Files
171
+
172
+ Vectory manages temporary files for conversions:
173
+
174
+ [source,ruby]
175
+ ----
176
+ # Temporary files created automatically
177
+ eps = Vectory::Eps.from_path("diagram.eps")
178
+ svg = eps.to_svg # Creates temp PDF, then temp SVG
179
+
180
+ # Write to final location
181
+ svg.write("diagram.svg")
182
+ ----
183
+
184
+ Temp files are cleaned up automatically.
185
+
186
+ == Error Handling
187
+
188
+ Structured error hierarchy:
189
+
190
+ [source,ruby]
191
+ ----
192
+ Vectory::Error # Base error
193
+ ├── SystemCallError # External tool failures
194
+ ├── NotWrittenToDiskError # Path access before write
195
+ ├── ParsingError # Content parsing failures
196
+ ├── InkscapeNotFoundError # Inkscape unavailable
197
+ ├── ConversionError # Conversion failures
198
+ └── InkscapeQueryError # Dimension query failures
199
+ ----
200
+
201
+ == Performance Considerations
202
+
203
+ === Caching
204
+
205
+ [source,ruby]
206
+ ----
207
+ # Tool version cached
208
+ inkscape = Vectory::InkscapeWrapper.instance
209
+ inkscape.version # Detected once, cached
210
+
211
+ # Tool path cached
212
+ inkscape.executable # Discovered once, cached
213
+ ----
214
+
215
+ === Lazy Evaluation
216
+
217
+ [source,ruby]
218
+ ----
219
+ # Version detected only when needed
220
+ inkscape = Vectory::InkscapeWrapper.instance
221
+ # Version not detected until first use
222
+ puts inkscape.version # Detection happens here
223
+ ----
224
+
225
+ === Process Management
226
+
227
+ [source,ruby]
228
+ ----
229
+ # Non-blocking timeout on Windows
230
+ spawn("taskkill", "/pid", pid, "/f",
231
+ %i[out err] => File::NULL)
232
+
233
+ # Blocking timeout on Unix
234
+ Process.kill(:TERM, pid)
235
+ ----
236
+
237
+ == Extensibility
238
+
239
+ === Adding New Formats
240
+
241
+ To add a new format:
242
+
243
+ . Create format class inheriting from `Vectory::Vector`
244
+ . Implement `#to_*` conversion methods
245
+ . Add format detection to `FileMagic`
246
+
247
+ [source,ruby]
248
+ ----
249
+ class Vectory::NewFormat < Vectory::Vector
250
+ def to_svg
251
+ # Convert to SVG
252
+ end
253
+
254
+ def self.from_path(path)
255
+ # Load from file
256
+ end
257
+ end
258
+ ----
259
+
260
+ === Adding New Conversions
261
+
262
+ Add conversion methods to existing format classes:
263
+
264
+ [source,ruby]
265
+ ----
266
+ class Vectory::Eps < Vectory::Vector
267
+ def to_new_format
268
+ # Convert to new format
269
+ end
270
+ end
271
+ ----
272
+
273
+ == See Also
274
+
275
+ * link:class-hierarchy/[Class Hierarchy] - Detailed class structure
276
+ * link:conversion-pipeline/[Conversion Pipeline] - How conversions work
277
+ * link:wrapper-pattern/[Wrapper Pattern] - Tool abstraction details
@@ -0,0 +1,148 @@
1
+ ---
2
+ layout: default
3
+ title: Understanding
4
+ nav_order: 5
5
+ has_children: true
6
+ ---
7
+
8
+ Learn how Vectory works internally.
9
+
10
+ == Overview
11
+
12
+ This section explains Vectory's internal architecture, design decisions, and implementation details. Understanding these concepts will help you use Vectory effectively and troubleshoot issues.
13
+
14
+ == Architecture
15
+
16
+ link:architecture[**Architecture Overview**]::
17
+ Class hierarchy and design patterns used in Vectory.
18
+ +
19
+ Object-oriented design with inheritance, singleton pattern for tool wrappers, and model-based architecture.
20
+
21
+ link:class-hierarchy[**Class Hierarchy**]::
22
+ Detailed explanation of the `Vectory::Image` inheritance tree.
23
+ +
24
+ `Vectory::Image` → `Vectory::Vector` → Format-specific classes.
25
+
26
+ link:conversion-pipeline[**Conversion Pipeline**]::
27
+ How conversions flow through intermediate formats.
28
+ +
29
+ Direct conversions vs. two-step conversions through PDF.
30
+
31
+ == External Tools
32
+
33
+ link:external-dependencies[**External Dependencies Overview**]::
34
+ Comprehensive guide to all external tools and gems.
35
+ +
36
+ Inkscape, Ghostscript, emf2svg, moxml - their purposes and how Vectory integrates with them.
37
+
38
+ link:inkscape-wrapper[**Inkscape Wrapper**]::
39
+ Singleton pattern implementation for Inkscape integration.
40
+ +
41
+ Version detection, command generation, timeout management, 0.x vs 1.x+ syntax differences.
42
+
43
+ link:ghostscript-wrapper[**Ghostscript Wrapper**]::
44
+ Ghostscript integration for EPS/PS to PDF conversion.
45
+ +
46
+ BoundingBox preservation, PDF generation, command-line options.
47
+
48
+ link:emf2svg-integration[**emf2svg Integration**]::
49
+ EMF format support via the emf2svg gem.
50
+ +
51
+ Windows EMF conversion, cross-platform compatibility.
52
+
53
+ link:moxml-integration[**moxml Integration**]::
54
+ XML/HTML parsing for Metanorma SVG mapping.
55
+ +
56
+ Link rewriting, ID preservation, namespace handling.
57
+
58
+ == Design Patterns
59
+
60
+ link:wrapper-pattern[**Wrapper Pattern**]::
61
+ Abstracting external tool dependencies.
62
+ +
63
+ `InkscapeWrapper` and `GhostscriptWrapper` provide clean interfaces.
64
+
65
+ link:singleton[**Singleton Pattern**]::
66
+ Shared Inkscape instance across conversions.
67
+ +
68
+ Version detection caching, resource efficiency.
69
+
70
+ link:factory-methods[**Factory Methods**]::
71
+ Class methods for creating image objects.
72
+ +
73
+ `from_path`, `from_content`, `from_datauri`, `from_node`.
74
+
75
+ == File System Operations
76
+
77
+ link:file-paths[**Path Management**]::
78
+ `initial_path` vs `path` distinction.
79
+ +
80
+ Original file location vs. current write location.
81
+
82
+ link:temp-files[**Temporary File Handling**]::
83
+ How Vectory manages intermediate files.
84
+ +
85
+ Automatic cleanup, temp directory usage.
86
+
87
+ == Error Handling
88
+
89
+ link:error-classes[**Error Class Hierarchy**]::
90
+ Exception types and when they're raised.
91
+ +
92
+ `SystemCallError`, `NotWrittenToDiskError`, `ParsingError`, etc.
93
+
94
+ link:timeout-handling[**Timeout Management**]::
95
+ How Vectory handles hung external processes.
96
+ +
97
+ Platform-specific timeout implementation (Unix vs Windows).
98
+
99
+ == Format Detection
100
+
101
+ link:magic-numbers[**Magic Number Detection**]::
102
+ How file formats are detected from content.
103
+ +
104
+ EPS, PS, EMF magic numbers; SVG content inspection.
105
+
106
+ link:file-type-heuristics[**File Type Heuristics**]::
107
+ Fallback strategies when magic numbers aren't enough.
108
+
109
+ == Performance
110
+
111
+ link:caching[**Caching Strategies**]::
112
+ Version detection caching, tool discovery caching.
113
+
114
+ link:batch-optimization[**Batch Processing**]::
115
+ Optimizations for processing multiple files.
116
+
117
+ == Platform-Specific Behavior
118
+
119
+ link:windows-support[**Windows Support**]::
120
+ Special considerations for Windows platforms.
121
+ +
122
+ Path handling, process management, Inkscape version compatibility.
123
+
124
+ link:macos-support[**macOS Support**]::
125
+ Display environment configuration for headless operation.
126
+ +
127
+ `DISPLAY=""` for Inkscape timeout prevention.
128
+
129
+ link:linux-support[**Linux Support**]::
130
+ Standard Unix behavior for Vectory.
131
+
132
+ == Technical Constraints
133
+
134
+ link:format-limitations[**Format Limitations**]::
135
+ Known limitations of each supported format.
136
+ +
137
+ Conversion artifacts, precision issues.
138
+
139
+ link:tool-versions[**Tool Version Requirements**]::
140
+ Inkscape and Ghostscript version compatibility.
141
+ +
142
+ Inkscape 0.x vs 1.x+ syntax differences, known issues.
143
+
144
+ == See Also
145
+
146
+ * link:../features/[Features] - What Vectory can do
147
+ * link:../guides/[Guides] - How to use Vectory
148
+ * link:../reference/[Reference] - Complete API documentation
@@ -0,0 +1,270 @@
1
+ ---
2
+ layout: default
3
+ title: Inkscape Wrapper
4
+ parent: Understanding
5
+ nav_order: 2
6
+ ---
7
+ = Inkscape Wrapper
8
+
9
+ Singleton pattern implementation for Inkscape integration.
10
+
11
+ == Overview
12
+
13
+ The `InkscapeWrapper` class provides a clean interface to Inkscape's command-line tools. It uses the singleton pattern to share a single instance across all conversions.
14
+
15
+ == Singleton Pattern
16
+
17
+ [source,ruby]
18
+ ----
19
+ class InkscapeWrapper
20
+ include Singleton
21
+
22
+ def initialize
23
+ @executable = nil
24
+ @version = nil
25
+ end
26
+ end
27
+ ----
28
+
29
+ [source,ruby]
30
+ ----
31
+ # Get singleton instance
32
+ inkscape = Vectory::InkscapeWrapper.instance
33
+
34
+ # Same instance returned on subsequent calls
35
+ inkscape2 = Vectory::InkscapeWrapper.instance
36
+ puts inkscape.equal?(inkscape2) # => true
37
+ ----
38
+
39
+ == Version Detection
40
+
41
+ === Automatic Detection
42
+
43
+ Inkscape version is detected on first access:
44
+
45
+ [source,ruby]
46
+ ----
47
+ inkscape = Vectory::InkscapeWrapper.instance
48
+
49
+ # Version detected automatically
50
+ puts inkscape.version # => "1.3.0"
51
+ ----
52
+
53
+ === Version Caching
54
+
55
+ Version is cached after first detection:
56
+
57
+ [source,ruby]
58
+ ----
59
+ # First call: detects version
60
+ version1 = inkscape.version
61
+
62
+ # Subsequent calls: returns cached value
63
+ version2 = inkscape.version
64
+
65
+ puts version1.equal?(version2) # => true
66
+ ----
67
+
68
+ === Version Checking
69
+
70
+ [source,ruby]
71
+ ----
72
+ inkscape = Vectory::InkscapeWrapper.instance
73
+
74
+ # Check if modern version (1.x)
75
+ if inkscape.modern?
76
+ puts "Using Inkscape 1.x syntax"
77
+ else
78
+ puts "Using Inkscape 0.x syntax"
79
+ end
80
+ ----
81
+
82
+ == Command Generation
83
+
84
+ === Version-Specific Syntax
85
+
86
+ Inkscape 0.x and 1.x use different command-line syntax:
87
+
88
+ [source,ruby]
89
+ ----
90
+ # Inkscape 0.x syntax
91
+ inkscape --file=input.svg --export-eps=output.eps
92
+
93
+ # Inkscape 1.x syntax
94
+ inkscape input.svg --export-type=eps --output-file=output.eps
95
+ ----
96
+
97
+ Vectory generates appropriate commands automatically:
98
+
99
+ [source,ruby]
100
+ ----
101
+ class InkscapeWrapper
102
+ def command(input, output, format)
103
+ if modern?
104
+ # Inkscape 1.x syntax
105
+ [executable, input, "--export-type=#{format}",
106
+ "--output-file=#{output}"]
107
+ else
108
+ # Inkscape 0.x syntax
109
+ [executable, "--file=#{input}",
110
+ "--export-#{format}=#{output}"]
111
+ end
112
+ end
113
+ end
114
+ ----
115
+
116
+ == Conversion Methods
117
+
118
+ === SVG to Other Formats
119
+
120
+ [source,ruby]
121
+ ----
122
+ inkscape = Vectory::InkscapeWrapper.instance
123
+
124
+ # SVG to PDF
125
+ inkscape.svg_to_pdf("input.svg", "output.pdf")
126
+
127
+ # SVG to EPS
128
+ inkscape.svg_to_eps("input.svg", "output.eps")
129
+
130
+ # SVG to PS
131
+ inkscape.svg_to_ps("input.svg", "output.ps")
132
+
133
+ # SVG to EMF
134
+ inkscape.svg_to_emf("input.svg", "output.emf")
135
+ ----
136
+
137
+ === Other Formats to SVG
138
+
139
+ [source,ruby]
140
+ ----
141
+ # PDF to SVG
142
+ inkscape.pdf_to_svg("input.pdf", "output.svg")
143
+
144
+ # EPS to SVG
145
+ inkscape.eps_to_svg("input.eps", "output.svg")
146
+
147
+ # PS to SVG
148
+ inkscape.ps_to_svg("input.ps", "output.svg")
149
+ ----
150
+
151
+ == Dimension Queries
152
+
153
+ === Query Without Rendering
154
+
155
+ [source,ruby]
156
+ ----
157
+ inkscape = Vectory::InkscapeWrapper.instance
158
+
159
+ # Query dimensions without full conversion
160
+ width, height = inkscape.query_dimensions("diagram.svg")
161
+ puts "Size: #{width}x#{height}"
162
+ ----
163
+
164
+ === Implementation
165
+
166
+ Uses Inkscape's `--query-width` and `--query-height` options:
167
+
168
+ [source,ruby]
169
+ ----
170
+ def query_dimensions(input)
171
+ output = `#{executable} --query-width --query-height #{input}`
172
+ # Parse output to extract dimensions
173
+ parse_dimensions(output)
174
+ end
175
+ ----
176
+
177
+ == Error Handling
178
+
179
+ === Tool Not Found
180
+
181
+ [source,ruby]
182
+ ----
183
+ begin
184
+ inkscape = Vectory::InkscapeWrapper.instance
185
+ inkscape.version
186
+ rescue Vectory::InkscapeNotFoundError => e
187
+ puts "Inkscape not found: #{e.message}"
188
+ puts "Install Inkscape or set INKSCAPE_PATH"
189
+ end
190
+ ----
191
+
192
+ === Execution Failures
193
+
194
+ [source,ruby]
195
+ ----
196
+ begin
197
+ inkscape.svg_to_pdf("input.svg", "output.pdf")
198
+ rescue Vectory::SystemCallError => e
199
+ puts "Conversion failed: #{e.message}"
200
+ puts "Exit code: #{e.exit_code}"
201
+ end
202
+ ----
203
+
204
+ == Platform-Specific Behavior
205
+
206
+ === macOS
207
+
208
+ Vectory automatically sets `DISPLAY=""` to prevent timeouts:
209
+
210
+ [source,ruby]
211
+ ----
212
+ # Automatic on macOS
213
+ ENV["DISPLAY"] = "" if RUBY_PLATFORM =~ /darwin/
214
+ ----
215
+
216
+ === Windows
217
+
218
+ * Inkscape 1.3.1+ has EPS/PS issues
219
+ * Use Inkscape 1.3.0 for best compatibility
220
+
221
+ === Linux
222
+
223
+ Standard Unix behavior applies.
224
+
225
+ == Configuration
226
+
227
+ === Custom Inkscape Path
228
+
229
+ [source,bash]
230
+ ----
231
+ # Set environment variable
232
+ export INKSCAPE_PATH="/custom/path/to/inkscape"
233
+ ----
234
+
235
+ [source,ruby]
236
+ ----
237
+ # Or set in code
238
+ ENV["INKSCAPE_PATH"] = "/custom/path/to/inkscape"
239
+ inkscape = Vectory::InkscapeWrapper.instance
240
+ puts inkscape.executable # => "/custom/path/to/inkscape"
241
+ ----
242
+
243
+ ## Performance
244
+
245
+ === Singleton Efficiency
246
+
247
+ [source,ruby]
248
+ ----
249
+ # Single instance shared across conversions
250
+ 1000.times do
251
+ svg = Vectory::Eps.from_path("file#{i}.eps").to_svg
252
+ end
253
+ # InkscapeWrapper.instance created once, reused 1000 times
254
+ ----
255
+
256
+ === Version Detection Cost
257
+
258
+ [source,ruby]
259
+ ----
260
+ # Version detected once
261
+ inkscape = Vectory::InkscapeWrapper.instance
262
+ version1 = inkscape.version # Detects: ~50ms
263
+ version2 = inkscape.version # Cached: ~0ms
264
+ ----
265
+
266
+ == See Also
267
+
268
+ * link:../architecture/[Architecture Overview] - Design patterns
269
+ * link:ghostscript-wrapper/[Ghostscript Wrapper] - Other tool wrapper
270
+ * link:../features/external-dependencies/[External Dependencies] - Tool requirements