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,891 @@
|
|
|
1
|
+
= SvgGenerator Class
|
|
2
|
+
:page-nav_order: 7
|
|
3
|
+
:page-parent: API Reference
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
The [`SvgGenerator`](../../lib/postsvg/svg_generator.rb:7) class is responsible for generating SVG output from PostScript graphics operations. It uses the Moxml library to construct valid SVG documents with proper XML structure, namespaces, and formatting.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../api-reference.adoc[API Reference Overview]
|
|
13
|
+
* link:interpreter.adoc[Interpreter Class]
|
|
14
|
+
* link:graphics-state.adoc[GraphicsState Class]
|
|
15
|
+
* link:path-builder.adoc[PathBuilder Class]
|
|
16
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
17
|
+
|
|
18
|
+
== Concepts
|
|
19
|
+
|
|
20
|
+
**SVG (Scalable Vector Graphics)**:: An XML-based vector image format for 2D graphics that can be scaled to any size without loss of quality.
|
|
21
|
+
|
|
22
|
+
**Moxml**:: An XML manipulation library used by Postsvg to construct well-formed SVG documents programmatically.
|
|
23
|
+
|
|
24
|
+
**Path Data**:: A string representation of geometric shapes using SVG path commands (M, L, C, Z, etc.).
|
|
25
|
+
|
|
26
|
+
**Graphics State**:: The current drawing parameters including stroke color, fill color, line width, and other rendering attributes.
|
|
27
|
+
|
|
28
|
+
**ViewBox**:: An SVG attribute that defines the position and dimension of the SVG viewport in user space.
|
|
29
|
+
|
|
30
|
+
== Class Overview
|
|
31
|
+
|
|
32
|
+
The [`SvgGenerator`](../../lib/postsvg/svg_generator.rb:7) class is defined in [`lib/postsvg/svg_generator.rb`](../../lib/postsvg/svg_generator.rb:1).
|
|
33
|
+
|
|
34
|
+
**Responsibilities:**
|
|
35
|
+
|
|
36
|
+
* Accumulate path data from PostScript operations
|
|
37
|
+
* Convert PostScript coordinates to SVG path data
|
|
38
|
+
* Apply graphics state attributes (stroke, fill, line width)
|
|
39
|
+
* Generate complete SVG documents with proper structure
|
|
40
|
+
* Format SVG output for readability and correctness
|
|
41
|
+
|
|
42
|
+
**Dependencies:**
|
|
43
|
+
|
|
44
|
+
* `moxml` gem - XML document construction and manipulation
|
|
45
|
+
* [`GraphicsState`](../../lib/postsvg/graphics_state.rb:5) - For stroke/fill colors and line properties
|
|
46
|
+
|
|
47
|
+
**Design Characteristics:**
|
|
48
|
+
|
|
49
|
+
* Stateful accumulator pattern
|
|
50
|
+
* Separation of path data collection and SVG generation
|
|
51
|
+
* Format-specific number handling for clean SVG output
|
|
52
|
+
|
|
53
|
+
== Class Methods
|
|
54
|
+
|
|
55
|
+
=== new
|
|
56
|
+
|
|
57
|
+
Create a new SvgGenerator instance.
|
|
58
|
+
|
|
59
|
+
**Syntax:**
|
|
60
|
+
|
|
61
|
+
[source,ruby]
|
|
62
|
+
----
|
|
63
|
+
generator = Postsvg::SvgGenerator.new <1>
|
|
64
|
+
----
|
|
65
|
+
<1> Initialize an empty SVG generator
|
|
66
|
+
|
|
67
|
+
**Returns:**
|
|
68
|
+
|
|
69
|
+
New `SvgGenerator` instance ready to accumulate paths
|
|
70
|
+
|
|
71
|
+
**Source:**
|
|
72
|
+
|
|
73
|
+
[`lib/postsvg/svg_generator.rb:8-10`](../../lib/postsvg/svg_generator.rb:8)
|
|
74
|
+
|
|
75
|
+
.Create SVG generator
|
|
76
|
+
[example]
|
|
77
|
+
====
|
|
78
|
+
[source,ruby]
|
|
79
|
+
----
|
|
80
|
+
require 'postsvg'
|
|
81
|
+
|
|
82
|
+
# Create generator
|
|
83
|
+
generator = Postsvg::SvgGenerator.new
|
|
84
|
+
|
|
85
|
+
# Generator starts with empty path collection
|
|
86
|
+
# Ready to receive add_path calls
|
|
87
|
+
----
|
|
88
|
+
====
|
|
89
|
+
|
|
90
|
+
== Instance Methods
|
|
91
|
+
|
|
92
|
+
=== add_path
|
|
93
|
+
|
|
94
|
+
Add a path with graphics state to the SVG output.
|
|
95
|
+
|
|
96
|
+
**Syntax:**
|
|
97
|
+
|
|
98
|
+
[source,ruby]
|
|
99
|
+
----
|
|
100
|
+
generator.add_path(path, graphics_state, operation) <1>
|
|
101
|
+
----
|
|
102
|
+
<1> Add path data with rendering instructions
|
|
103
|
+
|
|
104
|
+
**Where:**
|
|
105
|
+
|
|
106
|
+
`path`:: Array of path segments, each a hash with:
|
|
107
|
+
* `:type` - Segment type (`:moveto`, `:lineto`, `:curveto`, `:closepath`)
|
|
108
|
+
* `:x`, `:y` - Coordinates for moveto/lineto
|
|
109
|
+
* `:x1`, `:y1`, `:x2`, `:y2`, `:x3`, `:y3` - Control and end points for curveto
|
|
110
|
+
|
|
111
|
+
`graphics_state`:: [`GraphicsState`](graphics-state.adoc) instance containing:
|
|
112
|
+
* `stroke_color_hex` - Stroke color as hex string
|
|
113
|
+
* `fill_color_hex` - Fill color as hex string
|
|
114
|
+
* `line_width` - Stroke width in user units
|
|
115
|
+
|
|
116
|
+
`operation`:: Symbol indicating paint operation
|
|
117
|
+
* `:stroke` - Draw path outline
|
|
118
|
+
* `:fill` - Fill path interior
|
|
119
|
+
|
|
120
|
+
**Returns:**
|
|
121
|
+
|
|
122
|
+
`nil` (modifies internal state)
|
|
123
|
+
|
|
124
|
+
**Source:**
|
|
125
|
+
|
|
126
|
+
[`lib/postsvg/svg_generator.rb:12-24`](../../lib/postsvg/svg_generator.rb:12)
|
|
127
|
+
|
|
128
|
+
.Add stroke path
|
|
129
|
+
[example]
|
|
130
|
+
====
|
|
131
|
+
[source,ruby]
|
|
132
|
+
----
|
|
133
|
+
require 'postsvg'
|
|
134
|
+
|
|
135
|
+
generator = Postsvg::SvgGenerator.new
|
|
136
|
+
|
|
137
|
+
# Create path data
|
|
138
|
+
path = [
|
|
139
|
+
{ type: :moveto, x: 10, y: 10 },
|
|
140
|
+
{ type: :lineto, x: 100, y: 10 },
|
|
141
|
+
{ type: :lineto, x: 100, y: 100 }
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
# Create graphics state
|
|
145
|
+
state = Postsvg::GraphicsState.new
|
|
146
|
+
state.stroke_color = [0, 0, 0] # Black
|
|
147
|
+
state.line_width = 2.0
|
|
148
|
+
|
|
149
|
+
# Add path to generator
|
|
150
|
+
generator.add_path(path, state, :stroke)
|
|
151
|
+
|
|
152
|
+
# Path is stored for later SVG generation
|
|
153
|
+
----
|
|
154
|
+
====
|
|
155
|
+
|
|
156
|
+
.Add fill path
|
|
157
|
+
[example]
|
|
158
|
+
====
|
|
159
|
+
[source,ruby]
|
|
160
|
+
----
|
|
161
|
+
# Create filled rectangle
|
|
162
|
+
path = [
|
|
163
|
+
{ type: :moveto, x: 50, y: 50 },
|
|
164
|
+
{ type: :lineto, x: 150, y: 50 },
|
|
165
|
+
{ type: :lineto, x: 150, y: 150 },
|
|
166
|
+
{ type: :lineto, x: 50, y: 150 },
|
|
167
|
+
{ type: :closepath }
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
state = Postsvg::GraphicsState.new
|
|
171
|
+
state.fill_color = [1.0, 0, 0] # Red
|
|
172
|
+
|
|
173
|
+
generator.add_path(path, state, :fill)
|
|
174
|
+
----
|
|
175
|
+
====
|
|
176
|
+
|
|
177
|
+
.Add curved path
|
|
178
|
+
[example]
|
|
179
|
+
====
|
|
180
|
+
[source,ruby]
|
|
181
|
+
----
|
|
182
|
+
# Create bezier curve
|
|
183
|
+
path = [
|
|
184
|
+
{ type: :moveto, x: 10, y: 100 },
|
|
185
|
+
{
|
|
186
|
+
type: :curveto,
|
|
187
|
+
x1: 40, y1: 10, # First control point
|
|
188
|
+
x2: 60, y2: 10, # Second control point
|
|
189
|
+
x3: 90, y3: 100 # End point
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
state = Postsvg::GraphicsState.new
|
|
194
|
+
state.stroke_color = [0, 0, 1.0] # Blue
|
|
195
|
+
state.line_width = 3.0
|
|
196
|
+
|
|
197
|
+
generator.add_path(path, state, :stroke)
|
|
198
|
+
----
|
|
199
|
+
====
|
|
200
|
+
|
|
201
|
+
**Empty Path Handling:**
|
|
202
|
+
|
|
203
|
+
Empty paths are automatically ignored:
|
|
204
|
+
|
|
205
|
+
[source,ruby]
|
|
206
|
+
----
|
|
207
|
+
generator.add_path([], state, :stroke) # No-op, returns immediately
|
|
208
|
+
----
|
|
209
|
+
|
|
210
|
+
=== generate
|
|
211
|
+
|
|
212
|
+
Generate the complete SVG document from accumulated paths.
|
|
213
|
+
|
|
214
|
+
**Syntax:**
|
|
215
|
+
|
|
216
|
+
[source,ruby]
|
|
217
|
+
----
|
|
218
|
+
svg_output = generator.generate(
|
|
219
|
+
width: width,
|
|
220
|
+
height: height,
|
|
221
|
+
viewbox: viewbox_string
|
|
222
|
+
) <1>
|
|
223
|
+
----
|
|
224
|
+
<1> Generate SVG document with specified dimensions
|
|
225
|
+
|
|
226
|
+
**Where:**
|
|
227
|
+
|
|
228
|
+
`width`:: Integer or float specifying SVG width in user units
|
|
229
|
+
|
|
230
|
+
`height`:: Integer or float specifying SVG height in user units
|
|
231
|
+
|
|
232
|
+
`viewbox`:: String defining the SVG viewport (e.g., `"0 0 200 200"`)
|
|
233
|
+
* Format: `"min-x min-y width height"`
|
|
234
|
+
* Defines coordinate system for paths
|
|
235
|
+
|
|
236
|
+
**Returns:**
|
|
237
|
+
|
|
238
|
+
String containing complete, well-formed SVG document with:
|
|
239
|
+
* XML declaration
|
|
240
|
+
* SVG root element with namespace
|
|
241
|
+
* All accumulated path elements
|
|
242
|
+
* Proper formatting and indentation
|
|
243
|
+
|
|
244
|
+
**Source:**
|
|
245
|
+
|
|
246
|
+
[`lib/postsvg/svg_generator.rb:26-84`](../../lib/postsvg/svg_generator.rb:26)
|
|
247
|
+
|
|
248
|
+
.Generate basic SVG
|
|
249
|
+
[example]
|
|
250
|
+
====
|
|
251
|
+
[source,ruby]
|
|
252
|
+
----
|
|
253
|
+
require 'postsvg'
|
|
254
|
+
|
|
255
|
+
generator = Postsvg::SvgGenerator.new
|
|
256
|
+
|
|
257
|
+
# Add simple line
|
|
258
|
+
path = [
|
|
259
|
+
{ type: :moveto, x: 0, y: 0 },
|
|
260
|
+
{ type: :lineto, x: 100, y: 100 }
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
state = Postsvg::GraphicsState.new
|
|
264
|
+
state.stroke_color = [0, 0, 0]
|
|
265
|
+
state.line_width = 1.0
|
|
266
|
+
|
|
267
|
+
generator.add_path(path, state, :stroke)
|
|
268
|
+
|
|
269
|
+
# Generate SVG
|
|
270
|
+
svg = generator.generate(
|
|
271
|
+
width: 200,
|
|
272
|
+
height: 200,
|
|
273
|
+
viewbox: "0 0 200 200"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
puts svg
|
|
277
|
+
# Output:
|
|
278
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
279
|
+
# <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
|
|
280
|
+
# <path d="M 0 0 L 100 100" fill="none" stroke="#000000" stroke-width="1" />
|
|
281
|
+
# </svg>
|
|
282
|
+
----
|
|
283
|
+
====
|
|
284
|
+
|
|
285
|
+
.Generate SVG with multiple paths
|
|
286
|
+
[example]
|
|
287
|
+
====
|
|
288
|
+
[source,ruby]
|
|
289
|
+
----
|
|
290
|
+
generator = Postsvg::SvgGenerator.new
|
|
291
|
+
|
|
292
|
+
# Add stroke path
|
|
293
|
+
stroke_path = [
|
|
294
|
+
{ type: :moveto, x: 10, y: 10 },
|
|
295
|
+
{ type: :lineto, x: 90, y: 90 }
|
|
296
|
+
]
|
|
297
|
+
stroke_state = Postsvg::GraphicsState.new
|
|
298
|
+
stroke_state.stroke_color = [0, 0, 0]
|
|
299
|
+
stroke_state.line_width = 2.0
|
|
300
|
+
generator.add_path(stroke_path, stroke_state, :stroke)
|
|
301
|
+
|
|
302
|
+
# Add fill path
|
|
303
|
+
fill_path = [
|
|
304
|
+
{ type: :moveto, x: 50, y: 50 },
|
|
305
|
+
{ type: :lineto, x: 100, y: 50 },
|
|
306
|
+
{ type: :lineto, x: 75, y: 100 },
|
|
307
|
+
{ type: :closepath }
|
|
308
|
+
]
|
|
309
|
+
fill_state = Postsvg::GraphicsState.new
|
|
310
|
+
fill_state.fill_color = [1.0, 0, 0]
|
|
311
|
+
generator.add_path(fill_path, fill_state, :fill)
|
|
312
|
+
|
|
313
|
+
# Generate combined SVG
|
|
314
|
+
svg = generator.generate(
|
|
315
|
+
width: 150,
|
|
316
|
+
height: 150,
|
|
317
|
+
viewbox: "0 0 150 150"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
File.write('output.svg', svg)
|
|
321
|
+
----
|
|
322
|
+
====
|
|
323
|
+
|
|
324
|
+
.Generate with custom viewBox
|
|
325
|
+
[example]
|
|
326
|
+
====
|
|
327
|
+
[source,ruby]
|
|
328
|
+
----
|
|
329
|
+
# Create content in 0-100 coordinate space
|
|
330
|
+
generator = Postsvg::SvgGenerator.new
|
|
331
|
+
|
|
332
|
+
path = [
|
|
333
|
+
{ type: :moveto, x: 25, y: 25 },
|
|
334
|
+
{ type: :lineto, x: 75, y: 75 }
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
state = Postsvg::GraphicsState.new
|
|
338
|
+
state.stroke_color = [0, 0, 0]
|
|
339
|
+
state.line_width = 3.0
|
|
340
|
+
generator.add_path(path, state, :stroke)
|
|
341
|
+
|
|
342
|
+
# Scale to 500x500 display while maintaining 0-100 coordinate system
|
|
343
|
+
svg = generator.generate(
|
|
344
|
+
width: 500,
|
|
345
|
+
height: 500,
|
|
346
|
+
viewbox: "0 0 100 100"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# The line will be scaled proportionally
|
|
350
|
+
# Physical display: 500×500 pixels
|
|
351
|
+
# Coordinate space: 0-100 units
|
|
352
|
+
----
|
|
353
|
+
====
|
|
354
|
+
|
|
355
|
+
== Private Methods
|
|
356
|
+
|
|
357
|
+
=== build_path_data
|
|
358
|
+
|
|
359
|
+
Convert path segments to SVG path data string.
|
|
360
|
+
|
|
361
|
+
**Syntax:**
|
|
362
|
+
|
|
363
|
+
[source,ruby]
|
|
364
|
+
----
|
|
365
|
+
path_data = build_path_data(path) <1>
|
|
366
|
+
----
|
|
367
|
+
<1> Internal method for path data conversion
|
|
368
|
+
|
|
369
|
+
**Where:**
|
|
370
|
+
|
|
371
|
+
`path`:: Array of path segment hashes
|
|
372
|
+
|
|
373
|
+
**Returns:**
|
|
374
|
+
|
|
375
|
+
String containing SVG path data (e.g., `"M 10 10 L 50 50 Z"`)
|
|
376
|
+
|
|
377
|
+
**Source:**
|
|
378
|
+
|
|
379
|
+
[`lib/postsvg/svg_generator.rb:88-107`](../../lib/postsvg/svg_generator.rb:88)
|
|
380
|
+
|
|
381
|
+
**Path Commands Generated:**
|
|
382
|
+
|
|
383
|
+
* `M x y` - Move to absolute coordinates
|
|
384
|
+
* `L x y` - Line to absolute coordinates
|
|
385
|
+
* `C x1 y1, x2 y2, x3 y3` - Cubic Bézier curve
|
|
386
|
+
* `Z` - Close path
|
|
387
|
+
|
|
388
|
+
.Path data generation
|
|
389
|
+
[example]
|
|
390
|
+
====
|
|
391
|
+
[source,ruby]
|
|
392
|
+
----
|
|
393
|
+
# Input path segments
|
|
394
|
+
path = [
|
|
395
|
+
{ type: :moveto, x: 10, y: 20 },
|
|
396
|
+
{ type: :lineto, x: 30, y: 40 },
|
|
397
|
+
{ type: :closepath }
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
# Generates: "M 10 20 L 30 40 Z"
|
|
401
|
+
----
|
|
402
|
+
====
|
|
403
|
+
|
|
404
|
+
=== build_moxml_path_element
|
|
405
|
+
|
|
406
|
+
Create a Moxml path element with attributes.
|
|
407
|
+
|
|
408
|
+
**Syntax:**
|
|
409
|
+
|
|
410
|
+
[source,ruby]
|
|
411
|
+
----
|
|
412
|
+
path_elem = build_moxml_path_element(doc, path) <1>
|
|
413
|
+
----
|
|
414
|
+
<1> Internal method for creating path elements
|
|
415
|
+
|
|
416
|
+
**Where:**
|
|
417
|
+
|
|
418
|
+
`doc`:: Moxml document instance
|
|
419
|
+
|
|
420
|
+
`path`:: Hash containing path data and attributes:
|
|
421
|
+
* `:d` - Path data string
|
|
422
|
+
* `:operation` - `:stroke` or `:fill`
|
|
423
|
+
* `:stroke` - Stroke color hex
|
|
424
|
+
* `:fill` - Fill color hex
|
|
425
|
+
* `:stroke_width` - Line width
|
|
426
|
+
|
|
427
|
+
**Returns:**
|
|
428
|
+
|
|
429
|
+
Moxml element representing SVG `<path>` element
|
|
430
|
+
|
|
431
|
+
**Source:**
|
|
432
|
+
|
|
433
|
+
[`lib/postsvg/svg_generator.rb:109-124`](../../lib/postsvg/svg_generator.rb:109)
|
|
434
|
+
|
|
435
|
+
**Attribute Mapping:**
|
|
436
|
+
|
|
437
|
+
[cols="1,1,2",options="header"]
|
|
438
|
+
|===
|
|
439
|
+
| Operation | Attributes | Example
|
|
440
|
+
|
|
441
|
+
| `:stroke`
|
|
442
|
+
| `fill="none"`, stroke attributes
|
|
443
|
+
| `<path fill="none" stroke="#000" stroke-width="2"/>`
|
|
444
|
+
|
|
445
|
+
| `:fill`
|
|
446
|
+
| `stroke="none"`, fill attribute
|
|
447
|
+
| `<path stroke="none" fill="#f00"/>`
|
|
448
|
+
|===
|
|
449
|
+
|
|
450
|
+
=== num_fmt
|
|
451
|
+
|
|
452
|
+
Format numbers for SVG output with intelligent precision.
|
|
453
|
+
|
|
454
|
+
**Syntax:**
|
|
455
|
+
|
|
456
|
+
[source,ruby]
|
|
457
|
+
----
|
|
458
|
+
formatted = num_fmt(number) <1>
|
|
459
|
+
----
|
|
460
|
+
<1> Internal method for number formatting
|
|
461
|
+
|
|
462
|
+
**Where:**
|
|
463
|
+
|
|
464
|
+
`number`:: Numeric value to format (Integer, Float, or special values)
|
|
465
|
+
|
|
466
|
+
**Returns:**
|
|
467
|
+
|
|
468
|
+
String representation optimized for SVG:
|
|
469
|
+
* Integers: No decimal point (e.g., `"10"`)
|
|
470
|
+
* Floats: Up to 3 decimal places, trailing zeros removed
|
|
471
|
+
* `nil`, `NaN`, `Infinity`: `"0"`
|
|
472
|
+
|
|
473
|
+
**Source:**
|
|
474
|
+
|
|
475
|
+
[`lib/postsvg/svg_generator.rb:126-138`](../../lib/postsvg/svg_generator.rb:126)
|
|
476
|
+
|
|
477
|
+
.Number formatting examples
|
|
478
|
+
[example]
|
|
479
|
+
====
|
|
480
|
+
[source,ruby]
|
|
481
|
+
----
|
|
482
|
+
num_fmt(10) # → "10"
|
|
483
|
+
num_fmt(10.0) # → "10"
|
|
484
|
+
num_fmt(10.5) # → "10.5"
|
|
485
|
+
num_fmt(10.123) # → "10.123"
|
|
486
|
+
num_fmt(10.1234) # → "10.123" (rounded)
|
|
487
|
+
num_fmt(10.1000) # → "10.1" (trailing zeros removed)
|
|
488
|
+
num_fmt(nil) # → "0"
|
|
489
|
+
num_fmt(Float::NAN) # → "0"
|
|
490
|
+
----
|
|
491
|
+
====
|
|
492
|
+
|
|
493
|
+
**Rationale:**
|
|
494
|
+
|
|
495
|
+
Clean number formatting produces:
|
|
496
|
+
* Smaller SVG file sizes
|
|
497
|
+
* More readable path data
|
|
498
|
+
* Consistent output across platforms
|
|
499
|
+
* Proper handling of edge cases
|
|
500
|
+
|
|
501
|
+
== Attributes
|
|
502
|
+
|
|
503
|
+
The `SvgGenerator` class maintains internal state but does not expose public attributes. Access to paths is through the `generate` method.
|
|
504
|
+
|
|
505
|
+
== Usage Patterns
|
|
506
|
+
|
|
507
|
+
=== Pattern 1: Simple SVG Generation
|
|
508
|
+
|
|
509
|
+
[source,ruby]
|
|
510
|
+
----
|
|
511
|
+
require 'postsvg'
|
|
512
|
+
|
|
513
|
+
# Create generator
|
|
514
|
+
generator = Postsvg::SvgGenerator.new
|
|
515
|
+
|
|
516
|
+
# Add paths during PostScript interpretation
|
|
517
|
+
interpreter.execute(tokens) do |path, state, op|
|
|
518
|
+
generator.add_path(path, state, op)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Generate final SVG
|
|
522
|
+
svg = generator.generate(
|
|
523
|
+
width: 400,
|
|
524
|
+
height: 300,
|
|
525
|
+
viewbox: "0 0 400 300"
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
File.write('output.svg', svg)
|
|
529
|
+
----
|
|
530
|
+
|
|
531
|
+
=== Pattern 2: Accumulating Multiple Graphics
|
|
532
|
+
|
|
533
|
+
[source,ruby]
|
|
534
|
+
----
|
|
535
|
+
require 'postsvg'
|
|
536
|
+
|
|
537
|
+
generator = Postsvg::SvgGenerator.new
|
|
538
|
+
|
|
539
|
+
# Add multiple shapes
|
|
540
|
+
shapes = [
|
|
541
|
+
{ path: rectangle_path, state: black_stroke, op: :stroke },
|
|
542
|
+
{ path: circle_path, state: red_fill, op: :fill },
|
|
543
|
+
{ path: curve_path, state: blue_stroke, op: :stroke }
|
|
544
|
+
]
|
|
545
|
+
|
|
546
|
+
shapes.each do |shape|
|
|
547
|
+
generator.add_path(shape[:path], shape[:state], shape[:op])
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Generate combined SVG
|
|
551
|
+
svg = generator.generate(
|
|
552
|
+
width: 500,
|
|
553
|
+
height: 500,
|
|
554
|
+
viewbox: "0 0 500 500"
|
|
555
|
+
)
|
|
556
|
+
----
|
|
557
|
+
|
|
558
|
+
=== Pattern 3: Responsive SVG Output
|
|
559
|
+
|
|
560
|
+
[source,ruby]
|
|
561
|
+
----
|
|
562
|
+
require 'postsvg'
|
|
563
|
+
|
|
564
|
+
def generate_responsive_svg(paths, bbox)
|
|
565
|
+
generator = Postsvg::SvgGenerator.new
|
|
566
|
+
|
|
567
|
+
paths.each do |path_data|
|
|
568
|
+
generator.add_path(
|
|
569
|
+
path_data[:path],
|
|
570
|
+
path_data[:state],
|
|
571
|
+
path_data[:operation]
|
|
572
|
+
)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# Use viewBox for scalability
|
|
576
|
+
# SVG will scale to container while maintaining aspect ratio
|
|
577
|
+
generator.generate(
|
|
578
|
+
width: bbox[:width],
|
|
579
|
+
height: bbox[:height],
|
|
580
|
+
viewbox: "#{bbox[:llx]} #{bbox[:lly]} #{bbox[:width]} #{bbox[:height]}"
|
|
581
|
+
)
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Usage
|
|
585
|
+
svg = generate_responsive_svg(collected_paths, bounding_box)
|
|
586
|
+
----
|
|
587
|
+
|
|
588
|
+
=== Pattern 4: Progressive SVG Construction
|
|
589
|
+
|
|
590
|
+
[source,ruby]
|
|
591
|
+
----
|
|
592
|
+
require 'postsvg'
|
|
593
|
+
|
|
594
|
+
class ProgressiveSvgBuilder
|
|
595
|
+
def initialize
|
|
596
|
+
@generator = Postsvg::SvgGenerator.new
|
|
597
|
+
@path_count = 0
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def add_shape(path, state, operation)
|
|
601
|
+
@generator.add_path(path, state, operation)
|
|
602
|
+
@path_count += 1
|
|
603
|
+
|
|
604
|
+
puts "Added path #{@path_count}: #{operation}"
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def finalize(width, height)
|
|
608
|
+
viewbox = "0 0 #{width} #{height}"
|
|
609
|
+
svg = @generator.generate(
|
|
610
|
+
width: width,
|
|
611
|
+
height: height,
|
|
612
|
+
viewbox: viewbox
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
puts "Generated SVG with #{@path_count} paths"
|
|
616
|
+
svg
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Usage
|
|
621
|
+
builder = ProgressiveSvgBuilder.new
|
|
622
|
+
|
|
623
|
+
# Add shapes as they're created
|
|
624
|
+
builder.add_shape(path1, state1, :stroke)
|
|
625
|
+
builder.add_shape(path2, state2, :fill)
|
|
626
|
+
builder.add_shape(path3, state3, :stroke)
|
|
627
|
+
|
|
628
|
+
# Generate final output
|
|
629
|
+
svg = builder.finalize(600, 400)
|
|
630
|
+
----
|
|
631
|
+
|
|
632
|
+
== Thread Safety
|
|
633
|
+
|
|
634
|
+
The `SvgGenerator` class is **not thread-safe**. It maintains mutable internal state (`@paths` array) that should not be shared across threads.
|
|
635
|
+
|
|
636
|
+
.Correct multi-threaded usage
|
|
637
|
+
[example]
|
|
638
|
+
====
|
|
639
|
+
[source,ruby]
|
|
640
|
+
----
|
|
641
|
+
# Bad: Sharing generator across threads
|
|
642
|
+
generator = Postsvg::SvgGenerator.new
|
|
643
|
+
threads = paths_by_thread.map do |paths|
|
|
644
|
+
Thread.new do
|
|
645
|
+
paths.each { |p| generator.add_path(p[:path], p[:state], p[:op]) }
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
# RACE CONDITION: Multiple threads modifying @paths array
|
|
649
|
+
|
|
650
|
+
# Good: Each thread has own generator
|
|
651
|
+
threads = paths_by_thread.map do |paths|
|
|
652
|
+
Thread.new do
|
|
653
|
+
generator = Postsvg::SvgGenerator.new
|
|
654
|
+
paths.each { |p| generator.add_path(p[:path], p[:state], p[:op]) }
|
|
655
|
+
generator.generate(width: 500, height: 500, viewbox: "0 0 500 500")
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
results = threads.map(&:value)
|
|
660
|
+
----
|
|
661
|
+
====
|
|
662
|
+
|
|
663
|
+
== Performance Considerations
|
|
664
|
+
|
|
665
|
+
**Time Complexity:**
|
|
666
|
+
|
|
667
|
+
* `add_path`: O(n) where n = number of segments in path (path data conversion)
|
|
668
|
+
* `generate`: O(m) where m = total number of paths (XML construction)
|
|
669
|
+
* Overall: O(n × m) for complete SVG generation
|
|
670
|
+
|
|
671
|
+
**Space Complexity:**
|
|
672
|
+
|
|
673
|
+
* Memory usage: O(p) where p = total path data size
|
|
674
|
+
* Each path stores: original segments, converted path data, graphics state attributes
|
|
675
|
+
* SVG output size typically 2-5× larger than compressed PostScript
|
|
676
|
+
|
|
677
|
+
**Optimization Tips:**
|
|
678
|
+
|
|
679
|
+
1. **Minimize path segments**: Simplify paths when possible
|
|
680
|
+
2. **Batch operations**: Add multiple paths before calling `generate`
|
|
681
|
+
3. **Reuse generators**: Create once per document, not per path
|
|
682
|
+
4. **Monitor memory**: Large documents with many paths can consume significant memory
|
|
683
|
+
|
|
684
|
+
.Performance monitoring
|
|
685
|
+
[example]
|
|
686
|
+
====
|
|
687
|
+
[source,ruby]
|
|
688
|
+
----
|
|
689
|
+
require 'postsvg'
|
|
690
|
+
require 'benchmark'
|
|
691
|
+
|
|
692
|
+
generator = Postsvg::SvgGenerator.new
|
|
693
|
+
path_count = 0
|
|
694
|
+
|
|
695
|
+
# Measure path addition
|
|
696
|
+
add_time = Benchmark.measure do
|
|
697
|
+
1000.times do |i|
|
|
698
|
+
path = generate_test_path(i)
|
|
699
|
+
state = create_graphics_state(i)
|
|
700
|
+
generator.add_path(path, state, :stroke)
|
|
701
|
+
path_count += 1
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
puts "Added #{path_count} paths in #{'%.3f' % add_time.real}s"
|
|
706
|
+
puts "Rate: #{(path_count / add_time.real).to_i} paths/sec"
|
|
707
|
+
|
|
708
|
+
# Measure SVG generation
|
|
709
|
+
gen_time = Benchmark.measure do
|
|
710
|
+
@svg = generator.generate(
|
|
711
|
+
width: 1000,
|
|
712
|
+
height: 1000,
|
|
713
|
+
viewbox: "0 0 1000 1000"
|
|
714
|
+
)
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
puts "Generated SVG in #{'%.3f' % gen_time.real}s"
|
|
718
|
+
puts "Output size: #{@svg.bytesize / 1024} KB"
|
|
719
|
+
----
|
|
720
|
+
====
|
|
721
|
+
|
|
722
|
+
**Typical Performance:**
|
|
723
|
+
|
|
724
|
+
* 100 simple paths: <10ms generation time
|
|
725
|
+
* 1,000 simple paths: ~50ms generation time
|
|
726
|
+
* 10,000 simple paths: ~500ms generation time
|
|
727
|
+
* Complex curves add ~2-3× overhead
|
|
728
|
+
|
|
729
|
+
== Error Handling
|
|
730
|
+
|
|
731
|
+
The `SvgGenerator` class is designed to be robust but may encounter issues:
|
|
732
|
+
|
|
733
|
+
**Common Issues:**
|
|
734
|
+
|
|
735
|
+
1. **Invalid path data**: Segments with missing coordinates
|
|
736
|
+
2. **Nil graphics state**: Missing stroke/fill colors
|
|
737
|
+
3. **Invalid dimensions**: Non-positive width/height
|
|
738
|
+
4. **Moxml errors**: XML generation failures
|
|
739
|
+
|
|
740
|
+
.Defensive SVG generation
|
|
741
|
+
[example]
|
|
742
|
+
====
|
|
743
|
+
[source,ruby]
|
|
744
|
+
----
|
|
745
|
+
require 'postsvg'
|
|
746
|
+
|
|
747
|
+
def safe_generate_svg(paths, width, height, viewbox)
|
|
748
|
+
generator = Postsvg::SvgGenerator.new
|
|
749
|
+
|
|
750
|
+
# Validate inputs
|
|
751
|
+
raise ArgumentError, "Width must be positive" unless width > 0
|
|
752
|
+
raise ArgumentError, "Height must be positive" unless height > 0
|
|
753
|
+
|
|
754
|
+
# Add paths with validation
|
|
755
|
+
paths.each do |path_data|
|
|
756
|
+
next if path_data[:path].nil? || path_data[:path].empty?
|
|
757
|
+
next if path_data[:state].nil?
|
|
758
|
+
|
|
759
|
+
generator.add_path(
|
|
760
|
+
path_data[:path],
|
|
761
|
+
path_data[:state],
|
|
762
|
+
path_data[:operation] || :stroke
|
|
763
|
+
)
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Generate with error handling
|
|
767
|
+
begin
|
|
768
|
+
generator.generate(
|
|
769
|
+
width: width,
|
|
770
|
+
height: height,
|
|
771
|
+
viewbox: viewbox
|
|
772
|
+
)
|
|
773
|
+
rescue => e
|
|
774
|
+
puts "SVG generation error: #{e.message}"
|
|
775
|
+
# Return minimal valid SVG
|
|
776
|
+
minimal_svg(width, height, viewbox)
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
def minimal_svg(width, height, viewbox)
|
|
781
|
+
<<~SVG
|
|
782
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
783
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
784
|
+
width="#{width}"
|
|
785
|
+
height="#{height}"
|
|
786
|
+
viewBox="#{viewbox}">
|
|
787
|
+
</svg>
|
|
788
|
+
SVG
|
|
789
|
+
end
|
|
790
|
+
----
|
|
791
|
+
====
|
|
792
|
+
|
|
793
|
+
== Advanced Usage
|
|
794
|
+
|
|
795
|
+
=== Custom Path Processing
|
|
796
|
+
|
|
797
|
+
[source,ruby]
|
|
798
|
+
----
|
|
799
|
+
require 'postsvg'
|
|
800
|
+
|
|
801
|
+
class CustomSvgGenerator < Postsvg::SvgGenerator
|
|
802
|
+
def add_path(path, graphics_state, operation)
|
|
803
|
+
# Pre-process path data
|
|
804
|
+
optimized_path = optimize_segments(path)
|
|
805
|
+
|
|
806
|
+
# Add with optimization
|
|
807
|
+
super(optimized_path, graphics_state, operation)
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
private
|
|
811
|
+
|
|
812
|
+
def optimize_segments(path)
|
|
813
|
+
# Remove redundant moveto commands
|
|
814
|
+
# Merge collinear line segments
|
|
815
|
+
# Simplify nearly-straight curves
|
|
816
|
+
# etc.
|
|
817
|
+
optimized = []
|
|
818
|
+
|
|
819
|
+
path.each_cons(2) do |seg1, seg2|
|
|
820
|
+
unless redundant?(seg1, seg2)
|
|
821
|
+
optimized << seg1
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
optimized << path.last if path.any?
|
|
826
|
+
optimized
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
def redundant?(seg1, seg2)
|
|
830
|
+
# Custom logic to detect redundant segments
|
|
831
|
+
false
|
|
832
|
+
end
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
# Usage
|
|
836
|
+
generator = CustomSvgGenerator.new
|
|
837
|
+
----
|
|
838
|
+
|
|
839
|
+
=== SVG Post-Processing
|
|
840
|
+
|
|
841
|
+
[source,ruby]
|
|
842
|
+
----
|
|
843
|
+
require 'postsvg'
|
|
844
|
+
|
|
845
|
+
def generate_optimized_svg(paths, width, height, viewbox)
|
|
846
|
+
generator = Postsvg::SvgGenerator.new
|
|
847
|
+
|
|
848
|
+
paths.each do |path_data|
|
|
849
|
+
generator.add_path(path_data[:path], path_data[:state], path_data[:operation])
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
svg = generator.generate(
|
|
853
|
+
width: width,
|
|
854
|
+
height: height,
|
|
855
|
+
viewbox: viewbox
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
# Post-process SVG
|
|
859
|
+
optimize_svg_output(svg)
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
def optimize_svg_output(svg)
|
|
863
|
+
# Remove unnecessary whitespace
|
|
864
|
+
optimized = svg.gsub(/\s+/, ' ')
|
|
865
|
+
|
|
866
|
+
# Compress path data
|
|
867
|
+
optimized.gsub!(/(\d+)\.0+(?=\D|$)/, '\1')
|
|
868
|
+
|
|
869
|
+
# Merge similar attributes
|
|
870
|
+
# Add SVGO-style optimizations
|
|
871
|
+
# etc.
|
|
872
|
+
|
|
873
|
+
optimized
|
|
874
|
+
end
|
|
875
|
+
----
|
|
876
|
+
|
|
877
|
+
== Next Steps
|
|
878
|
+
|
|
879
|
+
* Learn about link:path-builder.adoc[PathBuilder Class] for path construction
|
|
880
|
+
* Review link:graphics-state.adoc[GraphicsState] for rendering attributes
|
|
881
|
+
* See link:interpreter.adoc[Interpreter Class] for execution context
|
|
882
|
+
* Check link:../architecture.adoc[Architecture] for system design
|
|
883
|
+
|
|
884
|
+
== Bibliography
|
|
885
|
+
|
|
886
|
+
* link:interpreter.adoc[Interpreter Class Documentation]
|
|
887
|
+
* link:graphics-state.adoc[GraphicsState Documentation]
|
|
888
|
+
* link:path-builder.adoc[PathBuilder Documentation]
|
|
889
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
890
|
+
* link:https://developer.mozilla.org/en-US/docs/Web/SVG[MDN SVG Documentation]
|
|
891
|
+
* link:https://www.w3.org/TR/SVG2/[W3C SVG Specification]
|