postsvg 0.1.0 → 0.3.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/CHANGELOG.md +105 -0
- data/CLAUDE.md +173 -0
- data/Gemfile +2 -3
- data/README.adoc +456 -179
- data/Rakefile +100 -0
- data/TODO.roadmap/00-architecture.md +139 -0
- data/TODO.roadmap/01-autoload-migration.md +39 -0
- data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
- data/TODO.roadmap/03-domain-model.md +66 -0
- data/TODO.roadmap/04-lexer.md +40 -0
- data/TODO.roadmap/05-parser.md +43 -0
- data/TODO.roadmap/06-graphics-state.md +45 -0
- data/TODO.roadmap/07-matrix-and-color.md +37 -0
- data/TODO.roadmap/08-svg-builder.md +51 -0
- data/TODO.roadmap/09-renderer.md +52 -0
- data/TODO.roadmap/10-visitor.md +58 -0
- data/TODO.roadmap/11-operator-coverage.md +103 -0
- data/TODO.roadmap/12-svg-domain.md +62 -0
- data/TODO.roadmap/13-translation-handlers.md +69 -0
- data/TODO.roadmap/14-ps-serializer.md +49 -0
- data/TODO.roadmap/15-cli-and-public-api.md +47 -0
- data/TODO.roadmap/16-specs.md +84 -0
- data/TODO.roadmap/17-docs-sync.md +47 -0
- data/TODO.roadmap/18-performance-and-determinism.md +47 -0
- data/TODO.roadmap/19-error-model.md +45 -0
- data/TODO.roadmap/20-font-and-text.md +46 -0
- data/TODO.roadmap/21-images.md +45 -0
- data/TODO.roadmap/22-forms-and-resources.md +25 -0
- data/TODO.roadmap/23-level2-level3.md +52 -0
- data/TODO.roadmap/24-ci-and-release.md +42 -0
- data/TODO.roadmap/README.md +77 -0
- data/docs/.gitignore +29 -0
- data/docs/CHANGELOG.md +114 -0
- data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
- data/docs/DEPLOYMENT.md +456 -0
- data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
- data/docs/DOCUMENTATION_PLAN.md +425 -0
- data/docs/FINAL_SUMMARY.md +657 -0
- data/docs/Gemfile +15 -0
- data/docs/README.md +327 -0
- data/docs/_config.yml +99 -0
- data/docs/advanced-topics.adoc +370 -0
- data/docs/api-reference/colors.adoc +705 -0
- data/docs/api-reference/converter.adoc +699 -0
- data/docs/api-reference/execution-context.adoc +1210 -0
- data/docs/api-reference/graphics-state.adoc +1070 -0
- data/docs/api-reference/interpreter.adoc +810 -0
- data/docs/api-reference/matrix.adoc +1179 -0
- data/docs/api-reference/path-builder.adoc +1284 -0
- data/docs/api-reference/postsvg-module.adoc +388 -0
- data/docs/api-reference/svg-generator.adoc +891 -0
- data/docs/api-reference/tokenizer.adoc +925 -0
- data/docs/api-reference.adoc +221 -0
- data/docs/architecture/command-registry.adoc +1191 -0
- data/docs/architecture/conversion-pipeline.adoc +746 -0
- data/docs/architecture/design-decisions.adoc +999 -0
- data/docs/architecture/generator-stage.adoc +1115 -0
- data/docs/architecture/graphics-state-model.adoc +1089 -0
- data/docs/architecture/interpreter-stage.adoc +1125 -0
- data/docs/architecture/parser-stage.adoc +1051 -0
- data/docs/architecture.adoc +354 -0
- data/docs/cli-reference/batch-command.adoc +616 -0
- data/docs/cli-reference/check-command.adoc +677 -0
- data/docs/cli-reference/cli-options.adoc +802 -0
- data/docs/cli-reference/convert-command.adoc +462 -0
- data/docs/cli-reference/version-command.adoc +296 -0
- data/docs/cli-reference.adoc +317 -0
- data/docs/concepts/conversion-pipeline.adoc +903 -0
- data/docs/concepts/coordinate-systems.adoc +836 -0
- data/docs/concepts/graphics-state.adoc +861 -0
- data/docs/concepts/path-operations.adoc +1076 -0
- data/docs/concepts/postscript-language.adoc +859 -0
- data/docs/concepts/svg-generation.adoc +937 -0
- data/docs/concepts.adoc +198 -0
- data/docs/contributing.adoc +443 -0
- data/docs/development.adoc +420 -0
- data/docs/faq.adoc +493 -0
- data/docs/getting-started/basic-usage.adoc +538 -0
- data/docs/getting-started/common-workflows.adoc +577 -0
- data/docs/getting-started/first-conversion.adoc +492 -0
- data/docs/getting-started/installation.adoc +534 -0
- data/docs/getting-started.adoc +94 -0
- data/docs/index.adoc +248 -0
- data/docs/optimization.adoc +196 -0
- data/docs/ps2svg_compatibility.adoc +149 -0
- data/docs/quick-reference.adoc +453 -0
- data/docs/sitemap.adoc +337 -0
- data/docs/troubleshooting.adoc +486 -0
- data/docs/validation.adoc +772 -0
- data/exe/postsvg +1 -0
- data/lib/postsvg/cli.rb +104 -57
- data/lib/postsvg/color.rb +132 -0
- data/lib/postsvg/errors.rb +68 -3
- data/lib/postsvg/format_number.rb +22 -0
- data/lib/postsvg/graphics_context.rb +80 -0
- data/lib/postsvg/graphics_stack.rb +43 -0
- data/lib/postsvg/model/literals/array.rb +41 -0
- data/lib/postsvg/model/literals/dictionary.rb +34 -0
- data/lib/postsvg/model/literals/hex.rb +37 -0
- data/lib/postsvg/model/literals/name.rb +40 -0
- data/lib/postsvg/model/literals/number.rb +36 -0
- data/lib/postsvg/model/literals/procedure.rb +41 -0
- data/lib/postsvg/model/literals/string.rb +30 -0
- data/lib/postsvg/model/literals.rb +19 -0
- data/lib/postsvg/model/operator.rb +58 -0
- data/lib/postsvg/model/operators/arithmetic.rb +264 -0
- data/lib/postsvg/model/operators/boolean.rb +182 -0
- data/lib/postsvg/model/operators/color.rb +74 -0
- data/lib/postsvg/model/operators/container.rb +186 -0
- data/lib/postsvg/model/operators/control_flow.rb +119 -0
- data/lib/postsvg/model/operators/device.rb +21 -0
- data/lib/postsvg/model/operators/dictionary.rb +118 -0
- data/lib/postsvg/model/operators/font.rb +121 -0
- data/lib/postsvg/model/operators/graphics_state.rb +84 -0
- data/lib/postsvg/model/operators/painting.rb +29 -0
- data/lib/postsvg/model/operators/path.rb +169 -0
- data/lib/postsvg/model/operators/stack.rb +72 -0
- data/lib/postsvg/model/operators/transformations.rb +103 -0
- data/lib/postsvg/model/operators.rb +89 -0
- data/lib/postsvg/model/program.rb +68 -0
- data/lib/postsvg/model/token.rb +43 -0
- data/lib/postsvg/model.rb +17 -0
- data/lib/postsvg/options.rb +29 -0
- data/lib/postsvg/renderer.rb +85 -0
- data/lib/postsvg/serializer.rb +325 -0
- data/lib/postsvg/source/ast_builder.rb +308 -0
- data/lib/postsvg/source/lexer.rb +322 -0
- data/lib/postsvg/source/operand_stack.rb +55 -0
- data/lib/postsvg/source.rb +21 -0
- data/lib/postsvg/svg/attribute_parser.rb +45 -0
- data/lib/postsvg/svg/clip_path_registry.rb +44 -0
- data/lib/postsvg/svg/document.rb +22 -0
- data/lib/postsvg/svg/element.rb +84 -0
- data/lib/postsvg/svg/elements/circle.rb +36 -0
- data/lib/postsvg/svg/elements/clip_path.rb +26 -0
- data/lib/postsvg/svg/elements/defs.rb +24 -0
- data/lib/postsvg/svg/elements/ellipse.rb +38 -0
- data/lib/postsvg/svg/elements/group.rb +37 -0
- data/lib/postsvg/svg/elements/image.rb +35 -0
- data/lib/postsvg/svg/elements/line.rb +36 -0
- data/lib/postsvg/svg/elements/path.rb +32 -0
- data/lib/postsvg/svg/elements/polygon.rb +12 -0
- data/lib/postsvg/svg/elements/polyline.rb +32 -0
- data/lib/postsvg/svg/elements/rect.rb +44 -0
- data/lib/postsvg/svg/elements/svg.rb +39 -0
- data/lib/postsvg/svg/elements/text.rb +42 -0
- data/lib/postsvg/svg/elements.rb +31 -0
- data/lib/postsvg/svg/paint.rb +34 -0
- data/lib/postsvg/svg/parser.rb +39 -0
- data/lib/postsvg/svg/path_data/command.rb +27 -0
- data/lib/postsvg/svg/path_data/parser.rb +82 -0
- data/lib/postsvg/svg/path_data.rb +17 -0
- data/lib/postsvg/svg/stroke.rb +31 -0
- data/lib/postsvg/svg/transform_list.rb +59 -0
- data/lib/postsvg/svg.rb +22 -0
- data/lib/postsvg/svg_builder.rb +249 -0
- data/lib/postsvg/translation/arc_converter.rb +86 -0
- data/lib/postsvg/translation/bounding_box.rb +59 -0
- data/lib/postsvg/translation/context.rb +34 -0
- data/lib/postsvg/translation/handler_registry.rb +38 -0
- data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
- data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
- data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
- data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
- data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
- data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
- data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
- data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
- data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
- data/lib/postsvg/translation/handlers/shared.rb +110 -0
- data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
- data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
- data/lib/postsvg/translation/handlers.rb +25 -0
- data/lib/postsvg/translation/ps_renderer.rb +105 -0
- data/lib/postsvg/translation/record_emitter.rb +35 -0
- data/lib/postsvg/translation.rb +17 -0
- data/lib/postsvg/version.rb +1 -1
- data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
- data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
- data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
- data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
- data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
- data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
- data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
- data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
- data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
- data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
- data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
- data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
- data/lib/postsvg/visitors/ps_visitor.rb +208 -0
- data/lib/postsvg/visitors.rb +9 -0
- data/lib/postsvg.rb +93 -59
- data/lychee.toml +86 -0
- metadata +216 -11
- data/postsvg.gemspec +0 -38
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
= Common Workflows
|
|
2
|
+
:page-nav_order: 4
|
|
3
|
+
:page-parent: Getting Started
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
This guide demonstrates common real-world workflows for using Postsvg in various scenarios. Each workflow includes complete, tested examples that you can adapt to your needs.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../getting-started.adoc[Getting Started Overview]
|
|
13
|
+
* link:basic-usage.adoc[Basic Usage]
|
|
14
|
+
* link:first-conversion.adoc[First Conversion Tutorial]
|
|
15
|
+
* link:../api-reference.adoc[API Reference]
|
|
16
|
+
|
|
17
|
+
== Concepts
|
|
18
|
+
|
|
19
|
+
**Workflow**:: A sequence of steps to accomplish a specific task using Postsvg.
|
|
20
|
+
|
|
21
|
+
**Batch Processing**:: Converting multiple files in an automated manner.
|
|
22
|
+
|
|
23
|
+
**Validation Pipeline**:: Checking files before conversion to ensure quality.
|
|
24
|
+
|
|
25
|
+
**Error Handling**:: Gracefully handling conversion failures and partial successes.
|
|
26
|
+
|
|
27
|
+
== Workflow 1: Single File Conversion with Validation
|
|
28
|
+
|
|
29
|
+
Convert a single file after validating it first.
|
|
30
|
+
|
|
31
|
+
[source,sh]
|
|
32
|
+
----
|
|
33
|
+
#!/bin/bash
|
|
34
|
+
# validate-and-convert.sh
|
|
35
|
+
|
|
36
|
+
INPUT="document.ps"
|
|
37
|
+
OUTPUT="document.svg"
|
|
38
|
+
|
|
39
|
+
# Step 1: Validate
|
|
40
|
+
echo "Validating $INPUT..."
|
|
41
|
+
if postsvg check --level=semantic "$INPUT"; then
|
|
42
|
+
echo "✓ Validation passed"
|
|
43
|
+
|
|
44
|
+
# Step 2: Convert
|
|
45
|
+
echo "Converting to SVG..."
|
|
46
|
+
if postsvg convert "$INPUT" "$OUTPUT"; then
|
|
47
|
+
echo "✓ Conversion complete: $OUTPUT"
|
|
48
|
+
else
|
|
49
|
+
echo "✗ Conversion failed"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
else
|
|
53
|
+
echo "✗ Validation failed"
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
----
|
|
57
|
+
|
|
58
|
+
**Usage:**
|
|
59
|
+
[source,sh]
|
|
60
|
+
----
|
|
61
|
+
chmod +x validate-and-convert.sh
|
|
62
|
+
./validate-and-convert.sh
|
|
63
|
+
----
|
|
64
|
+
|
|
65
|
+
== Workflow 2: Batch Conversion with Error Logging
|
|
66
|
+
|
|
67
|
+
Convert multiple files and log errors for later review.
|
|
68
|
+
|
|
69
|
+
[source,ruby]
|
|
70
|
+
----
|
|
71
|
+
#!/usr/bin/env ruby
|
|
72
|
+
# batch-convert.rb
|
|
73
|
+
|
|
74
|
+
require 'postsvg'
|
|
75
|
+
require 'logger'
|
|
76
|
+
|
|
77
|
+
# Setup logging
|
|
78
|
+
logger = Logger.new('conversion.log')
|
|
79
|
+
logger.info "Batch conversion started"
|
|
80
|
+
|
|
81
|
+
# Find all PS files
|
|
82
|
+
ps_files = Dir.glob('input/*.{ps,eps}', File::FNM_CASEFOLD)
|
|
83
|
+
logger.info "Found #{ps_files.size} files to convert"
|
|
84
|
+
|
|
85
|
+
# Track statistics
|
|
86
|
+
stats = { success: 0, failed: 0, partial: 0 }
|
|
87
|
+
|
|
88
|
+
# Convert each file
|
|
89
|
+
ps_files.each do |input_file|
|
|
90
|
+
output_file = input_file.sub(/\.(ps|eps)$/i, '.svg')
|
|
91
|
+
.sub('input/', 'output/')
|
|
92
|
+
|
|
93
|
+
begin
|
|
94
|
+
# Try strict conversion first
|
|
95
|
+
converter = Postsvg::Converter.new(
|
|
96
|
+
File.read(input_file),
|
|
97
|
+
strict_mode: true
|
|
98
|
+
)
|
|
99
|
+
svg = converter.convert
|
|
100
|
+
File.write(output_file, svg)
|
|
101
|
+
|
|
102
|
+
stats[:success] += 1
|
|
103
|
+
logger.info "✓ #{input_file} → #{output_file}"
|
|
104
|
+
puts "✓ #{File.basename(input_file)}"
|
|
105
|
+
|
|
106
|
+
rescue Postsvg::Error => e
|
|
107
|
+
# Try lenient mode
|
|
108
|
+
begin
|
|
109
|
+
converter = Postsvg::Converter.new(
|
|
110
|
+
File.read(input_file),
|
|
111
|
+
strict_mode: false
|
|
112
|
+
)
|
|
113
|
+
svg = converter.convert
|
|
114
|
+
File.write(output_file, svg)
|
|
115
|
+
|
|
116
|
+
stats[:partial] += 1
|
|
117
|
+
logger.warn "⚠ #{input_file}: #{e.message} (used lenient mode)"
|
|
118
|
+
puts "⚠ #{File.basename(input_file)} (partial)"
|
|
119
|
+
|
|
120
|
+
rescue StandardError => e2
|
|
121
|
+
stats[:failed] += 1
|
|
122
|
+
logger.error "✗ #{input_file}: #{e2.message}"
|
|
123
|
+
puts "✗ #{File.basename(input_file)}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Summary
|
|
129
|
+
logger.info "Conversion complete: #{stats[:success]} success, " \
|
|
130
|
+
"#{stats[:partial]} partial, #{stats[:failed]} failed"
|
|
131
|
+
puts "\nSummary:"
|
|
132
|
+
puts " Success: #{stats[:success]}"
|
|
133
|
+
puts " Partial: #{stats[:partial]}"
|
|
134
|
+
puts " Failed: #{stats[:failed]}"
|
|
135
|
+
puts "\nSee conversion.log for details"
|
|
136
|
+
----
|
|
137
|
+
|
|
138
|
+
**Usage:**
|
|
139
|
+
[source,sh]
|
|
140
|
+
----
|
|
141
|
+
chmod +x batch-convert.rb
|
|
142
|
+
./batch-convert.rb
|
|
143
|
+
----
|
|
144
|
+
|
|
145
|
+
== Workflow 3: CI/CD Integration
|
|
146
|
+
|
|
147
|
+
Validate PostScript files in a continuous integration pipeline.
|
|
148
|
+
|
|
149
|
+
**GitHub Actions:**
|
|
150
|
+
|
|
151
|
+
[source,yaml]
|
|
152
|
+
----
|
|
153
|
+
# .github/workflows/validate-ps.yml
|
|
154
|
+
name: Validate PostScript Files
|
|
155
|
+
|
|
156
|
+
on:
|
|
157
|
+
pull_request:
|
|
158
|
+
paths:
|
|
159
|
+
- '**.ps'
|
|
160
|
+
- '**.eps'
|
|
161
|
+
|
|
162
|
+
jobs:
|
|
163
|
+
validate:
|
|
164
|
+
runs-on: ubuntu-latest
|
|
165
|
+
steps:
|
|
166
|
+
- uses: actions/checkout@v3
|
|
167
|
+
|
|
168
|
+
- name: Setup Ruby
|
|
169
|
+
uses: ruby/setup-ruby@v1
|
|
170
|
+
with:
|
|
171
|
+
ruby-version: '3.2'
|
|
172
|
+
|
|
173
|
+
- name: Install Postsvg
|
|
174
|
+
run: gem install postsvg
|
|
175
|
+
|
|
176
|
+
- name: Find PostScript files
|
|
177
|
+
id: find-files
|
|
178
|
+
run: |
|
|
179
|
+
FILES=$(find . -name "*.ps" -o -name "*.eps" | tr '\n' ' ')
|
|
180
|
+
echo "files=$FILES" >> $GITHUB_OUTPUT
|
|
181
|
+
|
|
182
|
+
- name: Validate files
|
|
183
|
+
run: |
|
|
184
|
+
postsvg check --format=json --no-color \
|
|
185
|
+
--level=full --fail-fast \
|
|
186
|
+
${{ steps.find-files.outputs.files }}
|
|
187
|
+
|
|
188
|
+
- name: Convert files
|
|
189
|
+
if: success()
|
|
190
|
+
run: |
|
|
191
|
+
for file in ${{ steps.find-files.outputs.files }}; do
|
|
192
|
+
postsvg convert "$file" "${file%.*}.svg"
|
|
193
|
+
done
|
|
194
|
+
|
|
195
|
+
- name: Upload SVG artifacts
|
|
196
|
+
uses: actions/upload-artifact@v3
|
|
197
|
+
with:
|
|
198
|
+
name: svg-files
|
|
199
|
+
path: '**/*.svg'
|
|
200
|
+
----
|
|
201
|
+
|
|
202
|
+
**GitLab CI:**
|
|
203
|
+
|
|
204
|
+
[source,yaml]
|
|
205
|
+
----
|
|
206
|
+
# .gitlab-ci.yml
|
|
207
|
+
validate-ps:
|
|
208
|
+
image: ruby:3.2
|
|
209
|
+
stage: test
|
|
210
|
+
script:
|
|
211
|
+
- gem install postsvg
|
|
212
|
+
- find . -name "*.ps" -o -name "*.eps" |
|
|
213
|
+
xargs postsvg check --format=json --level=full
|
|
214
|
+
only:
|
|
215
|
+
changes:
|
|
216
|
+
- "**/*.ps"
|
|
217
|
+
- "**/*.eps"
|
|
218
|
+
|
|
219
|
+
convert-ps:
|
|
220
|
+
image: ruby:3.2
|
|
221
|
+
stage: build
|
|
222
|
+
script:
|
|
223
|
+
- gem install postsvg
|
|
224
|
+
- |
|
|
225
|
+
for file in $(find . -name "*.ps" -o -name "*.eps"); do
|
|
226
|
+
postsvg convert "$file" "${file%.*}.svg"
|
|
227
|
+
done
|
|
228
|
+
artifacts:
|
|
229
|
+
paths:
|
|
230
|
+
- "**/*.svg"
|
|
231
|
+
expire_in: 1 week
|
|
232
|
+
only:
|
|
233
|
+
changes:
|
|
234
|
+
- "**/*.ps"
|
|
235
|
+
- "**/*.eps"
|
|
236
|
+
----
|
|
237
|
+
|
|
238
|
+
== Workflow 4: Rails Integration
|
|
239
|
+
|
|
240
|
+
Integrate Postsvg into a Rails application for on-demand conversion.
|
|
241
|
+
|
|
242
|
+
**Service Object:**
|
|
243
|
+
|
|
244
|
+
[source,ruby]
|
|
245
|
+
----
|
|
246
|
+
# app/services/postscript_converter_service.rb
|
|
247
|
+
class PostscriptConverterService
|
|
248
|
+
class ConversionError < StandardError; end
|
|
249
|
+
|
|
250
|
+
def initialize(file_or_content, options = {})
|
|
251
|
+
@content = file_or_content.is_a?(String) ?
|
|
252
|
+
file_or_content :
|
|
253
|
+
file_or_content.read
|
|
254
|
+
@options = options
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def convert
|
|
258
|
+
validator = validate_content
|
|
259
|
+
raise ConversionError, validator[:errors].join(', ') unless validator[:valid]
|
|
260
|
+
|
|
261
|
+
converter = Postsvg::Converter.new(@content, strict_mode: @options[:strict])
|
|
262
|
+
svg_content = converter.convert
|
|
263
|
+
|
|
264
|
+
{
|
|
265
|
+
success: true,
|
|
266
|
+
svg: svg_content,
|
|
267
|
+
size: svg_content.bytesize,
|
|
268
|
+
validated: true
|
|
269
|
+
}
|
|
270
|
+
rescue Postsvg::Error => e
|
|
271
|
+
{
|
|
272
|
+
success: false,
|
|
273
|
+
error: e.message,
|
|
274
|
+
error_type: e.class.name
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
def validate_content
|
|
281
|
+
# Basic validation
|
|
282
|
+
return { valid: false, errors: ['Empty content'] } if @content.empty?
|
|
283
|
+
return { valid: false, errors: ['Not PostScript'] } unless postscript?
|
|
284
|
+
|
|
285
|
+
{ valid: true, errors: [] }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def postscript?
|
|
289
|
+
@content.start_with?('%!PS') || @content.start_with?('%!<')
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
----
|
|
293
|
+
|
|
294
|
+
**Controller:**
|
|
295
|
+
|
|
296
|
+
[source,ruby]
|
|
297
|
+
----
|
|
298
|
+
# app/controllers/conversions_controller.rb
|
|
299
|
+
class ConversionsController < ApplicationController
|
|
300
|
+
def create
|
|
301
|
+
uploaded_file = params[:file]
|
|
302
|
+
|
|
303
|
+
result = PostscriptConverterService.new(
|
|
304
|
+
uploaded_file,
|
|
305
|
+
strict: params[:strict] == 'true'
|
|
306
|
+
).convert
|
|
307
|
+
|
|
308
|
+
if result[:success]
|
|
309
|
+
send_data result[:svg],
|
|
310
|
+
filename: "#{uploaded_file.original_filename.sub(/\.(ps|eps)$/i, '')}.svg",
|
|
311
|
+
type: 'image/svg+xml',
|
|
312
|
+
disposition: 'attachment'
|
|
313
|
+
else
|
|
314
|
+
flash[:error] = "Conversion failed: #{result[:error]}"
|
|
315
|
+
redirect_to root_path
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
----
|
|
320
|
+
|
|
321
|
+
**View:**
|
|
322
|
+
|
|
323
|
+
[source,erb]
|
|
324
|
+
----
|
|
325
|
+
<!-- app/views/conversions/new.html.erb -->
|
|
326
|
+
<%= form_with url: conversions_path, multipart: true do |f| %>
|
|
327
|
+
<div class="field">
|
|
328
|
+
<%= f.label :file, "PostScript File" %>
|
|
329
|
+
<%= f.file_field :file, accept: '.ps,.eps' %>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<div class="field">
|
|
333
|
+
<%= f.check_box :strict %>
|
|
334
|
+
<%= f.label :strict, "Strict Mode (fail on unknown operators)" %>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<%= f.submit "Convert to SVG" %>
|
|
338
|
+
<% end %>
|
|
339
|
+
----
|
|
340
|
+
|
|
341
|
+
== Workflow 5: Automated Directory Watch
|
|
342
|
+
|
|
343
|
+
Automatically convert PostScript files when they appear in a directory.
|
|
344
|
+
|
|
345
|
+
[source,ruby]
|
|
346
|
+
----
|
|
347
|
+
#!/usr/bin/env ruby
|
|
348
|
+
# ps-watcher.rb
|
|
349
|
+
|
|
350
|
+
require 'postsvg'
|
|
351
|
+
require 'listen'
|
|
352
|
+
|
|
353
|
+
INPUT_DIR = 'input/'
|
|
354
|
+
OUTPUT_DIR = 'output/'
|
|
355
|
+
|
|
356
|
+
# Ensure output directory exists
|
|
357
|
+
Dir.mkdir(OUTPUT_DIR) unless Dir.exist?(OUTPUT_DIR)
|
|
358
|
+
|
|
359
|
+
puts "Watching #{INPUT_DIR} for PostScript files..."
|
|
360
|
+
puts "Press Ctrl+C to stop"
|
|
361
|
+
|
|
362
|
+
listener = Listen.to(INPUT_DIR, only: /\.(ps|eps)$/i) do |modified, added, removed|
|
|
363
|
+
# Handle new or modified files
|
|
364
|
+
(modified + added).each do |file|
|
|
365
|
+
next unless File.exist?(file) # Skip if file was removed
|
|
366
|
+
|
|
367
|
+
output_file = File.join(
|
|
368
|
+
OUTPUT_DIR,
|
|
369
|
+
File.basename(file).sub(/\.(ps|eps)$/i, '.svg')
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
begin
|
|
373
|
+
puts "Converting #{File.basename(file)}..."
|
|
374
|
+
Postsvg.convert_file(file, output_file)
|
|
375
|
+
puts "✓ Created #{File.basename(output_file)}"
|
|
376
|
+
rescue => e
|
|
377
|
+
puts "✗ Failed #{File.basename(file)}: #{e.message}"
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
listener.start
|
|
383
|
+
sleep
|
|
384
|
+
----
|
|
385
|
+
|
|
386
|
+
**Usage:**
|
|
387
|
+
[source,sh]
|
|
388
|
+
----
|
|
389
|
+
gem install listen
|
|
390
|
+
chmod +x ps-watcher.rb
|
|
391
|
+
./ps-watcher.rb
|
|
392
|
+
----
|
|
393
|
+
|
|
394
|
+
== Workflow 6: Quality Assurance Pipeline
|
|
395
|
+
|
|
396
|
+
Comprehensive quality checks before production deployment.
|
|
397
|
+
|
|
398
|
+
[source,ruby]
|
|
399
|
+
----
|
|
400
|
+
#!/usr/bin/env ruby
|
|
401
|
+
# qa-pipeline.rb
|
|
402
|
+
|
|
403
|
+
require 'postsvg'
|
|
404
|
+
require 'fileutils'
|
|
405
|
+
|
|
406
|
+
class QAPipeline
|
|
407
|
+
def initialize(input_dir, output_dir)
|
|
408
|
+
@input_dir = input_dir
|
|
409
|
+
@output_dir = output_dir
|
|
410
|
+
@report = { passed: [], failed: [], warnings: [] }
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def run
|
|
414
|
+
puts "Starting QA Pipeline..."
|
|
415
|
+
puts "=" * 50
|
|
416
|
+
|
|
417
|
+
files = Dir.glob(File.join(@input_dir, '*.{ps,eps}'))
|
|
418
|
+
puts "Found #{files.size} files"
|
|
419
|
+
puts
|
|
420
|
+
|
|
421
|
+
files.each do |file|
|
|
422
|
+
process_file(file)
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
print_report
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
private
|
|
429
|
+
|
|
430
|
+
def process_file(file)
|
|
431
|
+
basename = File.basename(file)
|
|
432
|
+
puts "Processing: #{basename}"
|
|
433
|
+
|
|
434
|
+
# Step 1: Syntax validation
|
|
435
|
+
unless validate_syntax(file)
|
|
436
|
+
@report[:failed] << "#{basename}: Syntax validation failed"
|
|
437
|
+
return
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Step 2: Semantic validation
|
|
441
|
+
unless validate_semantic(file)
|
|
442
|
+
@report[:failed] << "#{basename}: Semantic validation failed"
|
|
443
|
+
return
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Step 3: Conversion
|
|
447
|
+
output_file = File.join(@output_dir, basename.sub(/\.(ps|eps)$/i, '.svg'))
|
|
448
|
+
unless convert_file(file, output_file)
|
|
449
|
+
@report[:failed] << "#{basename}: Conversion failed"
|
|
450
|
+
return
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Step 4: Verify output
|
|
454
|
+
unless verify_output(output_file)
|
|
455
|
+
@report[:warnings] << "#{basename}: Output verification warnings"
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
@report[:passed] << basename
|
|
459
|
+
puts "✓ Passed all checks"
|
|
460
|
+
puts
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def validate_syntax(file)
|
|
464
|
+
system("postsvg check --level=syntax --quiet #{file}")
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def validate_semantic(file)
|
|
468
|
+
system("postsvg check --level=semantic --quiet #{file}")
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def convert_file(input, output)
|
|
472
|
+
Postsvg.convert_file(input, output)
|
|
473
|
+
true
|
|
474
|
+
rescue => e
|
|
475
|
+
puts " Error: #{e.message}"
|
|
476
|
+
false
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def verify_output(file)
|
|
480
|
+
content = File.read(file)
|
|
481
|
+
content.include?('<svg') && content.include?('</svg>')
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def print_report
|
|
485
|
+
puts "=" * 50
|
|
486
|
+
puts "QA Pipeline Report"
|
|
487
|
+
puts "=" * 50
|
|
488
|
+
puts "Passed: #{@report[:passed].size}"
|
|
489
|
+
puts "Failed: #{@report[:failed].size}"
|
|
490
|
+
puts "Warnings: #{@report[:warnings].size}"
|
|
491
|
+
puts
|
|
492
|
+
|
|
493
|
+
if @report[:failed].any?
|
|
494
|
+
puts "Failed files:"
|
|
495
|
+
@report[:failed].each { |f| puts " ✗ #{f}" }
|
|
496
|
+
puts
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
if @report[:warnings].any?
|
|
500
|
+
puts "Warnings:"
|
|
501
|
+
@report[:warnings].each { |w| puts " ⚠ #{w}" }
|
|
502
|
+
puts
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
exit(@report[:failed].empty? ? 0 : 1)
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Run pipeline
|
|
510
|
+
QAPipeline.new('input/', 'output/').run
|
|
511
|
+
----
|
|
512
|
+
|
|
513
|
+
== Workflow 7: Performance Monitoring
|
|
514
|
+
|
|
515
|
+
Monitor conversion performance for large batches.
|
|
516
|
+
|
|
517
|
+
[source,ruby]
|
|
518
|
+
----
|
|
519
|
+
#!/usr/bin/env ruby
|
|
520
|
+
# performance-monitor.rb
|
|
521
|
+
|
|
522
|
+
require 'postsvg'
|
|
523
|
+
require 'benchmark'
|
|
524
|
+
|
|
525
|
+
files = Dir.glob('*.ps')
|
|
526
|
+
results = []
|
|
527
|
+
|
|
528
|
+
puts "Performance Monitoring Report"
|
|
529
|
+
puts "=" * 60
|
|
530
|
+
puts
|
|
531
|
+
|
|
532
|
+
files.each do |file|
|
|
533
|
+
size = File.size(file)
|
|
534
|
+
|
|
535
|
+
time = Benchmark.measure do
|
|
536
|
+
Postsvg.convert_file(file, file.sub('.ps', '.svg'))
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
results << {
|
|
540
|
+
file: file,
|
|
541
|
+
size: size,
|
|
542
|
+
time: time.real,
|
|
543
|
+
rate: size / time.real / 1024 # KB/s
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
printf "%-30s %8d bytes %6.2f sec %8.1f KB/s\n",
|
|
547
|
+
File.basename(file), size, time.real, size / time.real / 1024
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
puts
|
|
551
|
+
puts "=" * 60
|
|
552
|
+
puts "Summary:"
|
|
553
|
+
puts " Total files: #{results.size}"
|
|
554
|
+
puts " Total size: #{results.sum { |r| r[:size] } / 1024} KB"
|
|
555
|
+
puts " Total time: #{'%.2f' % results.sum { |r| r[:time] }} sec"
|
|
556
|
+
puts " Average: #{'%.1f' % (results.sum { |r| r[:rate] } / results.size)} KB/s"
|
|
557
|
+
puts " Fastest: #{'%.1f' % results.max_by { |r| r[:rate] }[:rate]} KB/s"
|
|
558
|
+
puts " Slowest: #{'%.1f' % results.min_by { |r| r[:rate] }[:rate]} KB/s"
|
|
559
|
+
----
|
|
560
|
+
|
|
561
|
+
== Next Steps
|
|
562
|
+
|
|
563
|
+
After mastering these common workflows:
|
|
564
|
+
|
|
565
|
+
* Learn link:../advanced-topics.adoc[Advanced Topics] for complex scenarios
|
|
566
|
+
* Review link:../api-reference.adoc[API Reference] for detailed API usage
|
|
567
|
+
* Check link:../cli-reference.adoc[CLI Reference] for all command options
|
|
568
|
+
* See link:../development.adoc[Development Guide] to extend Postsvg
|
|
569
|
+
* Visit link:../troubleshooting.adoc[Troubleshooting] if you encounter issues
|
|
570
|
+
|
|
571
|
+
== Bibliography
|
|
572
|
+
|
|
573
|
+
* link:basic-usage.adoc[Basic Usage Guide]
|
|
574
|
+
* link:first-conversion.adoc[First Conversion Tutorial]
|
|
575
|
+
* link:../api-reference.adoc[API Reference]
|
|
576
|
+
* link:../cli-reference.adoc[CLI Reference]
|
|
577
|
+
* link:../advanced-topics.adoc[Advanced Topics]
|