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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -0
  3. data/CLAUDE.md +173 -0
  4. data/Gemfile +2 -3
  5. data/README.adoc +456 -179
  6. data/Rakefile +100 -0
  7. data/TODO.roadmap/00-architecture.md +139 -0
  8. data/TODO.roadmap/01-autoload-migration.md +39 -0
  9. data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
  10. data/TODO.roadmap/03-domain-model.md +66 -0
  11. data/TODO.roadmap/04-lexer.md +40 -0
  12. data/TODO.roadmap/05-parser.md +43 -0
  13. data/TODO.roadmap/06-graphics-state.md +45 -0
  14. data/TODO.roadmap/07-matrix-and-color.md +37 -0
  15. data/TODO.roadmap/08-svg-builder.md +51 -0
  16. data/TODO.roadmap/09-renderer.md +52 -0
  17. data/TODO.roadmap/10-visitor.md +58 -0
  18. data/TODO.roadmap/11-operator-coverage.md +103 -0
  19. data/TODO.roadmap/12-svg-domain.md +62 -0
  20. data/TODO.roadmap/13-translation-handlers.md +69 -0
  21. data/TODO.roadmap/14-ps-serializer.md +49 -0
  22. data/TODO.roadmap/15-cli-and-public-api.md +47 -0
  23. data/TODO.roadmap/16-specs.md +84 -0
  24. data/TODO.roadmap/17-docs-sync.md +47 -0
  25. data/TODO.roadmap/18-performance-and-determinism.md +47 -0
  26. data/TODO.roadmap/19-error-model.md +45 -0
  27. data/TODO.roadmap/20-font-and-text.md +46 -0
  28. data/TODO.roadmap/21-images.md +45 -0
  29. data/TODO.roadmap/22-forms-and-resources.md +25 -0
  30. data/TODO.roadmap/23-level2-level3.md +52 -0
  31. data/TODO.roadmap/24-ci-and-release.md +42 -0
  32. data/TODO.roadmap/README.md +77 -0
  33. data/docs/.gitignore +29 -0
  34. data/docs/CHANGELOG.md +114 -0
  35. data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
  36. data/docs/DEPLOYMENT.md +456 -0
  37. data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
  38. data/docs/DOCUMENTATION_PLAN.md +425 -0
  39. data/docs/FINAL_SUMMARY.md +657 -0
  40. data/docs/Gemfile +15 -0
  41. data/docs/README.md +327 -0
  42. data/docs/_config.yml +99 -0
  43. data/docs/advanced-topics.adoc +370 -0
  44. data/docs/api-reference/colors.adoc +705 -0
  45. data/docs/api-reference/converter.adoc +699 -0
  46. data/docs/api-reference/execution-context.adoc +1210 -0
  47. data/docs/api-reference/graphics-state.adoc +1070 -0
  48. data/docs/api-reference/interpreter.adoc +810 -0
  49. data/docs/api-reference/matrix.adoc +1179 -0
  50. data/docs/api-reference/path-builder.adoc +1284 -0
  51. data/docs/api-reference/postsvg-module.adoc +388 -0
  52. data/docs/api-reference/svg-generator.adoc +891 -0
  53. data/docs/api-reference/tokenizer.adoc +925 -0
  54. data/docs/api-reference.adoc +221 -0
  55. data/docs/architecture/command-registry.adoc +1191 -0
  56. data/docs/architecture/conversion-pipeline.adoc +746 -0
  57. data/docs/architecture/design-decisions.adoc +999 -0
  58. data/docs/architecture/generator-stage.adoc +1115 -0
  59. data/docs/architecture/graphics-state-model.adoc +1089 -0
  60. data/docs/architecture/interpreter-stage.adoc +1125 -0
  61. data/docs/architecture/parser-stage.adoc +1051 -0
  62. data/docs/architecture.adoc +354 -0
  63. data/docs/cli-reference/batch-command.adoc +616 -0
  64. data/docs/cli-reference/check-command.adoc +677 -0
  65. data/docs/cli-reference/cli-options.adoc +802 -0
  66. data/docs/cli-reference/convert-command.adoc +462 -0
  67. data/docs/cli-reference/version-command.adoc +296 -0
  68. data/docs/cli-reference.adoc +317 -0
  69. data/docs/concepts/conversion-pipeline.adoc +903 -0
  70. data/docs/concepts/coordinate-systems.adoc +836 -0
  71. data/docs/concepts/graphics-state.adoc +861 -0
  72. data/docs/concepts/path-operations.adoc +1076 -0
  73. data/docs/concepts/postscript-language.adoc +859 -0
  74. data/docs/concepts/svg-generation.adoc +937 -0
  75. data/docs/concepts.adoc +198 -0
  76. data/docs/contributing.adoc +443 -0
  77. data/docs/development.adoc +420 -0
  78. data/docs/faq.adoc +493 -0
  79. data/docs/getting-started/basic-usage.adoc +538 -0
  80. data/docs/getting-started/common-workflows.adoc +577 -0
  81. data/docs/getting-started/first-conversion.adoc +492 -0
  82. data/docs/getting-started/installation.adoc +534 -0
  83. data/docs/getting-started.adoc +94 -0
  84. data/docs/index.adoc +248 -0
  85. data/docs/optimization.adoc +196 -0
  86. data/docs/ps2svg_compatibility.adoc +149 -0
  87. data/docs/quick-reference.adoc +453 -0
  88. data/docs/sitemap.adoc +337 -0
  89. data/docs/troubleshooting.adoc +486 -0
  90. data/docs/validation.adoc +772 -0
  91. data/exe/postsvg +1 -0
  92. data/lib/postsvg/cli.rb +104 -57
  93. data/lib/postsvg/color.rb +132 -0
  94. data/lib/postsvg/errors.rb +68 -3
  95. data/lib/postsvg/format_number.rb +22 -0
  96. data/lib/postsvg/graphics_context.rb +80 -0
  97. data/lib/postsvg/graphics_stack.rb +43 -0
  98. data/lib/postsvg/model/literals/array.rb +41 -0
  99. data/lib/postsvg/model/literals/dictionary.rb +34 -0
  100. data/lib/postsvg/model/literals/hex.rb +37 -0
  101. data/lib/postsvg/model/literals/name.rb +40 -0
  102. data/lib/postsvg/model/literals/number.rb +36 -0
  103. data/lib/postsvg/model/literals/procedure.rb +41 -0
  104. data/lib/postsvg/model/literals/string.rb +30 -0
  105. data/lib/postsvg/model/literals.rb +19 -0
  106. data/lib/postsvg/model/operator.rb +58 -0
  107. data/lib/postsvg/model/operators/arithmetic.rb +264 -0
  108. data/lib/postsvg/model/operators/boolean.rb +182 -0
  109. data/lib/postsvg/model/operators/color.rb +74 -0
  110. data/lib/postsvg/model/operators/container.rb +186 -0
  111. data/lib/postsvg/model/operators/control_flow.rb +119 -0
  112. data/lib/postsvg/model/operators/device.rb +21 -0
  113. data/lib/postsvg/model/operators/dictionary.rb +118 -0
  114. data/lib/postsvg/model/operators/font.rb +121 -0
  115. data/lib/postsvg/model/operators/graphics_state.rb +84 -0
  116. data/lib/postsvg/model/operators/painting.rb +29 -0
  117. data/lib/postsvg/model/operators/path.rb +169 -0
  118. data/lib/postsvg/model/operators/stack.rb +72 -0
  119. data/lib/postsvg/model/operators/transformations.rb +103 -0
  120. data/lib/postsvg/model/operators.rb +89 -0
  121. data/lib/postsvg/model/program.rb +68 -0
  122. data/lib/postsvg/model/token.rb +43 -0
  123. data/lib/postsvg/model.rb +17 -0
  124. data/lib/postsvg/options.rb +29 -0
  125. data/lib/postsvg/renderer.rb +85 -0
  126. data/lib/postsvg/serializer.rb +325 -0
  127. data/lib/postsvg/source/ast_builder.rb +308 -0
  128. data/lib/postsvg/source/lexer.rb +322 -0
  129. data/lib/postsvg/source/operand_stack.rb +55 -0
  130. data/lib/postsvg/source.rb +21 -0
  131. data/lib/postsvg/svg/attribute_parser.rb +45 -0
  132. data/lib/postsvg/svg/clip_path_registry.rb +44 -0
  133. data/lib/postsvg/svg/document.rb +22 -0
  134. data/lib/postsvg/svg/element.rb +84 -0
  135. data/lib/postsvg/svg/elements/circle.rb +36 -0
  136. data/lib/postsvg/svg/elements/clip_path.rb +26 -0
  137. data/lib/postsvg/svg/elements/defs.rb +24 -0
  138. data/lib/postsvg/svg/elements/ellipse.rb +38 -0
  139. data/lib/postsvg/svg/elements/group.rb +37 -0
  140. data/lib/postsvg/svg/elements/image.rb +35 -0
  141. data/lib/postsvg/svg/elements/line.rb +36 -0
  142. data/lib/postsvg/svg/elements/path.rb +32 -0
  143. data/lib/postsvg/svg/elements/polygon.rb +12 -0
  144. data/lib/postsvg/svg/elements/polyline.rb +32 -0
  145. data/lib/postsvg/svg/elements/rect.rb +44 -0
  146. data/lib/postsvg/svg/elements/svg.rb +39 -0
  147. data/lib/postsvg/svg/elements/text.rb +42 -0
  148. data/lib/postsvg/svg/elements.rb +31 -0
  149. data/lib/postsvg/svg/paint.rb +34 -0
  150. data/lib/postsvg/svg/parser.rb +39 -0
  151. data/lib/postsvg/svg/path_data/command.rb +27 -0
  152. data/lib/postsvg/svg/path_data/parser.rb +82 -0
  153. data/lib/postsvg/svg/path_data.rb +17 -0
  154. data/lib/postsvg/svg/stroke.rb +31 -0
  155. data/lib/postsvg/svg/transform_list.rb +59 -0
  156. data/lib/postsvg/svg.rb +22 -0
  157. data/lib/postsvg/svg_builder.rb +249 -0
  158. data/lib/postsvg/translation/arc_converter.rb +86 -0
  159. data/lib/postsvg/translation/bounding_box.rb +59 -0
  160. data/lib/postsvg/translation/context.rb +34 -0
  161. data/lib/postsvg/translation/handler_registry.rb +38 -0
  162. data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
  163. data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
  164. data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
  165. data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
  166. data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
  167. data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
  168. data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
  169. data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
  170. data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
  171. data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
  172. data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
  173. data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
  174. data/lib/postsvg/translation/handlers/shared.rb +110 -0
  175. data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
  176. data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
  177. data/lib/postsvg/translation/handlers.rb +25 -0
  178. data/lib/postsvg/translation/ps_renderer.rb +105 -0
  179. data/lib/postsvg/translation/record_emitter.rb +35 -0
  180. data/lib/postsvg/translation.rb +17 -0
  181. data/lib/postsvg/version.rb +1 -1
  182. data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
  183. data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
  184. data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
  185. data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
  186. data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
  187. data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
  188. data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
  189. data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
  190. data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
  191. data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
  192. data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
  193. data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
  194. data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
  195. data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
  196. data/lib/postsvg/visitors/ps_visitor.rb +208 -0
  197. data/lib/postsvg/visitors.rb +9 -0
  198. data/lib/postsvg.rb +93 -59
  199. data/lychee.toml +86 -0
  200. metadata +216 -11
  201. data/postsvg.gemspec +0 -38
