vectory 0.8.1 → 0.9.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/.github/workflows/rake.yml +2 -0
- data/Gemfile +1 -0
- data/README.adoc +57 -42
- data/docs/reference/api.adoc +84 -2
- data/lib/vectory/cli.rb +1 -2
- data/lib/vectory/conversion/ghostscript_strategy.rb +0 -3
- data/lib/vectory/conversion/inkscape_strategy.rb +0 -3
- data/lib/vectory/conversion.rb +4 -4
- data/lib/vectory/eps.rb +0 -3
- data/lib/vectory/ghostscript_wrapper.rb +98 -75
- data/lib/vectory/inkscape_wrapper.rb +141 -120
- data/lib/vectory/pdf.rb +65 -25
- data/lib/vectory/ps.rb +0 -3
- data/lib/vectory/svg_document.rb +115 -7
- data/lib/vectory/svg_mapping.rb +99 -8
- data/lib/vectory/vector.rb +0 -1
- data/lib/vectory/version.rb +1 -1
- data/lib/vectory.rb +39 -20
- data/vectory.gemspec +2 -1
- metadata +17 -6
- data/.hound.yml +0 -5
- data/lib/vectory/capture.rb +0 -243
- data/lib/vectory/system_call.rb +0 -86
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc294acc45e36e018340d14a047047d5addc5c712fabc42fec2642b82bc77aa8
|
|
4
|
+
data.tar.gz: '09b35052e8b019a2553eaa34ae14c6fce0344c5fc8d0e61b42fc4bb574bbe569'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffb21fcdc31b15763547be90085ec49288588c7fabc9d9a2f803f5a0fcf5f958c3bf020e7f5aa96abeb2d31de2c1077e6cb9b9fa57b108e28355328e2fa64eb3
|
|
7
|
+
data.tar.gz: 989f2bb3ade3b0416571b67d7cf8262def78291e9730ee73050052ecc3fa84639be2f93d21af392fc7f7b2a0eb3be3aedd19afe7628598aa60f01afcd4d4f5ae
|
data/.github/workflows/rake.yml
CHANGED
data/Gemfile
CHANGED
data/README.adoc
CHANGED
|
@@ -169,15 +169,49 @@ Vectory::Eps.from_path("img.eps").to_uri.content
|
|
|
169
169
|
==== SVG mapping (for the metanorma project)
|
|
170
170
|
|
|
171
171
|
Vectory can integrate SVG files into XML or HTML, respecting internal id and
|
|
172
|
-
link references
|
|
172
|
+
link references. It supports ID disambiguation for multi-document and multi-svgmap
|
|
173
|
+
scenarios.
|
|
174
|
+
|
|
175
|
+
===== Basic usage
|
|
173
176
|
|
|
174
177
|
[source,ruby]
|
|
175
178
|
----
|
|
176
179
|
xml_string = Vectory::SvgMapping.from_path("doc.xml").to_xml
|
|
177
180
|
----
|
|
178
181
|
|
|
179
|
-
|
|
180
|
-
|
|
182
|
+
===== ID suffixing for uniqueness
|
|
183
|
+
|
|
184
|
+
When processing multiple documents or multiple svgmaps within a document,
|
|
185
|
+
SVG IDs must be unique to avoid conflicts. Vectory applies two types of suffixes:
|
|
186
|
+
|
|
187
|
+
*ID suffix* (Cross-document uniqueness)::
|
|
188
|
+
An optional suffix derived from document or container identity (e.g., `_ISO_17301-1_2016`).
|
|
189
|
+
This provides uniqueness across documents or collections.
|
|
190
|
+
|
|
191
|
+
*Index suffix* (Multi-svgmap uniqueness)::
|
|
192
|
+
Automatically applied based on the svgmap's position in the document (0, 1, 2, ...).
|
|
193
|
+
Formatted as a 9-digit zero-padded number (e.g., `_000000000`, `_000000001`).
|
|
194
|
+
This provides uniqueness when a document contains multiple svgmaps.
|
|
195
|
+
|
|
196
|
+
.Final ID composition
|
|
197
|
+
[source]
|
|
198
|
+
----
|
|
199
|
+
Original ID: fig1
|
|
200
|
+
With ID suffix only: fig1_ISO_17301-1_2016
|
|
201
|
+
With both suffixes: fig1_ISO_17301-1_2016_000000000
|
|
202
|
+
----
|
|
203
|
+
|
|
204
|
+
===== Usage with ID suffix
|
|
205
|
+
|
|
206
|
+
[source,ruby]
|
|
207
|
+
----
|
|
208
|
+
mapping = Vectory::SvgMapping.new(doc, "", id_suffix: "_ISO_17301-1_2016")
|
|
209
|
+
xml_string = mapping.call.to_xml
|
|
210
|
+
----
|
|
211
|
+
|
|
212
|
+
===== Input format
|
|
213
|
+
|
|
214
|
+
The input XML must support the `svgmap` tag with link mapping.
|
|
181
215
|
|
|
182
216
|
[source,xml]
|
|
183
217
|
----
|
|
@@ -187,8 +221,8 @@ mapping. For example, it can convert XML like this:
|
|
|
187
221
|
<xref target="ref1">Computer</xref>
|
|
188
222
|
</target>
|
|
189
223
|
<target href="http://www.example.com">
|
|
190
|
-
<link target="http://www.example.com">Phone</link
|
|
191
|
-
|
|
224
|
+
<link target="http://www.example.com">Phone</link>
|
|
225
|
+
</target>
|
|
192
226
|
</svgmap>
|
|
193
227
|
----
|
|
194
228
|
|
|
@@ -196,16 +230,12 @@ mapping. For example, it can convert XML like this:
|
|
|
196
230
|
[source,xml]
|
|
197
231
|
----
|
|
198
232
|
<?xml version="1.0" encoding="utf-8"?>
|
|
199
|
-
|
|
200
|
-
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
201
|
-
viewBox="0 0 595.28 841.89" style="enable-background:new 0 0 595.28 841.89;" xml:space="preserve">
|
|
233
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
202
234
|
<style type="text/css">
|
|
203
|
-
|
|
204
|
-
|
|
235
|
+
#Layer_1 { fill:none }
|
|
236
|
+
svg[id = 'Layer_1'] { fill:none }
|
|
205
237
|
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
|
206
238
|
</style>
|
|
207
|
-
<image style="overflow:visible;" width="368" height="315" xlink:href="data:image/gif;base64,R0lG..ommited to save space" transform="matrix(1 0 0 1 114 263.8898)">
|
|
208
|
-
</image>
|
|
209
239
|
<a xlink:href="mn://action_schema" xlink:dummy="Layer_1">
|
|
210
240
|
<rect x="123.28" y="273.93" class="st0" width="88.05" height="41.84"/>
|
|
211
241
|
</a>
|
|
@@ -218,15 +248,14 @@ mapping. For example, it can convert XML like this:
|
|
|
218
248
|
</svg>
|
|
219
249
|
----
|
|
220
250
|
|
|
221
|
-
|
|
222
|
-
the `a` tags:
|
|
251
|
+
===== Output format
|
|
223
252
|
|
|
224
253
|
[source,xml]
|
|
225
254
|
----
|
|
226
255
|
<figure>
|
|
227
|
-
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1'
|
|
228
|
-
|
|
229
|
-
<
|
|
256
|
+
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1'
|
|
257
|
+
id='Layer_1_000000001' x='0px' y='0px' viewBox='0 0 595.28 841.89'>
|
|
258
|
+
<style> #Layer_1_000000001 { fill:none }</style>
|
|
230
259
|
<a xlink:href='#ref1' xlink:dummy='Layer_1_000000001'>
|
|
231
260
|
<rect x='123.28' y='273.93' class='st0' width='88.05' height='41.84'/>
|
|
232
261
|
</a>
|
|
@@ -240,53 +269,39 @@ the `a` tags:
|
|
|
240
269
|
</figure>
|
|
241
270
|
----
|
|
242
271
|
|
|
243
|
-
|
|
272
|
+
===== Inline SVG support
|
|
273
|
+
|
|
274
|
+
SVG can also be provided inline within the svgmap:
|
|
244
275
|
|
|
245
276
|
[source,xml]
|
|
246
277
|
----
|
|
247
278
|
<svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
|
|
248
279
|
<figure>
|
|
249
|
-
<svg xmlns='http://www.w3.org/2000/svg'
|
|
250
|
-
<a href="mn://action_schema"
|
|
251
|
-
<rect x="123.28" y="273.93" class="st0"
|
|
252
|
-
</a>
|
|
253
|
-
<a href="mn://basic_attribute_schema" >
|
|
254
|
-
<rect x="324.69" y="450.52" class="st0" width="132.62" height="40.75"/>
|
|
255
|
-
</a>
|
|
256
|
-
<a xlink:href="mn://support_resource_schema" >
|
|
257
|
-
<rect x="324.69" y="528.36" class="st0" width="148.16" height="40.75"/>
|
|
280
|
+
<svg xmlns='http://www.w3.org/2000/svg' id='Layer_1'>
|
|
281
|
+
<a href="mn://action_schema">
|
|
282
|
+
<rect x="123.28" y="273.93" class="st0"/>
|
|
258
283
|
</a>
|
|
259
284
|
</svg>
|
|
260
285
|
</figure>
|
|
261
286
|
<target href="mn://action_schema">
|
|
262
287
|
<xref target="ref1">Computer</xref>
|
|
263
288
|
</target>
|
|
264
|
-
<target href="http://www.example.com">
|
|
265
|
-
<link target="http://www.example.com">Phone</link>
|
|
266
|
-
</target>
|
|
267
289
|
</svgmap>
|
|
268
290
|
----
|
|
269
291
|
|
|
270
|
-
|
|
292
|
+
===== Data URI support
|
|
293
|
+
|
|
294
|
+
Images can be provided as data URIs:
|
|
271
295
|
|
|
272
296
|
[source,xml]
|
|
273
297
|
----
|
|
274
298
|
<svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
|
|
275
299
|
<figure>
|
|
276
|
-
<image src='data:image/svg+xml;base64,PD94
|
|
300
|
+
<image src='data:image/svg+xml;base64,PD94...'/>
|
|
277
301
|
</figure>
|
|
278
302
|
<target href="href1.htm">
|
|
279
303
|
<xref target="ref1">Computer</xref>
|
|
280
304
|
</target>
|
|
281
|
-
<target href="mn://basic_attribute_schema">
|
|
282
|
-
<link target="http://www.example.com">Phone</link>
|
|
283
|
-
</target>
|
|
284
|
-
<target href="mn://support_resource_schema">
|
|
285
|
-
<eref type="express" bibitemid="express_action_schema" citeas="">
|
|
286
|
-
<localityStack><locality type="anchor"><referenceFrom>action_schema.basic</referenceFrom></locality></localityStack>
|
|
287
|
-
Coffee
|
|
288
|
-
</eref>
|
|
289
|
-
</target>
|
|
290
305
|
</svgmap>
|
|
291
306
|
----
|
|
292
307
|
|
|
@@ -355,7 +370,7 @@ Vector#width
|
|
|
355
370
|
|
|
356
371
|
== Development
|
|
357
372
|
|
|
358
|
-
=== Regenerating
|
|
373
|
+
=== Regenerating test fixtures
|
|
359
374
|
|
|
360
375
|
Some test fixtures are generated by external tools (Ghostscript, Inkscape, cairo).
|
|
361
376
|
When these tools are updated, the reference files may need to be regenerated.
|
data/docs/reference/api.adoc
CHANGED
|
@@ -339,7 +339,7 @@ rescue Vectory::ConversionError => e
|
|
|
339
339
|
end
|
|
340
340
|
----
|
|
341
341
|
|
|
342
|
-
=== Dimension
|
|
342
|
+
=== Dimension query
|
|
343
343
|
|
|
344
344
|
[source,ruby]
|
|
345
345
|
----
|
|
@@ -348,7 +348,89 @@ width, height = eps.dimensions
|
|
|
348
348
|
puts "Dimensions: #{width}x#{height}"
|
|
349
349
|
----
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
== SVG mapping
|
|
352
|
+
|
|
353
|
+
Integrates SVG content into XML documents with ID disambiguation for multi-document scenarios.
|
|
354
|
+
|
|
355
|
+
=== Vectory::SvgMapping
|
|
356
|
+
|
|
357
|
+
Processes XML documents containing `<svgmap>` elements, extracting and integrating SVG content.
|
|
358
|
+
|
|
359
|
+
[source,ruby]
|
|
360
|
+
----
|
|
361
|
+
# Basic usage
|
|
362
|
+
xml_string = Vectory::SvgMapping.from_path("doc.xml").to_xml
|
|
363
|
+
|
|
364
|
+
# With ID suffix for cross-document uniqueness
|
|
365
|
+
mapping = Vectory::SvgMapping.new(doc, "", id_suffix: "_ISO_17301-1_2016")
|
|
366
|
+
xml_string = mapping.call.to_xml
|
|
367
|
+
----
|
|
368
|
+
|
|
369
|
+
**Factory methods**:
|
|
370
|
+
|
|
371
|
+
* `Vectory::SvgMapping.from_path(path)` - Load from file path
|
|
372
|
+
* `Vectory::SvgMapping.from_xml(xml)` - Load from XML string
|
|
373
|
+
* `Vectory::SvgMapping.new(doc, local_directory, id_suffix: nil)` - Direct instantiation
|
|
374
|
+
|
|
375
|
+
**Parameters**:
|
|
376
|
+
|
|
377
|
+
* `doc` - Nokogiri XML Document containing svgmap elements
|
|
378
|
+
* `local_directory` - Directory for resolving relative file paths
|
|
379
|
+
* `id_suffix` - Optional suffix for cross-document ID uniqueness (e.g., "_ISO_17301-1_2016")
|
|
380
|
+
|
|
381
|
+
**Methods**:
|
|
382
|
+
|
|
383
|
+
* `#call` - Process all svgmap elements, returns processed document
|
|
384
|
+
* `#to_xml` - Process and return XML string
|
|
385
|
+
|
|
386
|
+
=== Vectory::SvgDocument
|
|
387
|
+
|
|
388
|
+
Represents an SVG document and handles ID suffixing for uniqueness.
|
|
389
|
+
|
|
390
|
+
[source,ruby]
|
|
391
|
+
----
|
|
392
|
+
# Create from SVG content
|
|
393
|
+
svg_doc = Vectory::SvgDocument.new(svg_content)
|
|
394
|
+
|
|
395
|
+
# Apply both ID suffix and index suffix
|
|
396
|
+
svg_doc.namespace(index, links, xpath_to_remove, id_suffix: "_ISO_17301-1_2016")
|
|
397
|
+
result = svg_doc.content
|
|
398
|
+
----
|
|
399
|
+
|
|
400
|
+
**Constructor**:
|
|
401
|
+
|
|
402
|
+
* `Vectory::SvgDocument.new(content)` - Create from SVG XML string
|
|
403
|
+
|
|
404
|
+
**Methods**:
|
|
405
|
+
|
|
406
|
+
* `namespace(index_suffix, links, xpath_to_remove, id_suffix: nil)` - Apply transformations
|
|
407
|
+
** `index_suffix` - Position-based suffix (0, 1, 2...) formatted as 9-digit zero-padded
|
|
408
|
+
** `links` - Hash mapping hrefs to targets
|
|
409
|
+
** `xpath_to_remove` - XPath for elements to remove (processing instructions)
|
|
410
|
+
** `id_suffix` - Optional identity-based suffix for cross-document uniqueness
|
|
411
|
+
|
|
412
|
+
* `content` - Returns processed SVG as XML string
|
|
413
|
+
* `remap_links(map)` - Remap internal links
|
|
414
|
+
* `apply_id_suffix(id_suffix)` - Apply ID suffix
|
|
415
|
+
* `apply_index_suffix(index)` - Apply index suffix
|
|
416
|
+
|
|
417
|
+
=== ID Suffixing
|
|
418
|
+
|
|
419
|
+
SVG IDs are suffixed in two stages:
|
|
420
|
+
|
|
421
|
+
[source]
|
|
422
|
+
----
|
|
423
|
+
# Stage 1: ID suffix (cross-document uniqueness)
|
|
424
|
+
fig1 → fig1_ISO_17301-1_2016
|
|
425
|
+
|
|
426
|
+
# Stage 2: Index suffix (multi-svgmap uniqueness)
|
|
427
|
+
fig1_ISO_17301-1_2016 → fig1_ISO_17301-1_2016_000000000
|
|
428
|
+
----
|
|
429
|
+
|
|
430
|
+
* **ID suffix** (`id_suffix` parameter): Derived from document/container identity. Optional.
|
|
431
|
+
* **Index suffix** (`index_suffix` parameter): Zero-padded position (0, 1, 2... → _000000000, _000000001). Automatically applied.
|
|
432
|
+
|
|
433
|
+
== See Also
|
|
352
434
|
|
|
353
435
|
* link:../guides/error-handling/[Error Handling Guide] - Common errors and solutions
|
|
354
436
|
* link:../understanding/architecture/[Architecture] - Design patterns
|
data/lib/vectory/cli.rb
CHANGED
data/lib/vectory/conversion.rb
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "conversion/strategy"
|
|
4
|
-
require_relative "conversion/inkscape_strategy"
|
|
5
|
-
require_relative "conversion/ghostscript_strategy"
|
|
6
|
-
|
|
7
3
|
module Vectory
|
|
8
4
|
# Conversion module provides strategy-based conversion interface
|
|
9
5
|
#
|
|
@@ -16,6 +12,10 @@ module Vectory
|
|
|
16
12
|
# @example Get available strategies for a conversion
|
|
17
13
|
# Vectory::Conversion.strategies_for(:svg, :eps)
|
|
18
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
19
|
class << self
|
|
20
20
|
# Convert content from one format to another
|
|
21
21
|
#
|
data/lib/vectory/eps.rb
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
require "tempfile"
|
|
4
4
|
require "fileutils"
|
|
5
|
-
|
|
6
|
-
require_relative "system_call"
|
|
7
|
-
require_relative "platform"
|
|
5
|
+
require "ukiryu"
|
|
8
6
|
|
|
9
7
|
module Vectory
|
|
10
8
|
# GhostscriptWrapper converts PS and EPS files to PDF using Ghostscript
|
|
9
|
+
#
|
|
10
|
+
# Uses Ukiryu for platform-adaptive command execution.
|
|
11
11
|
class GhostscriptWrapper
|
|
12
12
|
SUPPORTED_INPUT_FORMATS = %w[ps eps].freeze
|
|
13
13
|
|
|
14
14
|
class << self
|
|
15
15
|
def available?
|
|
16
|
-
|
|
16
|
+
ghostscript_tool
|
|
17
17
|
true
|
|
18
18
|
rescue GhostscriptNotFoundError
|
|
19
19
|
false
|
|
@@ -22,9 +22,8 @@ module Vectory
|
|
|
22
22
|
def version
|
|
23
23
|
return nil unless available?
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
call.stdout.strip
|
|
25
|
+
tool = ghostscript_tool
|
|
26
|
+
tool.version
|
|
28
27
|
rescue StandardError
|
|
29
28
|
nil
|
|
30
29
|
end
|
|
@@ -49,15 +48,22 @@ module Vectory
|
|
|
49
48
|
# Close output file so GhostScript can write to it
|
|
50
49
|
output_file.close
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
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)
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
result = tool.execute(:convert,
|
|
57
|
+
execution_timeout: Configuration.instance.timeout,
|
|
58
|
+
**params)
|
|
59
|
+
|
|
60
|
+
unless result.success?
|
|
59
61
|
raise ConversionError,
|
|
60
|
-
"GhostScript conversion failed
|
|
62
|
+
"GhostScript conversion failed. " \
|
|
63
|
+
"Command: #{result.command}, " \
|
|
64
|
+
"Exit status: #{result.status}, " \
|
|
65
|
+
"stdout: '#{result.stdout.strip}', " \
|
|
66
|
+
"stderr: '#{result.stderr.strip}'"
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
unless File.exist?(output_file.path)
|
|
@@ -71,9 +77,9 @@ module Vectory
|
|
|
71
77
|
if output_content.size < 100
|
|
72
78
|
raise ConversionError,
|
|
73
79
|
"GhostScript created invalid PDF (#{output_content.size} bytes). " \
|
|
74
|
-
"Command: #{
|
|
75
|
-
"stdout: '#{
|
|
76
|
-
"stderr: '#{
|
|
80
|
+
"Command: #{result.command}, " \
|
|
81
|
+
"stdout: '#{result.stdout.strip}', " \
|
|
82
|
+
"stderr: '#{result.stderr.strip}'"
|
|
77
83
|
end
|
|
78
84
|
|
|
79
85
|
output_content
|
|
@@ -86,74 +92,91 @@ module Vectory
|
|
|
86
92
|
end
|
|
87
93
|
end
|
|
88
94
|
|
|
89
|
-
|
|
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
|
|
90
117
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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}'"
|
|
104
139
|
end
|
|
105
140
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return path if path
|
|
141
|
+
unless File.exist?(output_file.path)
|
|
142
|
+
raise ConversionError,
|
|
143
|
+
"GhostScript did not create output file: #{output_file.path}"
|
|
110
144
|
end
|
|
111
|
-
else
|
|
112
|
-
# On Unix-like systems, check PATH
|
|
113
|
-
path = find_in_path("gs")
|
|
114
|
-
return path if path
|
|
115
|
-
end
|
|
116
145
|
|
|
117
|
-
|
|
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
|
|
118
153
|
end
|
|
119
154
|
|
|
120
|
-
|
|
121
|
-
# If command already has an extension, try it as-is first
|
|
122
|
-
if File.extname(cmd) != ""
|
|
123
|
-
Platform.executable_search_paths.each do |path|
|
|
124
|
-
exe = File.join(path, cmd)
|
|
125
|
-
return exe if File.executable?(exe) && !File.directory?(exe)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
155
|
+
private
|
|
128
156
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
nil
|
|
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}"
|
|
138
163
|
end
|
|
139
164
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
cmd_parts
|
|
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
|
|
157
180
|
end
|
|
158
181
|
end
|
|
159
182
|
end
|