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,937 @@
|
|
|
1
|
+
= SVG Generation
|
|
2
|
+
:page-nav_order: 5
|
|
3
|
+
:page-parent: Core Concepts
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
This document explains how Postsvg generates SVG output from interpreted PostScript graphics operations. Understanding the SVG generation process helps when debugging output, optimizing performance, and extending Postsvg's capabilities.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../concepts.adoc[Core Concepts Overview]
|
|
13
|
+
* link:conversion-pipeline.adoc[Conversion Pipeline]
|
|
14
|
+
* link:path-operations.adoc[Path Operations]
|
|
15
|
+
* link:coordinate-systems.adoc[Coordinate Systems]
|
|
16
|
+
|
|
17
|
+
== Concepts
|
|
18
|
+
|
|
19
|
+
**SVG Document**:: An XML document containing vector graphics defined by geometric shapes and paths.
|
|
20
|
+
|
|
21
|
+
**ViewBox**:: The coordinate system and viewport dimensions for SVG content.
|
|
22
|
+
|
|
23
|
+
**Path Data**:: A string of commands and coordinates defining vector paths in SVG.
|
|
24
|
+
|
|
25
|
+
**Transform Groups**:: SVG `<g>` elements with transform attributes for coordinate system adjustments.
|
|
26
|
+
|
|
27
|
+
**Optimization**:: Techniques to reduce SVG file size and improve rendering performance.
|
|
28
|
+
|
|
29
|
+
== SVG Document Structure
|
|
30
|
+
|
|
31
|
+
=== Complete SVG Document
|
|
32
|
+
|
|
33
|
+
Postsvg generates well-formed, standards-compliant SVG documents:
|
|
34
|
+
|
|
35
|
+
.SVG Document Template
|
|
36
|
+
[source,xml]
|
|
37
|
+
----
|
|
38
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
39
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
40
|
+
width="WIDTH"
|
|
41
|
+
height="HEIGHT"
|
|
42
|
+
viewBox="LLX LLY WIDTH HEIGHT">
|
|
43
|
+
<defs>
|
|
44
|
+
<!-- Reusable definitions: patterns, gradients, clip paths -->
|
|
45
|
+
</defs>
|
|
46
|
+
<g transform="translate(0 HEIGHT) scale(1 -1)">
|
|
47
|
+
<!-- Graphics elements with Y-axis flip -->
|
|
48
|
+
</g>
|
|
49
|
+
</svg>
|
|
50
|
+
----
|
|
51
|
+
|
|
52
|
+
=== Document Components
|
|
53
|
+
|
|
54
|
+
**XML Declaration:**
|
|
55
|
+
|
|
56
|
+
[source,xml]
|
|
57
|
+
----
|
|
58
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
Identifies the document as XML version 1.0 with UTF-8 encoding.
|
|
62
|
+
|
|
63
|
+
**SVG Root Element:**
|
|
64
|
+
|
|
65
|
+
[source,xml]
|
|
66
|
+
----
|
|
67
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
68
|
+
width="612"
|
|
69
|
+
height="792"
|
|
70
|
+
viewBox="0 0 612 792">
|
|
71
|
+
----
|
|
72
|
+
|
|
73
|
+
**Attributes:**
|
|
74
|
+
|
|
75
|
+
* `xmlns` - SVG namespace declaration (required)
|
|
76
|
+
* `width` - Physical width in pixels or units
|
|
77
|
+
* `height` - Physical height in pixels or units
|
|
78
|
+
* `viewBox` - User coordinate system (min-x min-y width height)
|
|
79
|
+
|
|
80
|
+
**Definitions Section:**
|
|
81
|
+
|
|
82
|
+
[source,xml]
|
|
83
|
+
----
|
|
84
|
+
<defs>
|
|
85
|
+
<clipPath id="clipPath2">
|
|
86
|
+
<path d="M 50 50 L 350 50 L 350 350 L 50 350 Z"/>
|
|
87
|
+
</clipPath>
|
|
88
|
+
<pattern id="pattern1">...</pattern>
|
|
89
|
+
<linearGradient id="grad1">...</linearGradient>
|
|
90
|
+
</defs>
|
|
91
|
+
----
|
|
92
|
+
|
|
93
|
+
Contains reusable graphics elements referenced by other elements.
|
|
94
|
+
|
|
95
|
+
**Transform Group:**
|
|
96
|
+
|
|
97
|
+
[source,xml]
|
|
98
|
+
----
|
|
99
|
+
<g transform="translate(0 792) scale(1 -1)">
|
|
100
|
+
<!-- All path elements go here -->
|
|
101
|
+
</g>
|
|
102
|
+
----
|
|
103
|
+
|
|
104
|
+
Applies coordinate system transformation to flip Y-axis from PostScript to SVG convention.
|
|
105
|
+
|
|
106
|
+
== ViewBox Configuration
|
|
107
|
+
|
|
108
|
+
=== From BoundingBox to ViewBox
|
|
109
|
+
|
|
110
|
+
The PostScript BoundingBox comment defines the page dimensions:
|
|
111
|
+
|
|
112
|
+
[source,postscript]
|
|
113
|
+
----
|
|
114
|
+
%%BoundingBox: llx lly urx ury
|
|
115
|
+
----
|
|
116
|
+
|
|
117
|
+
This converts to SVG viewBox:
|
|
118
|
+
|
|
119
|
+
.BoundingBox to ViewBox Mapping
|
|
120
|
+
[source]
|
|
121
|
+
----
|
|
122
|
+
PostScript BoundingBox: llx lly urx ury
|
|
123
|
+
↓ ↓ ↓ ↓
|
|
124
|
+
SVG viewBox: min-x min-y width height
|
|
125
|
+
|
|
126
|
+
Where:
|
|
127
|
+
min-x = llx
|
|
128
|
+
min-y = lly
|
|
129
|
+
width = urx - llx
|
|
130
|
+
height = ury - lly
|
|
131
|
+
----
|
|
132
|
+
|
|
133
|
+
.ViewBox Example
|
|
134
|
+
[example]
|
|
135
|
+
====
|
|
136
|
+
**PostScript:**
|
|
137
|
+
|
|
138
|
+
[source,postscript]
|
|
139
|
+
----
|
|
140
|
+
%%BoundingBox: 0 0 612 792
|
|
141
|
+
% US Letter page (8.5" × 11" at 72 DPI)
|
|
142
|
+
----
|
|
143
|
+
|
|
144
|
+
**SVG:**
|
|
145
|
+
|
|
146
|
+
[source,xml]
|
|
147
|
+
----
|
|
148
|
+
<svg width="612" height="792" viewBox="0 0 612 792">
|
|
149
|
+
----
|
|
150
|
+
|
|
151
|
+
**Interpretation:**
|
|
152
|
+
|
|
153
|
+
* Coordinate system starts at (0, 0)
|
|
154
|
+
* Extends 612 units right, 792 units down
|
|
155
|
+
* 1 unit = 1 point = 1/72 inch
|
|
156
|
+
====
|
|
157
|
+
|
|
158
|
+
=== Non-Zero Origin
|
|
159
|
+
|
|
160
|
+
When BoundingBox doesn't start at origin:
|
|
161
|
+
|
|
162
|
+
.Offset BoundingBox
|
|
163
|
+
[example]
|
|
164
|
+
====
|
|
165
|
+
**PostScript:**
|
|
166
|
+
|
|
167
|
+
[source,postscript]
|
|
168
|
+
----
|
|
169
|
+
%%BoundingBox: 50 50 250 250
|
|
170
|
+
% 200×200 area, offset from origin
|
|
171
|
+
----
|
|
172
|
+
|
|
173
|
+
**SVG Option 1: Preserve Origin**
|
|
174
|
+
|
|
175
|
+
[source,xml]
|
|
176
|
+
----
|
|
177
|
+
<svg width="200" height="200" viewBox="50 50 200 200">
|
|
178
|
+
<!-- Content uses original coordinates (50-250) -->
|
|
179
|
+
</svg>
|
|
180
|
+
----
|
|
181
|
+
|
|
182
|
+
**SVG Option 2: Normalize to Zero**
|
|
183
|
+
|
|
184
|
+
[source,xml]
|
|
185
|
+
----
|
|
186
|
+
<svg width="200" height="200" viewBox="0 0 200 200">
|
|
187
|
+
<g transform="translate(-50 200) scale(1 -1) translate(0 -50)">
|
|
188
|
+
<!-- Content adjusted to (0-200) range -->
|
|
189
|
+
</g>
|
|
190
|
+
</svg>
|
|
191
|
+
----
|
|
192
|
+
====
|
|
193
|
+
|
|
194
|
+
== Coordinate System Transformation
|
|
195
|
+
|
|
196
|
+
=== The Y-Axis Problem
|
|
197
|
+
|
|
198
|
+
PostScript and SVG use different Y-axis orientations:
|
|
199
|
+
|
|
200
|
+
.Coordinate System Comparison
|
|
201
|
+
[source]
|
|
202
|
+
----
|
|
203
|
+
PostScript (Y ↑) SVG (Y ↓)
|
|
204
|
+
|
|
205
|
+
Height ┌───────┐ 0 0 ┌───────┐ 0
|
|
206
|
+
│ ● │ │ ● │
|
|
207
|
+
0 └───────┘ Width Height └───────┘ Width
|
|
208
|
+
|
|
209
|
+
Point (100, 150): Point (100, 150):
|
|
210
|
+
- 150 units UP - 150 units DOWN
|
|
211
|
+
- Near TOP - Near BOTTOM
|
|
212
|
+
----
|
|
213
|
+
|
|
214
|
+
=== The Transform Solution
|
|
215
|
+
|
|
216
|
+
Postsvg uses a transform group to flip the Y-axis:
|
|
217
|
+
|
|
218
|
+
[source,xml]
|
|
219
|
+
----
|
|
220
|
+
<g transform="translate(0 HEIGHT) scale(1 -1)">
|
|
221
|
+
<!-- PostScript graphics here -->
|
|
222
|
+
</g>
|
|
223
|
+
----
|
|
224
|
+
|
|
225
|
+
**Transformation Steps:**
|
|
226
|
+
|
|
227
|
+
1. **Translate (0, HEIGHT)** - Move origin to bottom-left
|
|
228
|
+
2. **Scale (1, -1)** - Flip Y-axis (multiply Y by -1)
|
|
229
|
+
|
|
230
|
+
.Transform Breakdown
|
|
231
|
+
[example]
|
|
232
|
+
====
|
|
233
|
+
Given height = 200:
|
|
234
|
+
|
|
235
|
+
[source,xml]
|
|
236
|
+
----
|
|
237
|
+
<g transform="translate(0 200) scale(1 -1)">
|
|
238
|
+
----
|
|
239
|
+
|
|
240
|
+
**Effect on Point (100, 50):**
|
|
241
|
+
|
|
242
|
+
[source]
|
|
243
|
+
----
|
|
244
|
+
Original SVG: (100, 50)
|
|
245
|
+
After translate(0, 200): (100, 250)
|
|
246
|
+
After scale(1, -1): (100, -250)
|
|
247
|
+
|
|
248
|
+
But SVG renders this as if Y started at bottom,
|
|
249
|
+
so PostScript (100, 50) appears in correct position!
|
|
250
|
+
----
|
|
251
|
+
====
|
|
252
|
+
|
|
253
|
+
=== Transform Limitations
|
|
254
|
+
|
|
255
|
+
The global Y-flip doesn't work for:
|
|
256
|
+
|
|
257
|
+
* Text (would be upside-down)
|
|
258
|
+
* Images (would be flipped)
|
|
259
|
+
* Patterns (may need special handling)
|
|
260
|
+
|
|
261
|
+
For these elements, additional transforms may be needed.
|
|
262
|
+
|
|
263
|
+
== Path Generation
|
|
264
|
+
|
|
265
|
+
=== From Graphics Operations to SVG Paths
|
|
266
|
+
|
|
267
|
+
The [`SvgGenerator`](../../lib/postsvg/svg_generator.rb:7) converts path segments to SVG path data.
|
|
268
|
+
|
|
269
|
+
.Path Generation Flow
|
|
270
|
+
[source]
|
|
271
|
+
----
|
|
272
|
+
PostScript Operations Graphics State SVG Path
|
|
273
|
+
────────────────────────────────────────────────────────────────
|
|
274
|
+
newpath → current_path: []
|
|
275
|
+
100 100 moveto → [(M 100,100)]
|
|
276
|
+
200 100 lineto → [(M 100,100),
|
|
277
|
+
(L 200,100)]
|
|
278
|
+
200 200 lineto → [(M 100,100),
|
|
279
|
+
(L 200,100),
|
|
280
|
+
(L 200,200)]
|
|
281
|
+
closepath → [(M 100,100),
|
|
282
|
+
(L 200,100),
|
|
283
|
+
(L 200,200),
|
|
284
|
+
(Z)]
|
|
285
|
+
stroke → Generate SVG → <path d="M 100 100
|
|
286
|
+
L 200 100
|
|
287
|
+
L 200 200
|
|
288
|
+
Z"
|
|
289
|
+
stroke="#000"
|
|
290
|
+
fill="none"/>
|
|
291
|
+
----
|
|
292
|
+
|
|
293
|
+
=== SVG Path Data Commands
|
|
294
|
+
|
|
295
|
+
PostScript path operators map to SVG path data commands:
|
|
296
|
+
|
|
297
|
+
.Command Mapping
|
|
298
|
+
[source]
|
|
299
|
+
----
|
|
300
|
+
PostScript SVG Description
|
|
301
|
+
─────────────────────────────────────────────────────
|
|
302
|
+
moveto x y → M x y Move to absolute position
|
|
303
|
+
rmoveto dx dy → m dx dy Move relative
|
|
304
|
+
lineto x y → L x y Line to absolute position
|
|
305
|
+
rlineto dx dy → l dx dy Line relative
|
|
306
|
+
curveto x1 y1 → C x1 y1 Cubic Bézier curve
|
|
307
|
+
x2 y2 x3 y3 x2 y2
|
|
308
|
+
x3 y3
|
|
309
|
+
rcurveto ... → c ... Relative cubic curve
|
|
310
|
+
closepath → Z Close path
|
|
311
|
+
----
|
|
312
|
+
|
|
313
|
+
.Path Data Example
|
|
314
|
+
[example]
|
|
315
|
+
====
|
|
316
|
+
**PostScript:**
|
|
317
|
+
|
|
318
|
+
[source,postscript]
|
|
319
|
+
----
|
|
320
|
+
newpath
|
|
321
|
+
50 50 moveto
|
|
322
|
+
150 50 lineto
|
|
323
|
+
100 150 lineto
|
|
324
|
+
closepath
|
|
325
|
+
fill
|
|
326
|
+
----
|
|
327
|
+
|
|
328
|
+
**SVG Path Data:**
|
|
329
|
+
|
|
330
|
+
[source,xml]
|
|
331
|
+
----
|
|
332
|
+
<path d="M 50 50 L 150 50 L 100 150 Z"
|
|
333
|
+
fill="#808080"
|
|
334
|
+
stroke="none"/>
|
|
335
|
+
----
|
|
336
|
+
====
|
|
337
|
+
|
|
338
|
+
=== Path Data Optimization
|
|
339
|
+
|
|
340
|
+
The [`PathBuilder`](../../lib/postsvg/path_builder.rb:7) optimizes path data:
|
|
341
|
+
|
|
342
|
+
**Number Formatting:**
|
|
343
|
+
|
|
344
|
+
[source,ruby]
|
|
345
|
+
----
|
|
346
|
+
def num_fmt(n)
|
|
347
|
+
# Remove trailing zeros
|
|
348
|
+
format("%.3f", n).sub(/\.?0+$/, "")
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Examples:
|
|
352
|
+
# 100.0 → "100"
|
|
353
|
+
# 50.500 → "50.5"
|
|
354
|
+
# 33.333 → "33.333"
|
|
355
|
+
----
|
|
356
|
+
|
|
357
|
+
**Coordinate Precision:**
|
|
358
|
+
|
|
359
|
+
* Use 3 decimal places for precision
|
|
360
|
+
* Remove unnecessary trailing zeros
|
|
361
|
+
* Handle negative zero (convert to 0)
|
|
362
|
+
|
|
363
|
+
.Optimized Path Data
|
|
364
|
+
[example]
|
|
365
|
+
====
|
|
366
|
+
**Before Optimization:**
|
|
367
|
+
|
|
368
|
+
[source,xml]
|
|
369
|
+
----
|
|
370
|
+
<path d="M 100.000 100.000 L 200.000 100.000 L 200.000 200.000 Z"/>
|
|
371
|
+
----
|
|
372
|
+
|
|
373
|
+
**After Optimization:**
|
|
374
|
+
|
|
375
|
+
[source,xml]
|
|
376
|
+
----
|
|
377
|
+
<path d="M 100 100 L 200 100 L 200 200 Z"/>
|
|
378
|
+
----
|
|
379
|
+
|
|
380
|
+
Saves 27 bytes (31% reduction)!
|
|
381
|
+
====
|
|
382
|
+
|
|
383
|
+
== Styling and Attributes
|
|
384
|
+
|
|
385
|
+
=== Stroke Operations
|
|
386
|
+
|
|
387
|
+
When a path is stroked, generate stroke attributes:
|
|
388
|
+
|
|
389
|
+
[source,xml]
|
|
390
|
+
----
|
|
391
|
+
<path d="..."
|
|
392
|
+
fill="none"
|
|
393
|
+
stroke="#000000"
|
|
394
|
+
stroke-width="2"
|
|
395
|
+
stroke-linecap="round"
|
|
396
|
+
stroke-linejoin="miter"
|
|
397
|
+
stroke-dasharray="5,3"/>
|
|
398
|
+
----
|
|
399
|
+
|
|
400
|
+
**Stroke Attributes:**
|
|
401
|
+
|
|
402
|
+
`stroke`:: Stroke color (hex RGB or color name)
|
|
403
|
+
`stroke-width`:: Line thickness in user units
|
|
404
|
+
`stroke-linecap`:: End cap style (butt, round, square)
|
|
405
|
+
`stroke-linejoin`:: Corner join style (miter, round, bevel)
|
|
406
|
+
`stroke-dasharray`:: Dash pattern (comma-separated lengths)
|
|
407
|
+
`stroke-miterlimit`:: Miter limit for sharp corners
|
|
408
|
+
|
|
409
|
+
.Stroke Example
|
|
410
|
+
[example]
|
|
411
|
+
====
|
|
412
|
+
**PostScript:**
|
|
413
|
+
|
|
414
|
+
[source,postscript]
|
|
415
|
+
----
|
|
416
|
+
newpath
|
|
417
|
+
50 50 moveto
|
|
418
|
+
350 50 lineto
|
|
419
|
+
5 setlinewidth
|
|
420
|
+
1 setlinecap
|
|
421
|
+
1 setlinejoin
|
|
422
|
+
[10 5] 0 setdash
|
|
423
|
+
1 0 0 setrgbcolor
|
|
424
|
+
stroke
|
|
425
|
+
----
|
|
426
|
+
|
|
427
|
+
**SVG:**
|
|
428
|
+
|
|
429
|
+
[source,xml]
|
|
430
|
+
----
|
|
431
|
+
<path d="M 50 50 L 350 50"
|
|
432
|
+
fill="none"
|
|
433
|
+
stroke="#FF0000"
|
|
434
|
+
stroke-width="5"
|
|
435
|
+
stroke-linecap="round"
|
|
436
|
+
stroke-linejoin="round"
|
|
437
|
+
stroke-dasharray="10,5"/>
|
|
438
|
+
----
|
|
439
|
+
====
|
|
440
|
+
|
|
441
|
+
=== Fill Operations
|
|
442
|
+
|
|
443
|
+
When a path is filled, generate fill attributes:
|
|
444
|
+
|
|
445
|
+
[source,xml]
|
|
446
|
+
----
|
|
447
|
+
<path d="..."
|
|
448
|
+
fill="#808080"
|
|
449
|
+
stroke="none"/>
|
|
450
|
+
----
|
|
451
|
+
|
|
452
|
+
**Fill Attributes:**
|
|
453
|
+
|
|
454
|
+
`fill`:: Fill color (hex RGB or color name)
|
|
455
|
+
`fill-rule`:: Winding rule (nonzero or evenodd)
|
|
456
|
+
`fill-opacity`:: Opacity (0.0 to 1.0)
|
|
457
|
+
|
|
458
|
+
.Fill Example
|
|
459
|
+
[example]
|
|
460
|
+
====
|
|
461
|
+
**PostScript:**
|
|
462
|
+
|
|
463
|
+
[source,postscript]
|
|
464
|
+
----
|
|
465
|
+
newpath
|
|
466
|
+
100 100 moveto
|
|
467
|
+
200 100 lineto
|
|
468
|
+
200 200 lineto
|
|
469
|
+
100 200 lineto
|
|
470
|
+
closepath
|
|
471
|
+
0.5 setgray
|
|
472
|
+
fill
|
|
473
|
+
----
|
|
474
|
+
|
|
475
|
+
**SVG:**
|
|
476
|
+
|
|
477
|
+
[source,xml]
|
|
478
|
+
----
|
|
479
|
+
<path d="M 100 100 L 200 100 L 200 200 L 100 200 Z"
|
|
480
|
+
fill="#808080"
|
|
481
|
+
stroke="none"/>
|
|
482
|
+
----
|
|
483
|
+
====
|
|
484
|
+
|
|
485
|
+
=== Color Conversion
|
|
486
|
+
|
|
487
|
+
PostScript colors convert to SVG hex format:
|
|
488
|
+
|
|
489
|
+
.Color Conversion
|
|
490
|
+
[source]
|
|
491
|
+
----
|
|
492
|
+
PostScript RGB SVG Hex
|
|
493
|
+
──────────────────────────────────
|
|
494
|
+
1 0 0 setrgbcolor → #FF0000 (red)
|
|
495
|
+
0 1 0 setrgbcolor → #00FF00 (green)
|
|
496
|
+
0 0 1 setrgbcolor → #0000FF (blue)
|
|
497
|
+
0.5 setgray → #808080 (50% gray)
|
|
498
|
+
0 setgray → #000000 (black)
|
|
499
|
+
1 setgray → #FFFFFF (white)
|
|
500
|
+
|
|
501
|
+
Conversion formula:
|
|
502
|
+
hex = format("#%02x%02x%02x", r*255, g*255, b*255)
|
|
503
|
+
----
|
|
504
|
+
|
|
505
|
+
.Color Examples
|
|
506
|
+
[example]
|
|
507
|
+
====
|
|
508
|
+
[source,ruby]
|
|
509
|
+
----
|
|
510
|
+
# Red
|
|
511
|
+
rgb_to_hex(1.0, 0.0, 0.0) # => "#ff0000"
|
|
512
|
+
|
|
513
|
+
# Orange
|
|
514
|
+
rgb_to_hex(1.0, 0.5, 0.0) # => "#ff8000"
|
|
515
|
+
|
|
516
|
+
# Gray
|
|
517
|
+
rgb_to_hex(0.5, 0.5, 0.5) # => "#808080"
|
|
518
|
+
|
|
519
|
+
# Implementation
|
|
520
|
+
def rgb_to_hex(r, g, b)
|
|
521
|
+
format("#%02x%02x%02x",
|
|
522
|
+
(r * 255).to_i,
|
|
523
|
+
(g * 255).to_i,
|
|
524
|
+
(b * 255).to_i)
|
|
525
|
+
end
|
|
526
|
+
----
|
|
527
|
+
====
|
|
528
|
+
|
|
529
|
+
== Clipping Paths
|
|
530
|
+
|
|
531
|
+
=== ClipPath Generation
|
|
532
|
+
|
|
533
|
+
PostScript clip operations create SVG clipPath definitions:
|
|
534
|
+
|
|
535
|
+
.ClipPath Generation Flow
|
|
536
|
+
[source]
|
|
537
|
+
----
|
|
538
|
+
PostScript SVG
|
|
539
|
+
──────────────────────────────────────────────────
|
|
540
|
+
newpath
|
|
541
|
+
50 50 moveto
|
|
542
|
+
350 50 lineto
|
|
543
|
+
350 350 lineto
|
|
544
|
+
50 350 lineto
|
|
545
|
+
closepath
|
|
546
|
+
clip → <defs>
|
|
547
|
+
newpath <clipPath id="clipPath2">
|
|
548
|
+
<path d="M 50 50 L 350 50
|
|
549
|
+
L 350 350 L 50 350 Z"/>
|
|
550
|
+
</clipPath>
|
|
551
|
+
</defs>
|
|
552
|
+
|
|
553
|
+
% Subsequent drawing
|
|
554
|
+
100 100 moveto
|
|
555
|
+
200 200 lineto
|
|
556
|
+
stroke → <path d="M 100 100 L 200 200"
|
|
557
|
+
clip-path="url(#clipPath2)"
|
|
558
|
+
.../>
|
|
559
|
+
----
|
|
560
|
+
|
|
561
|
+
=== ClipPath Deduplication
|
|
562
|
+
|
|
563
|
+
Postsvg optimizes by reusing identical clip paths:
|
|
564
|
+
|
|
565
|
+
.ClipPath Cache
|
|
566
|
+
[example]
|
|
567
|
+
====
|
|
568
|
+
[source,ruby]
|
|
569
|
+
----
|
|
570
|
+
# In ExecutionContext
|
|
571
|
+
@clippath_cache = {}
|
|
572
|
+
|
|
573
|
+
# First clip path
|
|
574
|
+
clip_path_d = "M 50 50 L 350 350 Z"
|
|
575
|
+
clip_id = @clippath_cache[clip_path_d]
|
|
576
|
+
|
|
577
|
+
unless clip_id
|
|
578
|
+
# Create new clipPath
|
|
579
|
+
clip_id = next_clippath_id # => 2
|
|
580
|
+
@clippath_cache[clip_path_d] = clip_id
|
|
581
|
+
add_def("<clipPath id=\"clipPath#{clip_id}\">
|
|
582
|
+
<path d=\"#{clip_path_d}\"/>
|
|
583
|
+
</clipPath>")
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
# Second identical clip path
|
|
587
|
+
clip_path_d = "M 50 50 L 350 350 Z"
|
|
588
|
+
clip_id = @clippath_cache[clip_path_d] # => 2 (reused!)
|
|
589
|
+
# No new clipPath created
|
|
590
|
+
----
|
|
591
|
+
|
|
592
|
+
**Benefits:**
|
|
593
|
+
|
|
594
|
+
* Reduces SVG file size
|
|
595
|
+
* Improves rendering performance
|
|
596
|
+
* Maintains clean, minimal output
|
|
597
|
+
====
|
|
598
|
+
|
|
599
|
+
== SVG Generation Process
|
|
600
|
+
|
|
601
|
+
=== Step-by-Step Generation
|
|
602
|
+
|
|
603
|
+
The generation process in [`SvgGenerator`](../../lib/postsvg/svg_generator.rb:7):
|
|
604
|
+
|
|
605
|
+
.Generation Steps
|
|
606
|
+
[source]
|
|
607
|
+
----
|
|
608
|
+
1. Initialize Generator
|
|
609
|
+
└─> Create empty paths array
|
|
610
|
+
|
|
611
|
+
2. Execute PostScript
|
|
612
|
+
└─> Build paths in PathBuilder
|
|
613
|
+
└─> Convert to SVG path data
|
|
614
|
+
└─> Add to paths array
|
|
615
|
+
|
|
616
|
+
3. Generate SVG Document
|
|
617
|
+
├─> Create XML declaration
|
|
618
|
+
├─> Create <svg> root
|
|
619
|
+
│ ├─> Set width, height, viewBox
|
|
620
|
+
│ └─> Add namespace
|
|
621
|
+
├─> Create <defs> section
|
|
622
|
+
│ └─> Add clipPaths, patterns, etc.
|
|
623
|
+
├─> Create transform <g>
|
|
624
|
+
│ └─> Add coordinate flip transform
|
|
625
|
+
└─> Add path elements
|
|
626
|
+
└─> With appropriate attributes
|
|
627
|
+
|
|
628
|
+
4. Format and Output
|
|
629
|
+
└─> Pretty-print XML
|
|
630
|
+
└─> Return SVG string
|
|
631
|
+
----
|
|
632
|
+
|
|
633
|
+
=== Implementation Example
|
|
634
|
+
|
|
635
|
+
.SVG Generation Code
|
|
636
|
+
[example]
|
|
637
|
+
====
|
|
638
|
+
[source,ruby]
|
|
639
|
+
----
|
|
640
|
+
# Initialize generator
|
|
641
|
+
generator = SvgGenerator.new
|
|
642
|
+
|
|
643
|
+
# Add paths during interpretation
|
|
644
|
+
generator.add_path(
|
|
645
|
+
path_segments,
|
|
646
|
+
graphics_state,
|
|
647
|
+
:stroke # or :fill
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
# Generate final SVG
|
|
651
|
+
svg = generator.generate(
|
|
652
|
+
width: bbox[:width],
|
|
653
|
+
height: bbox[:height],
|
|
654
|
+
viewbox: "#{bbox[:llx]} #{bbox[:lly]} " \
|
|
655
|
+
"#{bbox[:width]} #{bbox[:height]}"
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
# Output:
|
|
659
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
660
|
+
# <svg xmlns="http://www.w3.org/2000/svg"
|
|
661
|
+
# width="612" height="792"
|
|
662
|
+
# viewBox="0 0 612 792">
|
|
663
|
+
# <g transform="translate(0 792) scale(1 -1)">
|
|
664
|
+
# <path d="..." .../>
|
|
665
|
+
# </g>
|
|
666
|
+
# </svg>
|
|
667
|
+
----
|
|
668
|
+
====
|
|
669
|
+
|
|
670
|
+
== Optimization Techniques
|
|
671
|
+
|
|
672
|
+
=== Path Data Minimization
|
|
673
|
+
|
|
674
|
+
**Remove Redundancy:**
|
|
675
|
+
|
|
676
|
+
[source,xml]
|
|
677
|
+
----
|
|
678
|
+
<!-- Before: Redundant moveto -->
|
|
679
|
+
<path d="M 100 100 M 100 100 L 200 200"/>
|
|
680
|
+
|
|
681
|
+
<!-- After: Single moveto -->
|
|
682
|
+
<path d="M 100 100 L 200 200"/>
|
|
683
|
+
----
|
|
684
|
+
|
|
685
|
+
**Combine Consecutive Commands:**
|
|
686
|
+
|
|
687
|
+
[source,xml]
|
|
688
|
+
----
|
|
689
|
+
<!-- Before: Multiple lineto -->
|
|
690
|
+
<path d="M 100 100 L 150 100 L 200 100 L 250 100"/>
|
|
691
|
+
|
|
692
|
+
<!-- After: Implicit lineto -->
|
|
693
|
+
<path d="M 100 100 L 150 100 200 100 250 100"/>
|
|
694
|
+
----
|
|
695
|
+
|
|
696
|
+
**Use Relative Commands:**
|
|
697
|
+
|
|
698
|
+
[source,xml]
|
|
699
|
+
----
|
|
700
|
+
<!-- Before: Absolute coordinates -->
|
|
701
|
+
<path d="M 100 100 L 110 100 L 120 100 L 130 100"/>
|
|
702
|
+
|
|
703
|
+
<!-- After: Relative coordinates (shorter) -->
|
|
704
|
+
<path d="M 100 100 l 10 0 10 0 10 0"/>
|
|
705
|
+
----
|
|
706
|
+
|
|
707
|
+
=== Attribute Optimization
|
|
708
|
+
|
|
709
|
+
**Omit Default Values:**
|
|
710
|
+
|
|
711
|
+
[source,xml]
|
|
712
|
+
----
|
|
713
|
+
<!-- Before: Include defaults -->
|
|
714
|
+
<path d="..."
|
|
715
|
+
stroke-width="1"
|
|
716
|
+
stroke-linecap="butt"
|
|
717
|
+
stroke-linejoin="miter"/>
|
|
718
|
+
|
|
719
|
+
<!-- After: Omit defaults (1, butt, miter) -->
|
|
720
|
+
<path d="..."/>
|
|
721
|
+
----
|
|
722
|
+
|
|
723
|
+
**Use Style Attribute:**
|
|
724
|
+
|
|
725
|
+
[source,xml]
|
|
726
|
+
----
|
|
727
|
+
<!-- Before: Multiple attributes -->
|
|
728
|
+
<path d="..."
|
|
729
|
+
fill="#ff0000"
|
|
730
|
+
stroke="#000000"
|
|
731
|
+
stroke-width="2"/>
|
|
732
|
+
|
|
733
|
+
<!-- After: Style attribute (sometimes smaller) -->
|
|
734
|
+
<path d="..."
|
|
735
|
+
style="fill:#ff0000;stroke:#000;stroke-width:2"/>
|
|
736
|
+
----
|
|
737
|
+
|
|
738
|
+
=== Structural Optimization
|
|
739
|
+
|
|
740
|
+
**Group Common Attributes:**
|
|
741
|
+
|
|
742
|
+
[source,xml]
|
|
743
|
+
----
|
|
744
|
+
<!-- Before: Repeated attributes -->
|
|
745
|
+
<path d="..." fill="#ff0000" stroke="none"/>
|
|
746
|
+
<path d="..." fill="#ff0000" stroke="none"/>
|
|
747
|
+
<path d="..." fill="#ff0000" stroke="none"/>
|
|
748
|
+
|
|
749
|
+
<!-- After: Group with shared style -->
|
|
750
|
+
<g fill="#ff0000" stroke="none">
|
|
751
|
+
<path d="..."/>
|
|
752
|
+
<path d="..."/>
|
|
753
|
+
<path d="..."/>
|
|
754
|
+
</g>
|
|
755
|
+
----
|
|
756
|
+
|
|
757
|
+
== Advanced SVG Features
|
|
758
|
+
|
|
759
|
+
=== Gradients and Patterns
|
|
760
|
+
|
|
761
|
+
While Postsvg focuses on basic PostScript conversion, SVG supports advanced features:
|
|
762
|
+
|
|
763
|
+
**Linear Gradients:**
|
|
764
|
+
|
|
765
|
+
[source,xml]
|
|
766
|
+
----
|
|
767
|
+
<defs>
|
|
768
|
+
<linearGradient id="grad1">
|
|
769
|
+
<stop offset="0%" stop-color="#ff0000"/>
|
|
770
|
+
<stop offset="100%" stop-color="#0000ff"/>
|
|
771
|
+
</linearGradient>
|
|
772
|
+
</defs>
|
|
773
|
+
<path d="..." fill="url(#grad1)"/>
|
|
774
|
+
----
|
|
775
|
+
|
|
776
|
+
**Patterns:**
|
|
777
|
+
|
|
778
|
+
[source,xml]
|
|
779
|
+
----
|
|
780
|
+
<defs>
|
|
781
|
+
<pattern id="pattern1" width="20" height="20"
|
|
782
|
+
patternUnits="userSpaceOnUse">
|
|
783
|
+
<circle cx="10" cy="10" r="5" fill="#000"/>
|
|
784
|
+
</pattern>
|
|
785
|
+
</defs>
|
|
786
|
+
<path d="..." fill="url(#pattern1)"/>
|
|
787
|
+
----
|
|
788
|
+
|
|
789
|
+
=== Transparency and Blending
|
|
790
|
+
|
|
791
|
+
SVG Level 3 features:
|
|
792
|
+
|
|
793
|
+
**Opacity:**
|
|
794
|
+
|
|
795
|
+
[source,xml]
|
|
796
|
+
----
|
|
797
|
+
<path d="..." fill="#ff0000" opacity="0.5"/>
|
|
798
|
+
<path d="..." fill="#ff0000" fill-opacity="0.5"/>
|
|
799
|
+
----
|
|
800
|
+
|
|
801
|
+
**Blend Modes:**
|
|
802
|
+
|
|
803
|
+
[source,xml]
|
|
804
|
+
----
|
|
805
|
+
<path d="..." style="mix-blend-mode: multiply"/>
|
|
806
|
+
----
|
|
807
|
+
|
|
808
|
+
== Output Quality
|
|
809
|
+
|
|
810
|
+
=== Validation
|
|
811
|
+
|
|
812
|
+
Ensure generated SVG is valid:
|
|
813
|
+
|
|
814
|
+
**XML Well-Formedness:**
|
|
815
|
+
|
|
816
|
+
* Properly nested elements
|
|
817
|
+
* Quoted attribute values
|
|
818
|
+
* Escaped special characters (&, <, >, ", ')
|
|
819
|
+
|
|
820
|
+
**SVG Compliance:**
|
|
821
|
+
|
|
822
|
+
* Valid element names
|
|
823
|
+
* Valid attribute values
|
|
824
|
+
* Proper namespace declarations
|
|
825
|
+
|
|
826
|
+
.XML Escaping
|
|
827
|
+
[example]
|
|
828
|
+
====
|
|
829
|
+
[source,ruby]
|
|
830
|
+
----
|
|
831
|
+
def escape_xml(str)
|
|
832
|
+
str.to_s
|
|
833
|
+
.gsub("&", "&")
|
|
834
|
+
.gsub("<", "<")
|
|
835
|
+
.gsub(">", ">")
|
|
836
|
+
.gsub('"', """)
|
|
837
|
+
.gsub("'", "'")
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
# Usage in text elements
|
|
841
|
+
text = "x < y && y > z"
|
|
842
|
+
escaped = escape_xml(text)
|
|
843
|
+
# => "x < y && y > z"
|
|
844
|
+
----
|
|
845
|
+
====
|
|
846
|
+
|
|
847
|
+
=== Browser Compatibility
|
|
848
|
+
|
|
849
|
+
Generated SVG works in all modern browsers:
|
|
850
|
+
|
|
851
|
+
* Chrome/Edge (Blink)
|
|
852
|
+
* Firefox (Gecko)
|
|
853
|
+
* Safari (WebKit)
|
|
854
|
+
|
|
855
|
+
**Best Practices:**
|
|
856
|
+
|
|
857
|
+
* Use standard SVG 1.1 features
|
|
858
|
+
* Avoid browser-specific extensions
|
|
859
|
+
* Test in multiple browsers
|
|
860
|
+
|
|
861
|
+
== Performance Considerations
|
|
862
|
+
|
|
863
|
+
=== Generation Performance
|
|
864
|
+
|
|
865
|
+
**Time Complexity:**
|
|
866
|
+
|
|
867
|
+
* Path building: O(n) where n = number of path operators
|
|
868
|
+
* SVG generation: O(m) where m = number of paths
|
|
869
|
+
* Overall: O(n + m) - linear time
|
|
870
|
+
|
|
871
|
+
**Space Complexity:**
|
|
872
|
+
|
|
873
|
+
* Path storage: O(p) where p = path data size
|
|
874
|
+
* Output buffer: O(s) where s = SVG document size
|
|
875
|
+
|
|
876
|
+
=== Rendering Performance
|
|
877
|
+
|
|
878
|
+
**SVG Rendering:**
|
|
879
|
+
|
|
880
|
+
* Simple paths: Fast
|
|
881
|
+
* Complex paths: May be slow
|
|
882
|
+
* Many paths: Group by style
|
|
883
|
+
|
|
884
|
+
**Optimization:**
|
|
885
|
+
|
|
886
|
+
* Minimize path complexity
|
|
887
|
+
* Reduce number of elements
|
|
888
|
+
* Use appropriate precision
|
|
889
|
+
|
|
890
|
+
## Troubleshooting
|
|
891
|
+
|
|
892
|
+
=== Common Issues
|
|
893
|
+
|
|
894
|
+
**Upside-Down Graphics:**
|
|
895
|
+
|
|
896
|
+
[source,xml]
|
|
897
|
+
----
|
|
898
|
+
<!-- Missing transform -->
|
|
899
|
+
<svg><path d="..."/></svg>
|
|
900
|
+
|
|
901
|
+
<!-- Add Y-flip transform -->
|
|
902
|
+
<svg>
|
|
903
|
+
<g transform="translate(0 height) scale(1 -1)">
|
|
904
|
+
<path d="..."/>
|
|
905
|
+
</g>
|
|
906
|
+
</svg>
|
|
907
|
+
----
|
|
908
|
+
|
|
909
|
+
**Invisible Paths:**
|
|
910
|
+
|
|
911
|
+
* Check viewBox matches content coordinates
|
|
912
|
+
* Verify stroke/fill attributes
|
|
913
|
+
* Ensure path data is complete
|
|
914
|
+
|
|
915
|
+
**Invalid SVG:**
|
|
916
|
+
|
|
917
|
+
* Validate XML syntax
|
|
918
|
+
* Check namespace declaration
|
|
919
|
+
* Verify attribute values
|
|
920
|
+
|
|
921
|
+
== Next Steps
|
|
922
|
+
|
|
923
|
+
* Review link:path-operations.adoc[Path Operations] for path construction
|
|
924
|
+
* Explore link:coordinate-systems.adoc[Coordinate Systems] for transforms
|
|
925
|
+
* See link:conversion-pipeline.adoc[Conversion Pipeline] for full process
|
|
926
|
+
* Check link:../architecture/generator-stage.adoc[Generator Architecture]
|
|
927
|
+
* Read link:../api-reference.adoc[API Reference] for implementation details
|
|
928
|
+
|
|
929
|
+
== Bibliography
|
|
930
|
+
|
|
931
|
+
* link:path-operations.adoc[Path Operations]
|
|
932
|
+
* link:coordinate-systems.adoc[Coordinate Systems]
|
|
933
|
+
* link:conversion-pipeline.adoc[Conversion Pipeline]
|
|
934
|
+
* link:../architecture/generator-stage.adoc[Generator Stage Architecture]
|
|
935
|
+
* link:https://www.w3.org/TR/SVG/[SVG Specification]
|
|
936
|
+
* link:https://www.w3.org/TR/SVG/paths.html[SVG Paths Specification]
|
|
937
|
+
* link:https://developer.mozilla.org/en-US/docs/Web/SVG[MDN SVG Documentation]
|