@@ -0,0 +1,836 @@
1
+ = Coordinate Systems
2
+ :page-nav_order: 3
3
+ :page-parent: Core Concepts
4
+
5
+ == Purpose
6
+
7
+ This document explains the fundamental differences between PostScript and SVG coordinate systems and how Postsvg handles the coordinate transformation between them. Understanding coordinate systems is essential for debugging rendering issues and working with transformations.
8
+
9
+ == References
10
+
11
+ * link:../index.adoc[Documentation Home]
12
+ * link:../concepts.adoc[Core Concepts Overview]
13
+ * link:graphics-state.adoc[Graphics State Management]
14
+ * link:conversion-pipeline.adoc[Conversion Pipeline]
15
+ * link:../architecture.adoc[Architecture Documentation]
16
+
17
+ == Concepts
18
+
19
+ **User Space**:: The coordinate system used in PostScript source code, before transformations are applied.
20
+
21
+ **Device Space**:: The coordinate system of the output device (screen, printer, SVG canvas).
22
+
23
+ **Current Transformation Matrix (CTM)**:: The matrix that transforms coordinates from user space to device space.
24
+
25
+ **Origin**:: The (0, 0) point of a coordinate system, which differs between PostScript and SVG.
26
+
27
+ **Y-Axis Orientation**:: The direction of the Y-axis, which is inverted between PostScript and SVG.
28
+
29
+ == PostScript Coordinate System
30
+
31
+ === Origin and Orientation
32
+
33
+ PostScript uses a **bottom-left origin** with **Y increasing upward**.
34
+
35
+ .PostScript Coordinate System
36
+ [source]
37
+ ----
38
+ Y
39
+
40
+
41
+ 100 │ ┌─────────┐
42
+ │ │ Content │
43
+ 50 │ │ │
44
+ │ └─────────┘
45
+ 0 └─────┴────┴────→ X
46
+ 0 50 100
47
+ ----
48
+
49
+ **Characteristics:**
50
+
51
+ * Origin (0, 0) is at **bottom-left**
52
+ * X-axis increases **left to right**
53
+ * Y-axis increases **bottom to top**
54
+ * Matches mathematical convention
55
+ * Used by printers and traditional graphics systems
56
+
57
+ === Default Page Size
58
+
59
+ PostScript typically uses these standard page sizes (at 72 DPI):
60
+
61
+ **US Letter:**
62
+ [source,postscript]
63
+ ----
64
+ %%BoundingBox: 0 0 612 792
65
+ % Width: 612 points (8.5 inches)
66
+ % Height: 792 points (11 inches)
67
+ ----
68
+
69
+ **A4:**
70
+ [source,postscript]
71
+ ----
72
+ %%BoundingBox: 0 0 595 842
73
+ % Width: 595 points (210mm)
74
+ % Height: 842 points (297mm)
75
+ ----
76
+
77
+ === Coordinate Example
78
+
79
+ .PostScript Coordinates
80
+ [example]
81
+ ====
82
+ [source,postscript]
83
+ ----
84
+ %!PS-Adobe-3.0
85
+ %%BoundingBox: 0 0 200 200
86
+
87
+ % Point at (50, 150) - near top-left
88
+ 50 150 moveto
89
+ 150 150 lineto
90
+ stroke
91
+
92
+ % Point at (50, 50) - near bottom-left
93
+ 50 50 moveto
94
+ 150 50 lineto
95
+ stroke
96
+ ----
97
+
98
+ Visual representation:
99
+
100
+ [source]
101
+ ----
102
+ 200 ┌───────────────┐
103
+ │ │
104
+ 150 │ ●─────────● │ ← (50,150) to (150,150)
105
+ │ │
106
+ 100 │ │
107
+ │ │
108
+ 50 │ ●─────────● │ ← (50,50) to (150,50)
109
+ │ │
110
+ 0 └───────────────┘
111
+ 0 100 200
112
+ ----
113
+ ====
114
+
115
+ == SVG Coordinate System
116
+
117
+ === Origin and Orientation
118
+
119
+ SVG uses a **top-left origin** with **Y increasing downward**.
120
+
121
+ .SVG Coordinate System
122
+ [source]
123
+ ----
124
+ 0 ┌─────┴────┴────→ X
125
+ │ ┌─────────┐ 50 100
126
+ │ │ Content │
127
+ 50 │ │ │
128
+ │ └─────────┘
129
+ 100 │
130
+
131
+ Y
132
+ ----
133
+
134
+ **Characteristics:**
135
+
136
+ * Origin (0, 0) is at **top-left**
137
+ * X-axis increases **left to right**
138
+ * Y-axis increases **top to bottom**
139
+ * Matches screen/display convention
140
+ * Used by web browsers and most graphics software
141
+
142
+ === ViewBox Attribute
143
+
144
+ SVG uses the `viewBox` attribute to define the coordinate system:
145
+
146
+ [source,xml]
147
+ ----
148
+ <svg viewBox="min-x min-y width height">
149
+ ----
150
+
151
+ .SVG ViewBox
152
+ [example]
153
+ ====
154
+ [source,xml]
155
+ ----
156
+ <svg xmlns="http://www.w3.org/2000/svg"
157
+ width="200" height="200"
158
+ viewBox="0 0 200 200">
159
+ <!-- Content here uses viewBox coordinates -->
160
+ </svg>
161
+ ----
162
+
163
+ * `viewBox="0 0 200 200"` means:
164
+ * min-x: 0
165
+ * min-y: 0
166
+ * width: 200
167
+ * height: 200
168
+ ====
169
+
170
+ == The Coordinate System Problem
171
+
172
+ The **fundamental challenge** in PostScript to SVG conversion is the Y-axis orientation difference.
173
+
174
+ .Coordinate System Comparison
175
+ [source]
176
+ ----
177
+ PostScript (Y up) SVG (Y down)
178
+
179
+ 100 ┌─────────┐ 200 0 ┌─────────┐ 0
180
+ │ A │ │ D │
181
+ 50 │ │ 150 50 │ │ 50
182
+ │ B │ │ C │
183
+ 0 └─────────┘ 100 100 └─────────┘ 100
184
+ 0 100 0 100
185
+
186
+ Point A in PostScript: Point D in SVG:
187
+ PS: (50, 80) SVG: (50, 20)
188
+ Near top of page Near top of page
189
+
190
+ Point B in PostScript: Point C in SVG:
191
+ PS: (50, 20) SVG: (50, 80)
192
+ Near bottom of page Near bottom of page
193
+ ----
194
+
195
+ **The Issue:**
196
+
197
+ A point at PostScript coordinate `(x, y)` would appear at the **wrong vertical position** if used directly in SVG.
198
+
199
+ * PS `(50, 20)` - near bottom
200
+ * SVG `(50, 20)` - near top
201
+
202
+ == Coordinate Transformation Strategy
203
+
204
+ Postsvg uses a **transformation group** approach to handle the coordinate difference.
205
+
206
+ === The Transformation
207
+
208
+ [source,xml]
209
+ ----
210
+ <g transform="translate(0 height) scale(1 -1)">
211
+ <!-- PostScript graphics elements -->
212
+ </g>
213
+ ----
214
+
215
+ **This transformation:**
216
+
217
+ 1. **Translates** the origin to the bottom-left
218
+ 2. **Scales Y by -1** to flip the Y-axis
219
+
220
+ .Transformation Breakdown
221
+ [example]
222
+ ====
223
+ Given a page height of 200:
224
+
225
+ [source,xml]
226
+ ----
227
+ <g transform="translate(0 200) scale(1 -1)">
228
+ ----
229
+
230
+ **Step 1: Translate (0, 200)**
231
+
232
+ [source]
233
+ ----
234
+ Before: After translate(0, 200):
235
+ 0,0 0,0
236
+ ┌──── │
237
+ │ │
238
+ │ │ 200
239
+ │ ↓
240
+ 200 ┌──── ← New origin
241
+
242
+ ----
243
+
244
+ **Step 2: Scale (1, -1)**
245
+
246
+ [source]
247
+ ----
248
+ After translate: After scale(1, -1):
249
+
250
+ 200 ┌──── 0 ┌──── ← Y increasing downward
251
+ │ │ becomes upward
252
+ │ │
253
+ ↓ ↑
254
+ Y 200
255
+ ----
256
+
257
+ **Result:** Y-axis now increases upward, matching PostScript!
258
+ ====
259
+
260
+ === Why This Works
261
+
262
+ The combined transformation `translate(0 h) scale(1 -1)`:
263
+
264
+ [source]
265
+ ----
266
+ Original SVG point: (x, y)
267
+ After translate: (x, y + h)
268
+ After scale: (x, h - y)
269
+
270
+ This is equivalent to the PostScript coordinate!
271
+ ----
272
+
273
+ **Example:**
274
+
275
+ For height = 200:
276
+
277
+ [source]
278
+ ----
279
+ PS point (50, 150):
280
+ → SVG before transform: would be at (50, 150)
281
+ → SVG with transform: translate gives (50, 350)
282
+ scale gives (50, 200-350) = (50, -150)
283
+ Which renders at correct position!
284
+
285
+ Actually, the math is:
286
+ PS (50, 150) → transform → SVG renders at visual position
287
+ that matches PS (50, 150) appearance
288
+ ----
289
+
290
+ == User Space vs. Device Space
291
+
292
+ === User Space
293
+
294
+ **User space** is the coordinate system used in PostScript source code. It's modified by transformation operators:
295
+
296
+ [source,postscript]
297
+ ----
298
+ % User space coordinates
299
+ 100 100 moveto % In current user space
300
+ 200 200 lineto
301
+ stroke
302
+
303
+ % Transform user space
304
+ 2 2 scale % User space units are now 2x larger
305
+ 100 100 moveto % Actually 200,200 in original user space
306
+ ----
307
+
308
+ === Device Space
309
+
310
+ **Device space** is the final coordinate system of the output device after all transformations are applied.
311
+
312
+ .User Space to Device Space
313
+ [source]
314
+ ----
315
+ User Space CTM Device Space
316
+ (100, 100) ──────────────→ (transformed coordinates)
317
+ [a b c d e f]
318
+
319
+ Example with CTM = [2 0 0 2 0 0] (scale 2x):
320
+ User (100, 100) → Device (200, 200)
321
+ ----
322
+
323
+ === The Current Transformation Matrix (CTM)
324
+
325
+ The **CTM** is a 2×3 affine transformation matrix: `[a b c d e f]`
326
+
327
+ .CTM Structure
328
+ [source]
329
+ ----
330
+ [ a c e ] Matrix form: [ a c e ]
331
+ [ b d f ] [ b d f ]
332
+ [ 0 0 1 ] [ 0 0 1 ]
333
+
334
+ Point transformation:
335
+ [ x' ] [ a c e ] [ x ] [ ax + cy + e ]
336
+ [ y' ] = [ b d f ] × [ y ] = [ bx + dy + f ]
337
+ [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
338
+ ----
339
+
340
+ **Components:**
341
+
342
+ * `a, d` - **Scaling** factors
343
+ * `b, c` - **Rotation/skew** factors
344
+ * `e, f` - **Translation** offsets
345
+
346
+ === Identity CTM
347
+
348
+ The **default CTM** is the identity matrix:
349
+
350
+ [source,ruby]
351
+ ----
352
+ [1, 0, 0, 1, 0, 0]
353
+ ----
354
+
355
+ This maps user space 1:1 to device space (no transformation).
356
+
357
+ == Transformation Operations
358
+
359
+ === Translation
360
+
361
+ Shifts the origin without changing orientation or scale.
362
+
363
+ [source,postscript]
364
+ ----
365
+ tx ty translate
366
+ ----
367
+
368
+ **Effect on CTM:**
369
+
370
+ [source]
371
+ ----
372
+ Before: [a b c d e f]
373
+ After: [a b c d (e+tx) (f+ty)]
374
+ ----
375
+
376
+ .Translation Example
377
+ [example]
378
+ ====
379
+ [source,postscript]
380
+ ----
381
+ % Move origin to (100, 100)
382
+ 100 100 translate
383
+
384
+ % Now (0, 0) in user space = (100, 100) in original space
385
+ 0 0 moveto % Actually at (100, 100)
386
+ 50 50 lineto % Actually at (150, 150)
387
+ stroke
388
+ ----
389
+
390
+ Visual:
391
+
392
+ [source]
393
+ ----
394
+ Original: After translate(100, 100):
395
+ 0,0 0,0
396
+ ┌──── │
397
+ │ │
398
+ │ 100,100 │
399
+ │ ● │ ● ← New 0,0
400
+ │ │ ┌───
401
+
402
+ ----
403
+ ====
404
+
405
+ === Scaling
406
+
407
+ Changes the size of user space units.
408
+
409
+ [source,postscript]
410
+ ----
411
+ sx sy scale
412
+ ----
413
+
414
+ **Effect on CTM:**
415
+
416
+ [source]
417
+ ----
418
+ Before: [a b c d e f]
419
+ After: [a×sx b×sx c×sy d×sy e f]
420
+ ----
421
+
422
+ .Scaling Example
423
+ [example]
424
+ ====
425
+ [source,postscript]
426
+ ----
427
+ % Double all coordinates
428
+ 2 2 scale
429
+
430
+ % Drawing in user space
431
+ 0 0 50 50 rectfill % Actually draws 100×100 rectangle
432
+ ----
433
+
434
+ **Non-uniform scaling:**
435
+
436
+ [source,postscript]
437
+ ----
438
+ % Stretch horizontally, shrink vertically
439
+ 3 0.5 scale
440
+
441
+ 0 0 100 100 rectfill % Draws 300×50 rectangle
442
+ ----
443
+ ====
444
+
445
+ === Rotation
446
+
447
+ Rotates the coordinate system about the origin.
448
+
449
+ [source,postscript]
450
+ ----
451
+ angle rotate % angle in degrees, counter-clockwise
452
+ ----
453
+
454
+ **Effect on CTM:**
455
+
456
+ [source]
457
+ ----
458
+ Before: [a b c d e f]
459
+ Rotation matrix R = [cos(θ) sin(θ) -sin(θ) cos(θ) 0 0]
460
+ After: R × CTM
461
+ ----
462
+
463
+ .Rotation Example
464
+ [example]
465
+ ====
466
+ [source,postscript]
467
+ ----
468
+ % Rotate 45 degrees counter-clockwise
469
+ 45 rotate
470
+
471
+ 0 0 moveto
472
+ 100 0 lineto
473
+ stroke
474
+ % Draws line at 45° angle from origin
475
+ ----
476
+
477
+ Visual:
478
+
479
+ [source]
480
+ ----
481
+ Before rotation: After 45° rotation:
482
+ Y Y
483
+ ↑ ↑ ╱
484
+ │ │ ╱
485
+ │ │╱
486
+ │─────→ X ├────→ X
487
+ 0 0
488
+
489
+ 100 0 lineto Line now at 45° angle
490
+ ────→ ╱
491
+
492
+ ----
493
+ ====
494
+
495
+ === Rotation About a Point
496
+
497
+ To rotate about a point other than origin:
498
+
499
+ [source,postscript]
500
+ ----
501
+ % Rotate 45° about point (100, 100)
502
+ 100 100 translate % 1. Move point to origin
503
+ 45 rotate % 2. Rotate
504
+ -100 -100 translate % 3. Move back
505
+ ----
506
+
507
+ .Rotation About Point
508
+ [example]
509
+ ====
510
+ [source,postscript]
511
+ ----
512
+ % Draw square
513
+ 0 0 100 100 rectfill
514
+
515
+ % Rotate square 45° about its center (50, 50)
516
+ gsave
517
+ 50 50 translate
518
+ 45 rotate
519
+ -50 -50 translate
520
+ 0 0 100 100 rectfill
521
+ grestore
522
+ ----
523
+
524
+ Transformation sequence:
525
+
526
+ [source]
527
+ ----
528
+ Original: Translate(50,50): Rotate 45°:
529
+ ┌───┐ ┌───┐ ◇
530
+ │ │ │ │ ╱ ╲
531
+ │ │ → │ + │ → ╱ ╲
532
+ │ │ │ │ ╲ ╱
533
+ └───┘ └───┘ ╲ ╱
534
+ ↑ ◇
535
+ Center now at origin
536
+ ----
537
+ ====
538
+
539
+ == Matrix Operations in Postsvg
540
+
541
+ === Matrix Class
542
+
543
+ The [`Matrix`](../../lib/postsvg/matrix.rb:6) class implements affine transformations:
544
+
545
+ [source,ruby]
546
+ ----
547
+ # Create identity matrix
548
+ matrix = Matrix.new
549
+ # => { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }
550
+
551
+ # Create custom matrix
552
+ matrix = Matrix.new(a: 2, d: 2) # 2x scaling
553
+ ----
554
+
555
+ === Applying Transformations
556
+
557
+ [source,ruby]
558
+ ----
559
+ # Translation
560
+ matrix = matrix.translate(10, 20)
561
+
562
+ # Scaling
563
+ matrix = matrix.scale(2, 3)
564
+
565
+ # Rotation (in degrees)
566
+ matrix = matrix.rotate(45)
567
+
568
+ # Transform a point
569
+ point = matrix.apply_point(100, 100)
570
+ # => { x: transformed_x, y: transformed_y }
571
+ ----
572
+
573
+ === Matrix Multiplication
574
+
575
+ Transformations compose via matrix multiplication:
576
+
577
+ [source,ruby]
578
+ ----
579
+ # Multiple transformations
580
+ matrix = Matrix.new
581
+ matrix = matrix.translate(10, 20)
582
+ matrix = matrix.rotate(45)
583
+ matrix = matrix.scale(2, 2)
584
+
585
+ # Same as:
586
+ m1 = Matrix.new.translate(10, 20)
587
+ m2 = Matrix.new.rotate(45)
588
+ m3 = Matrix.new.scale(2, 2)
589
+ result = m1.multiply(m2).multiply(m3)
590
+ ----
591
+
592
+ **Order matters!** `A × B ≠ B × A`
593
+
594
+ .Transformation Order Example
595
+ [example]
596
+ ====
597
+ [source,ruby]
598
+ ----
599
+ # Translate THEN scale
600
+ m1 = Matrix.new.translate(10, 10).scale(2, 2)
601
+ # (100, 100) → (110, 110) → (220, 220)
602
+
603
+ # Scale THEN translate
604
+ m2 = Matrix.new.scale(2, 2).translate(10, 10)
605
+ # (100, 100) → (200, 200) → (210, 210)
606
+
607
+ # Different results!
608
+ ----
609
+ ====
610
+
611
+ === Matrix Decomposition
612
+
613
+ The `decompose` method extracts transformation components:
614
+
615
+ [source,ruby]
616
+ ----
617
+ matrix = Matrix.new
618
+ matrix = matrix.translate(10, 20)
619
+ matrix = matrix.rotate(45)
620
+ matrix = matrix.scale(2, 3)
621
+
622
+ components = matrix.decompose
623
+ # => {
624
+ # translate: { x: 10, y: 20 },
625
+ # rotate: 45,
626
+ # scale: { x: 2, y: 3 },
627
+ # skew: { x: 0, y: 0 }
628
+ # }
629
+ ----
630
+
631
+ This is useful for generating SVG transform strings.
632
+
633
+ == Coordinate Transformation in Practice
634
+
635
+ === PostScript to SVG Path Conversion
636
+
637
+ When converting a PostScript path to SVG:
638
+
639
+ .Path Coordinate Example
640
+ [example]
641
+ ====
642
+ **PostScript:**
643
+
644
+ [source,postscript]
645
+ ----
646
+ %%BoundingBox: 0 0 200 200
647
+ newpath
648
+ 50 50 moveto % Bottom-left area
649
+ 150 150 lineto % Top-right area
650
+ stroke
651
+ ----
652
+
653
+ **Direct (Wrong) SVG:**
654
+
655
+ [source,xml]
656
+ ----
657
+ <svg viewBox="0 0 200 200">
658
+ <!-- Point (50,50) appears near TOP-left! -->
659
+ <!-- Point (150,150) appears near BOTTOM-right! -->
660
+ <path d="M 50 50 L 150 150" stroke="black"/>
661
+ </svg>
662
+ ----
663
+
664
+ **Correct SVG with transform:**
665
+
666
+ [source,xml]
667
+ ----
668
+ <svg viewBox="0 0 200 200">
669
+ <g transform="translate(0 200) scale(1 -1)">
670
+ <!-- Now (50,50) appears near bottom-left -->
671
+ <!-- And (150,150) appears near top-right -->
672
+ <path d="M 50 50 L 150 150" stroke="black"/>
673
+ </g>
674
+ </svg>
675
+ ----
676
+ ====
677
+
678
+ === Handling the BoundingBox
679
+
680
+ The BoundingBox comment defines the page dimensions:
681
+
682
+ [source,postscript]
683
+ ----
684
+ %%BoundingBox: llx lly urx ury
685
+ ----
686
+
687
+ **Where:**
688
+
689
+ * `llx, lly` - Lower-left corner coordinates
690
+ * `urx, ury` - Upper-right corner coordinates
691
+
692
+ .BoundingBox to ViewBox
693
+ [example]
694
+ ====
695
+ [source,postscript]
696
+ ----
697
+ %%BoundingBox: 0 0 612 792
698
+ % US Letter page
699
+ ----
700
+
701
+ Converts to:
702
+
703
+ [source,xml]
704
+ ----
705
+ <svg viewBox="0 0 612 792"
706
+ width="612"
707
+ height="792">
708
+ <g transform="translate(0 792) scale(1 -1)">
709
+ <!-- Content -->
710
+ </g>
711
+ </svg>
712
+ ----
713
+
714
+ The transform height (792) comes from the BoundingBox height.
715
+ ====
716
+
717
+ === Non-Zero BoundingBox Origin
718
+
719
+ Sometimes BoundingBox doesn't start at (0, 0):
720
+
721
+ [source,postscript]
722
+ ----
723
+ %%BoundingBox: 50 50 250 250
724
+ % 200×200 area, offset by (50, 50)
725
+ ----
726
+
727
+ .Offset BoundingBox Handling
728
+ [example]
729
+ ====
730
+ **Approach 1: Adjust viewBox**
731
+
732
+ [source,xml]
733
+ ----
734
+ <svg viewBox="50 50 200 200"
735
+ width="200"
736
+ height="200">
737
+ <g transform="translate(0 200) scale(1 -1)">
738
+ <!-- Content uses original coordinates -->
739
+ </g>
740
+ </svg>
741
+ ----
742
+
743
+ **Approach 2: Normalize to origin**
744
+
745
+ [source,xml]
746
+ ----
747
+ <svg viewBox="0 0 200 200"
748
+ width="200"
749
+ height="200">
750
+ <g transform="translate(-50 200) scale(1 -1) translate(0 -50)">
751
+ <!-- Content adjusted to start at (0, 0) -->
752
+ </g>
753
+ </svg>
754
+ ----
755
+ ====
756
+
757
+ == Common Coordinate Issues
758
+
759
+ === Issue 1: Upside-Down Content
760
+
761
+ **Symptom:** Graphics appear flipped vertically
762
+
763
+ **Cause:** Missing or incorrect Y-axis flip transformation
764
+
765
+ **Solution:**
766
+
767
+ [source,xml]
768
+ ----
769
+ <!-- Wrong: No transform -->
770
+ <svg><path d="..." /></svg>
771
+
772
+ <!-- Correct: Y-axis flip -->
773
+ <svg>
774
+ <g transform="translate(0 height) scale(1 -1)">
775
+ <path d="..." />
776
+ </g>
777
+ </svg>
778
+ ----
779
+
780
+ === Issue 2: Content Outside ViewBox
781
+
782
+ **Symptom:** Graphics not visible or clipped
783
+
784
+ **Cause:** ViewBox doesn't match BoundingBox
785
+
786
+ **Solution:**
787
+
788
+ [source,ruby]
789
+ ----
790
+ # Extract BoundingBox correctly
791
+ bbox = {
792
+ llx: 0, lly: 0,
793
+ urx: 612, ury: 792,
794
+ width: 612, height: 792
795
+ }
796
+
797
+ # Use in SVG
798
+ viewBox = "#{bbox[:llx]} #{bbox[:lly]} #{bbox[:width]} #{bbox[:height]}"
799
+ ----
800
+
801
+ === Issue 3: Transformation Order
802
+
803
+ **Symptom:** Transformations produce unexpected results
804
+
805
+ **Cause:** Wrong order of transformations
806
+
807
+ **Example:**
808
+
809
+ [source,postscript]
810
+ ----
811
+ % Wrong order
812
+ 10 10 translate
813
+ 2 2 scale
814
+ % (100,100) → (110,110) → (220,220)
815
+
816
+ % Correct order for centering scale
817
+ 2 2 scale
818
+ 10 10 translate
819
+ % (100,100) → (200,200) → (210,210)
820
+ ----
821
+
822
+ == Next Steps
823
+
824
+ * Review link:graphics-state.adoc[Graphics State Management] for CTM details
825
+ * Explore link:path-operations.adoc[Path Operations] for coordinate usage
826
+ * See link:svg-generation.adoc[SVG Generation] for output details
827
+ * Check link:../architecture.adoc[Architecture] for implementation
828
+ * Read link:conversion-pipeline.adoc[Conversion Pipeline] for the full process
829
+
830
+ == Bibliography
831
+
832
+ * link:graphics-state.adoc[Graphics State Management]
833
+ * link:../architecture/graphics-state-model.adoc[Graphics State Implementation]
834
+ * link:svg-generation.adoc[SVG Generation]
835
+ * link:https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual] - Chapter 4: Graphics
836
+ * link:https://www.w3.org/TR/SVG/coords.html[SVG Coordinate Systems Specification]