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,1076 @@
1
+ = Path Operations
2
+ :page-nav_order: 4
3
+ :page-parent: Core Concepts
4
+
5
+ == Purpose
6
+
7
+ This document explains path construction and manipulation in Postsvg, covering how PostScript path operators build vector paths and how these paths are painted and converted to SVG. Understanding path operations is fundamental to working with vector graphics in PostScript and SVG.
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:coordinate-systems.adoc[Coordinate Systems]
15
+ * link:svg-generation.adoc[SVG Generation]
16
+
17
+ == Concepts
18
+
19
+ **Path**:: A sequence of connected and disconnected line and curve segments that defines a shape.
20
+
21
+ **Current Path**:: The path being constructed, stored in the graphics state until painted or cleared.
22
+
23
+ **Current Point**:: The endpoint of the last path segment, used as the starting point for the next segment.
24
+
25
+ **Subpath**:: A continuous sequence of connected segments within a path; paths can contain multiple subpaths.
26
+
27
+ **Path Painting**:: Rendering a path by stroking (outlining) or filling (interior coloring).
28
+
29
+ == Path Construction Model
30
+
31
+ === The Path Building Process
32
+
33
+ PostScript builds paths incrementally through a sequence of operators:
34
+
35
+ .Path Construction Flow
36
+ [source]
37
+ ----
38
+ ┌──────────────────────────────────────────────────────────┐
39
+ │ 1. newpath → Clear current path │
40
+ │ Current path: [] │
41
+ │ Current point: undefined │
42
+ └──────────────────────────────────────────────────────────┘
43
+
44
+ ┌──────────────────────────────────────────────────────────┐
45
+ │ 2. moveto → Start new subpath │
46
+ │ Current path: [(M x,y)] │
47
+ │ Current point: (x, y) │
48
+ └──────────────────────────────────────────────────────────┘
49
+
50
+ ┌──────────────────────────────────────────────────────────┐
51
+ │ 3. lineto/curveto → Add segments │
52
+ │ Current path: [(M x1,y1)(L x2,y2)(L x3,y3)] │
53
+ │ Current point: (x3, y3) │
54
+ └──────────────────────────────────────────────────────────┘
55
+
56
+ ┌──────────────────────────────────────────────────────────┐
57
+ │ 4. closepath → Close subpath (optional) │
58
+ │ Current path: [(M x1,y1)(L x2,y2)(L x3,y3)(Z)] │
59
+ │ Current point: (x1, y1) - back to start │
60
+ └──────────────────────────────────────────────────────────┘
61
+
62
+ ┌──────────────────────────────────────────────────────────┐
63
+ │ 5. stroke/fill → Paint and clear path │
64
+ │ Current path: [] - cleared after painting │
65
+ │ Current point: undefined │
66
+ └──────────────────────────────────────────────────────────┘
67
+ ----
68
+
69
+ === Path State Components
70
+
71
+ The path state is maintained in the graphics state:
72
+
73
+ **Current Path**:: Array of path segments (moveto, lineto, curveto, closepath)
74
+
75
+ **Current Point**:: Last touched coordinate (x, y)
76
+
77
+ **Path Validity**:: Whether a current point is defined
78
+
79
+ .Path State Example
80
+ [example]
81
+ ====
82
+ [source,postscript]
83
+ ----
84
+ newpath
85
+ % Path: [], Current point: undefined
86
+
87
+ 100 100 moveto
88
+ % Path: [(M 100,100)], Current point: (100,100)
89
+
90
+ 200 100 lineto
91
+ % Path: [(M 100,100)(L 200,100)], Current point: (200,100)
92
+
93
+ 200 200 lineto
94
+ % Path: [(M 100,100)(L 200,100)(L 200,200)], Current point: (200,200)
95
+
96
+ closepath
97
+ % Path: [(M 100,100)(L 200,100)(L 200,200)(Z)], Current point: (100,100)
98
+ ----
99
+ ====
100
+
101
+ == Path Construction Operators
102
+
103
+ === newpath
104
+
105
+ Clears the current path and resets the current point to undefined.
106
+
107
+ **Syntax:**
108
+
109
+ [source,postscript]
110
+ ----
111
+ - newpath -
112
+ ----
113
+
114
+ **Purpose:**
115
+
116
+ * Start fresh path construction
117
+ * Clear any previously constructed path
118
+ * Prepare for a new shape
119
+
120
+ .Using newpath
121
+ [example]
122
+ ====
123
+ [source,postscript]
124
+ ----
125
+ % First shape
126
+ newpath
127
+ 100 100 moveto
128
+ 200 200 lineto
129
+ stroke % Paints and clears path
130
+
131
+ % Second shape (newpath not strictly needed after stroke)
132
+ newpath % Explicitly start new path
133
+ 50 50 moveto
134
+ 150 50 lineto
135
+ stroke
136
+ ----
137
+ ====
138
+
139
+ **Important:** `stroke` and `fill` automatically clear the current path, so `newpath` is often redundant after painting operations.
140
+
141
+ === moveto
142
+
143
+ Begins a new subpath at the specified coordinates.
144
+
145
+ **Syntax:**
146
+
147
+ [source,postscript]
148
+ ----
149
+ x y moveto -
150
+ ----
151
+
152
+ **Behavior:**
153
+
154
+ * Sets current point to (x, y)
155
+ * Starts a new subpath
156
+ * Does not draw anything
157
+ * If path already exists, creates a disconnected subpath
158
+
159
+ .moveto Examples
160
+ [example]
161
+ ====
162
+ [source,postscript]
163
+ ----
164
+ % Start a path
165
+ 100 100 moveto
166
+ 200 100 lineto
167
+ stroke
168
+ % Draws single line from (100,100) to (200,100)
169
+
170
+ % Multiple subpaths
171
+ newpath
172
+ 50 50 moveto % First subpath
173
+ 150 50 lineto
174
+ 200 100 moveto % Second subpath (disconnected)
175
+ 200 200 lineto
176
+ stroke
177
+ % Draws two separate line segments
178
+ ----
179
+ ====
180
+
181
+ **Implementation:**
182
+
183
+ The [`Moveto`](../../lib/postsvg/commands/path/moveto.rb:59) command:
184
+
185
+ [source,ruby]
186
+ ----
187
+ def execute(context)
188
+ y = context.pop_number
189
+ x = context.pop_number
190
+ context.path_builder.move_to(x, y)
191
+ context.current_x = x
192
+ context.current_y = y
193
+ end
194
+ ----
195
+
196
+ === rmoveto
197
+
198
+ Relative moveto - moves relative to current point.
199
+
200
+ **Syntax:**
201
+
202
+ [source,postscript]
203
+ ----
204
+ dx dy rmoveto -
205
+ ----
206
+
207
+ **Behavior:**
208
+
209
+ * Moves to (current_x + dx, current_y + dy)
210
+ * Requires existing current point
211
+ * Useful for relative positioning
212
+
213
+ .rmoveto Example
214
+ [example]
215
+ ====
216
+ [source,postscript]
217
+ ----
218
+ 100 100 moveto % Current point: (100, 100)
219
+ 50 0 rmoveto % Move to (150, 100)
220
+ 0 50 rlineto % Line to (150, 150)
221
+ stroke
222
+ ----
223
+ ====
224
+
225
+ === lineto
226
+
227
+ Adds a straight line from current point to specified coordinates.
228
+
229
+ **Syntax:**
230
+
231
+ [source,postscript]
232
+ ----
233
+ x y lineto -
234
+ ----
235
+
236
+ **Behavior:**
237
+
238
+ * Draws line from current point to (x, y)
239
+ * Sets current point to (x, y)
240
+ * Requires defined current point
241
+ * Extends current subpath
242
+
243
+ .lineto Examples
244
+ [example]
245
+ ====
246
+ [source,postscript]
247
+ ----
248
+ % Simple line
249
+ 100 100 moveto
250
+ 200 200 lineto
251
+ stroke
252
+
253
+ % Polyline (connected segments)
254
+ 50 50 moveto
255
+ 100 75 lineto
256
+ 150 50 lineto
257
+ 200 100 lineto
258
+ stroke
259
+
260
+ % Rectangle using lineto
261
+ 100 100 moveto
262
+ 200 100 lineto
263
+ 200 200 lineto
264
+ 100 200 lineto
265
+ closepath
266
+ fill
267
+ ----
268
+ ====
269
+
270
+ **Implementation:**
271
+
272
+ The [`Lineto`](../../lib/postsvg/commands/path/lineto.rb:57) command:
273
+
274
+ [source,ruby]
275
+ ----
276
+ def execute(context)
277
+ y = context.pop_number
278
+ x = context.pop_number
279
+ context.path_builder.line_to(x, y)
280
+ context.current_x = x
281
+ context.current_y = y
282
+ end
283
+ ----
284
+
285
+ === rlineto
286
+
287
+ Relative lineto - adds line relative to current point.
288
+
289
+ **Syntax:**
290
+
291
+ [source,postscript]
292
+ ----
293
+ dx dy rlineto -
294
+ ----
295
+
296
+ .rlineto Example
297
+ [example]
298
+ ====
299
+ [source,postscript]
300
+ ----
301
+ % Draw square using relative coordinates
302
+ 100 100 moveto % Start at (100,100)
303
+ 100 0 rlineto % Right 100 to (200,100)
304
+ 0 100 rlineto % Up 100 to (200,200)
305
+ -100 0 rlineto % Left 100 to (100,200)
306
+ closepath % Back to start
307
+ fill
308
+ ----
309
+ ====
310
+
311
+ === curveto
312
+
313
+ Adds a cubic Bézier curve to the path.
314
+
315
+ **Syntax:**
316
+
317
+ [source,postscript]
318
+ ----
319
+ x1 y1 x2 y2 x3 y3 curveto -
320
+ ----
321
+
322
+ **Where:**
323
+
324
+ * Current point (x0, y0) - curve start
325
+ * (x1, y1) - first control point
326
+ * (x2, y2) - second control point
327
+ * (x3, y3) - curve end (new current point)
328
+
329
+ **Bézier Curve Mathematics:**
330
+
331
+ The curve is defined by:
332
+
333
+ [source]
334
+ ----
335
+ x(t) = (1-t)³·x0 + 3(1-t)²t·x1 + 3(1-t)t²·x2 + t³·x3
336
+ y(t) = (1-t)³·y0 + 3(1-t)²t·y1 + 3(1-t)t²·y2 + t³·y3
337
+
338
+ where t ranges from 0 to 1
339
+ ----
340
+
341
+ .Bézier Curve Geometry
342
+ [source]
343
+ ----
344
+ (x1,y1) (x2,y2)
345
+ ● ●
346
+ ╱ ╲ ╱ ╲
347
+ ╱ ╲___ ___╱ ╲
348
+ ╱ ╲ ╱ ╲
349
+ (x0,y0)● ● ●(x3,y3)
350
+ start curve end
351
+
352
+ Properties:
353
+ - Curve starts at (x0,y0), ends at (x3,y3)
354
+ - Tangent at start points toward (x1,y1)
355
+ - Tangent at end points from (x2,y2)
356
+ - Curve contained in convex hull of 4 points
357
+ ----
358
+
359
+ .curveto Examples
360
+ [example]
361
+ ====
362
+ [source,postscript]
363
+ ----
364
+ % Simple S-curve
365
+ 100 100 moveto
366
+ 150 150 250 150 300 100 curveto
367
+ stroke
368
+
369
+ % Smooth curve chain
370
+ 100 100 moveto
371
+ 150 50 200 50 250 100 curveto % First curve
372
+ 300 150 350 150 400 100 curveto % Second curve
373
+ stroke
374
+
375
+ % Approximate circle (4 curves)
376
+ % Using magic number 0.5522847498 for 90° arcs
377
+ /r 50 def
378
+ /k 0.5522847498 def
379
+ 200 200 moveto
380
+ 200 r k mul add 200 r add 200 k mul add 200 r add 200 200 r add curveto
381
+ 200 r sub k mul 200 r add 200 r sub 200 r add 200 k mul sub 200 r add curveto
382
+ 200 r sub 200 r add k mul sub 200 r sub k mul 200 r add 200 r sub curveto
383
+ 200 k mul 200 r sub 200 200 r sub k mul add 200 r sub 200 200 r sub curveto
384
+ closepath
385
+ stroke
386
+ ----
387
+ ====
388
+
389
+ **Implementation:**
390
+
391
+ The [`Curveto`](../../lib/postsvg/commands/path/curveto.rb:61) command:
392
+
393
+ [source,ruby]
394
+ ----
395
+ def execute(context)
396
+ y3 = context.pop_number
397
+ x3 = context.pop_number
398
+ y2 = context.pop_number
399
+ x2 = context.pop_number
400
+ y1 = context.pop_number
401
+ x1 = context.pop_number
402
+ context.path_builder.curve_to(x1, y1, x2, y2, x3, y3)
403
+ context.current_x = x3
404
+ context.current_y = y3
405
+ end
406
+ ----
407
+
408
+ === rcurveto
409
+
410
+ Relative curveto - curve with relative coordinates.
411
+
412
+ **Syntax:**
413
+
414
+ [source,postscript]
415
+ ----
416
+ dx1 dy1 dx2 dy2 dx3 dy3 rcurveto -
417
+ ----
418
+
419
+ All coordinates are relative to the current point.
420
+
421
+ === closepath
422
+
423
+ Closes the current subpath by adding a line back to the subpath's starting point.
424
+
425
+ **Syntax:**
426
+
427
+ [source,postscript]
428
+ ----
429
+ - closepath -
430
+ ----
431
+
432
+ **Behavior:**
433
+
434
+ * Adds straight line from current point to subpath start
435
+ * Marks subpath as closed
436
+ * Sets current point to subpath start
437
+ * Subsequent path operators start a new subpath
438
+
439
+ .closepath Behavior
440
+ [source]
441
+ ----
442
+ Without closepath: With closepath:
443
+ 100,100 → 200,100 100,100 → 200,100
444
+ ↓ ↑ ↓
445
+ 100,200 ← 200,200 100,200 ← 200,200
446
+
447
+ Gap at corner Closed corner
448
+ ----
449
+
450
+ .closepath Examples
451
+ [example]
452
+ ====
453
+ [source,postscript]
454
+ ----
455
+ % Triangle without closepath
456
+ newpath
457
+ 100 100 moveto
458
+ 200 100 lineto
459
+ 150 200 lineto
460
+ stroke
461
+ % Shows three lines, not fully closed
462
+
463
+ % Triangle with closepath
464
+ newpath
465
+ 100 100 moveto
466
+ 200 100 lineto
467
+ 150 200 lineto
468
+ closepath
469
+ stroke
470
+ % Shows fully closed triangle
471
+
472
+ % Multiple closed subpaths
473
+ newpath
474
+ % First square
475
+ 50 50 moveto
476
+ 150 50 lineto
477
+ 150 150 lineto
478
+ 50 150 lineto
479
+ closepath
480
+ % Second square
481
+ 200 200 moveto
482
+ 300 200 lineto
483
+ 300 300 lineto
484
+ 200 300 lineto
485
+ closepath
486
+ fill
487
+ % Fills both squares
488
+ ----
489
+ ====
490
+
491
+ **Implementation:**
492
+
493
+ The [`ClosePath`](../../lib/postsvg/commands/path/close_path.rb:33) command:
494
+
495
+ [source,ruby]
496
+ ----
497
+ def execute(context)
498
+ return unless context.path_length.positive? &&
499
+ !context.path_builder.parts.last&.end_with?("Z")
500
+
501
+ context.path_builder.close
502
+ end
503
+ ----
504
+
505
+ == Subpaths
506
+
507
+ === What are Subpaths?
508
+
509
+ A **subpath** is a continuous sequence of connected segments. A path can contain multiple disconnected subpaths.
510
+
511
+ .Single vs. Multiple Subpaths
512
+ [source]
513
+ ----
514
+ Single Subpath:
515
+ moveto → lineto → lineto → closepath
516
+ All segments connected
517
+
518
+ Multiple Subpaths:
519
+ moveto → lineto → lineto → closepath
520
+ moveto → lineto → lineto → closepath
521
+ Two disconnected shapes in one path
522
+ ----
523
+
524
+ === Creating Subpaths
525
+
526
+ Each `moveto` after the first creates a new subpath:
527
+
528
+ .Multiple Subpath Example
529
+ [example]
530
+ ====
531
+ [source,postscript]
532
+ ----
533
+ newpath
534
+ % First subpath (triangle)
535
+ 100 100 moveto
536
+ 200 100 lineto
537
+ 150 200 lineto
538
+ closepath
539
+
540
+ % Second subpath (square)
541
+ 250 100 moveto
542
+ 350 100 lineto
543
+ 350 200 lineto
544
+ 250 200 lineto
545
+ closepath
546
+
547
+ % Paint both subpaths
548
+ fill
549
+ ----
550
+
551
+ Both shapes are filled with a single `fill` operation.
552
+ ====
553
+
554
+ === Subpath Use Cases
555
+
556
+ **Multiple Shapes:**
557
+
558
+ [source,postscript]
559
+ ----
560
+ % Draw multiple disconnected circles
561
+ newpath
562
+ 100 100 50 0 360 arc
563
+ 250 100 50 0 360 arc
564
+ 400 100 50 0 360 arc
565
+ fill
566
+ % Fills all three circles at once
567
+ ----
568
+
569
+ **Compound Paths (Holes):**
570
+
571
+ [source,postscript]
572
+ ----
573
+ % Outer square
574
+ newpath
575
+ 50 50 moveto
576
+ 350 50 lineto
577
+ 350 350 lineto
578
+ 50 350 lineto
579
+ closepath
580
+
581
+ % Inner square (hole) - use opposite winding
582
+ 150 150 moveto
583
+ 150 250 lineto
584
+ 250 250 lineto
585
+ 250 150 lineto
586
+ closepath
587
+
588
+ fill
589
+ % Fills outer square with hole cut out
590
+ ----
591
+
592
+ == Path Painting Operations
593
+
594
+ === stroke
595
+
596
+ Paints the outline of the current path using current line attributes.
597
+
598
+ **Syntax:**
599
+
600
+ [source,postscript]
601
+ ----
602
+ - stroke -
603
+ ----
604
+
605
+ **Effects:**
606
+
607
+ * Paints path outline with current stroke color
608
+ * Uses current line width, cap, join, dash pattern
609
+ * Clears the current path after painting
610
+ * Resets current point to undefined
611
+
612
+ .stroke Examples
613
+ [example]
614
+ ====
615
+ [source,postscript]
616
+ ----
617
+ % Simple stroke
618
+ newpath
619
+ 100 100 moveto
620
+ 200 200 lineto
621
+ 1 setlinewidth
622
+ 0 0 0 setrgbcolor
623
+ stroke
624
+
625
+ % Styled stroke
626
+ newpath
627
+ 50 50 moveto
628
+ 350 50 lineto
629
+ 5 setlinewidth % Thick line
630
+ 1 setlinecap % Round caps
631
+ 1 setlinejoin % Round joins
632
+ [10 5] 0 setdash % Dashed pattern
633
+ 1 0 0 setrgbcolor % Red color
634
+ stroke
635
+ ----
636
+ ====
637
+
638
+ **Line Attributes:**
639
+
640
+ See link:graphics-state.adoc#line-attributes[Line Attributes] for details on:
641
+
642
+ * Line width
643
+ * Line cap style
644
+ * Line join style
645
+ * Dash pattern
646
+ * Miter limit
647
+
648
+ === fill
649
+
650
+ Paints the interior of the current path using the fill color.
651
+
652
+ **Syntax:**
653
+
654
+ [source,postscript]
655
+ ----
656
+ - fill -
657
+ ----
658
+
659
+ **Effects:**
660
+
661
+ * Paints path interior with current fill color
662
+ * Uses non-zero winding number rule
663
+ * Clears the current path after painting
664
+ * Resets current point to undefined
665
+
666
+ .fill Examples
667
+ [example]
668
+ ====
669
+ [source,postscript]
670
+ ----
671
+ % Simple fill
672
+ newpath
673
+ 100 100 moveto
674
+ 200 100 lineto
675
+ 200 200 lineto
676
+ 100 200 lineto
677
+ closepath
678
+ 0.5 setgray
679
+ fill
680
+
681
+ % Multiple subpaths
682
+ newpath
683
+ % Outer rectangle
684
+ 50 50 moveto
685
+ 350 50 lineto
686
+ 350 350 lineto
687
+ 50 350 lineto
688
+ closepath
689
+ % Inner rectangle (creates hole)
690
+ 150 150 moveto
691
+ 150 250 lineto
692
+ 250 250 lineto
693
+ 250 150 lineto
694
+ closepath
695
+ fill
696
+ ----
697
+ ====
698
+
699
+ === eofill
700
+
701
+ Even-odd fill - alternative fill rule.
702
+
703
+ **Syntax:**
704
+
705
+ [source,postscript]
706
+ ----
707
+ - eofill -
708
+ ----
709
+
710
+ **Fill Rules Comparison:**
711
+
712
+ .Non-Zero Winding vs. Even-Odd
713
+ [source]
714
+ ----
715
+ Non-zero winding (fill):
716
+ Counts path windings
717
+ Same direction: fills
718
+ Opposite direction: creates hole
719
+
720
+ Even-odd (eofill):
721
+ Counts path crossings
722
+ Odd crossings: fill
723
+ Even crossings: no fill
724
+ ----
725
+
726
+ .eofill Example
727
+ [example]
728
+ ====
729
+ [source,postscript]
730
+ ----
731
+ % Star with even-odd fill
732
+ newpath
733
+ % Outer star points (clockwise)
734
+ 200 50 moveto
735
+ 220 150 lineto
736
+ 320 150 lineto
737
+ 240 210 lineto
738
+ 270 310 lineto
739
+ 200 250 lineto
740
+ 130 310 lineto
741
+ 160 210 lineto
742
+ 80 150 lineto
743
+ 180 150 lineto
744
+ closepath
745
+ eofill
746
+ % Creates filled star with hollow center
747
+ ----
748
+ ====
749
+
750
+ === clip
751
+
752
+ Uses the current path as a clipping path.
753
+
754
+ **Syntax:**
755
+
756
+ [source,postscript]
757
+ ----
758
+ - clip -
759
+ ----
760
+
761
+ **Behavior:**
762
+
763
+ * Sets current path as clipping region
764
+ * Does NOT paint the path
765
+ * Does NOT clear the path (use `newpath` after)
766
+ * All subsequent operations clipped to this region
767
+
768
+ .clip Example
769
+ [example]
770
+ ====
771
+ [source,postscript]
772
+ ----
773
+ % Clip to circle
774
+ newpath
775
+ 200 200 100 0 360 arc
776
+ clip
777
+ newpath % Clear clip path without painting
778
+
779
+ % All drawing now clipped to circle
780
+ 0 0 moveto
781
+ 400 400 lineto
782
+ stroke
783
+ % Line only visible inside circle
784
+ ----
785
+ ====
786
+
787
+ == Path to SVG Conversion
788
+
789
+ === SVG Path Data Format
790
+
791
+ PostScript paths map directly to SVG path data:
792
+
793
+ .Path Command Mapping
794
+ [source]
795
+ ----
796
+ PostScript SVG Path Data
797
+ ───────────────────────────────────────
798
+ moveto x y → M x y
799
+ lineto x y → L x y
800
+ curveto x1 y1 → C x1 y1 x2 y2 x3 y3
801
+ x2 y2 x3 y3
802
+ closepath → Z
803
+ ----
804
+
805
+ .Conversion Example
806
+ [example]
807
+ ====
808
+ **PostScript:**
809
+
810
+ [source,postscript]
811
+ ----
812
+ newpath
813
+ 100 100 moveto
814
+ 200 100 lineto
815
+ 200 200 lineto
816
+ 100 200 lineto
817
+ closepath
818
+ fill
819
+ ----
820
+
821
+ **SVG:**
822
+
823
+ [source,xml]
824
+ ----
825
+ <path d="M 100 100 L 200 100 L 200 200 L 100 200 Z"
826
+ fill="#808080"
827
+ stroke="none"/>
828
+ ----
829
+ ====
830
+
831
+ === PathBuilder Implementation
832
+
833
+ The [`PathBuilder`](../../lib/postsvg/path_builder.rb:7) class constructs SVG path data:
834
+
835
+ [source,ruby]
836
+ ----
837
+ # Create path builder
838
+ builder = PathBuilder.new
839
+
840
+ # Add segments
841
+ builder.move_to(100, 100)
842
+ builder.line_to(200, 100)
843
+ builder.line_to(200, 200)
844
+ builder.close
845
+
846
+ # Generate SVG path data
847
+ path_data = builder.to_path
848
+ # => "M 100 100 L 200 100 L 200 200 Z"
849
+ ----
850
+
851
+ === Coordinate Transformation
852
+
853
+ Paths are built in PostScript coordinates, then transformed for SVG:
854
+
855
+ [source,ruby]
856
+ ----
857
+ # PostScript coordinates (Y up)
858
+ builder.move_to(50, 50) # Near bottom
859
+ builder.line_to(50, 150) # Near top
860
+
861
+ # SVG applies transform
862
+ # <g transform="translate(0 200) scale(1 -1)">
863
+ # <path d="M 50 50 L 50 150"/>
864
+ # </g>
865
+ # Renders correctly with Y-axis flip
866
+ ----
867
+
868
+ == Advanced Path Techniques
869
+
870
+ === Smooth Curves
871
+
872
+ Create smooth curves by aligning control points:
873
+
874
+ .Smooth Curve Chain
875
+ [example]
876
+ ====
877
+ [source,postscript]
878
+ ----
879
+ % First curve
880
+ 100 100 moveto
881
+ 150 50 200 50 250 100 curveto
882
+
883
+ % Second curve - control point aligned
884
+ % Previous end: (250,100)
885
+ % Previous control: (200,50)
886
+ % Reflected control: (300,150) = 2×250 - 200, 2×100 - 50
887
+ 300 150 350 150 400 100 curveto
888
+ stroke
889
+ ----
890
+ ====
891
+
892
+ === Approximating Arcs
893
+
894
+ Use Bézier curves to approximate circular arcs:
895
+
896
+ .Quarter Circle Approximation
897
+ [example]
898
+ ====
899
+ [source,postscript]
900
+ ----
901
+ % Magic number for 90° arc: κ = 4(√2-1)/3 ≈ 0.5522847498
902
+ /r 50 def % Radius
903
+ /k 0.5522847498 def % Control point distance
904
+
905
+ % Quarter circle from (r,0) to (0,r)
906
+ r 0 moveto
907
+ r k r mul 0 k r mul r 0 r curveto
908
+ stroke
909
+ ----
910
+
911
+ The control points are at distance `κ × r` from the endpoints.
912
+ ====
913
+
914
+ === Path Winding
915
+
916
+ Path direction affects filling with holes:
917
+
918
+ .Winding Direction
919
+ [example]
920
+ ====
921
+ [source,postscript]
922
+ ----
923
+ % Outer square (clockwise)
924
+ newpath
925
+ 100 100 moveto
926
+ 300 100 lineto
927
+ 300 300 lineto
928
+ 100 300 lineto
929
+ closepath
930
+
931
+ % Inner square (counter-clockwise for hole)
932
+ 200 200 moveto
933
+ 200 150 lineto
934
+ 150 150 lineto
935
+ 150 200 lineto
936
+ closepath
937
+
938
+ fill
939
+ % Outer filled, inner hollow
940
+ ----
941
+ ====
942
+
943
+ == Common Path Patterns
944
+
945
+ === Rectangle
946
+
947
+ [source,postscript]
948
+ ----
949
+ x y width height rectfill
950
+
951
+ % Or manually:
952
+ x y moveto
953
+ x width add y lineto
954
+ x width add y height add lineto
955
+ x y height add lineto
956
+ closepath
957
+ fill
958
+ ----
959
+
960
+ === Circle
961
+
962
+ [source,postscript]
963
+ ----
964
+ x y radius 0 360 arc
965
+ fill
966
+
967
+ % Using 4 Bézier curves for perfect circle
968
+ ----
969
+
970
+ === Rounded Rectangle
971
+
972
+ [source,postscript]
973
+ ----
974
+ /roundrect { % x y width height radius
975
+ /r exch def
976
+ /h exch def
977
+ /w exch def
978
+ /y exch def
979
+ /x exch def
980
+
981
+ newpath
982
+ x r add y moveto
983
+ x w add r sub y x w add y r add r arcto
984
+ x w add y h add r sub x w add r sub y h add r arcto
985
+ x r add y h add x y h add r sub r arcto
986
+ x y r add x r add y r arcto
987
+ closepath
988
+ } def
989
+
990
+ 100 100 200 150 20 roundrect
991
+ fill
992
+ ----
993
+
994
+ == Troubleshooting Path Issues
995
+
996
+ === No Current Point Error
997
+
998
+ **Problem:** Operator requires current point but none exists
999
+
1000
+ **Cause:**
1001
+
1002
+ [source,postscript]
1003
+ ----
1004
+ newpath
1005
+ lineto 100 100 % ERROR: no current point
1006
+ ----
1007
+
1008
+ **Solution:**
1009
+
1010
+ [source,postscript]
1011
+ ----
1012
+ newpath
1013
+ moveto 50 50 % Set current point first
1014
+ lineto 100 100 % Now works
1015
+ ----
1016
+
1017
+ === Path Not Closing
1018
+
1019
+ **Problem:** Shape has gap at corner
1020
+
1021
+ **Cause:** Missing `closepath`
1022
+
1023
+ **Solution:**
1024
+
1025
+ [source,postscript]
1026
+ ----
1027
+ % Wrong
1028
+ 100 100 moveto
1029
+ 200 100 lineto
1030
+ 200 200 lineto
1031
+ 100 200 lineto
1032
+ stroke % Gap at (100,100)
1033
+
1034
+ % Correct
1035
+ 100 100 moveto
1036
+ 200 100 lineto
1037
+ 200 200 lineto
1038
+ 100 200 lineto
1039
+ closepath
1040
+ stroke % Fully closed
1041
+ ----
1042
+
1043
+ === Empty SVG Path
1044
+
1045
+ **Problem:** Path not appearing in SVG
1046
+
1047
+ **Cause:** Path not painted before end
1048
+
1049
+ **Solution:**
1050
+
1051
+ [source,postscript]
1052
+ ----
1053
+ newpath
1054
+ 100 100 moveto
1055
+ 200 200 lineto
1056
+ stroke % Must paint!
1057
+ % Without stroke/fill, path is lost
1058
+ ----
1059
+
1060
+ == Next Steps
1061
+
1062
+ * Review link:svg-generation.adoc[SVG Generation] for output details
1063
+ * Explore link:graphics-state.adoc[Graphics State] for path state
1064
+ * See link:coordinate-systems.adoc[Coordinate Systems] for transformations
1065
+ * Check link:../api-reference.adoc[API Reference] for path commands
1066
+ * Read link:conversion-pipeline.adoc[Conversion Pipeline] for the full process
1067
+
1068
+ == Bibliography
1069
+
1070
+ * link:svg-generation.adoc[SVG Generation Details]
1071
+ * link:graphics-state.adoc[Graphics State Management]
1072
+ * link:coordinate-systems.adoc[Coordinate Systems]
1073
+ * link:../architecture.adoc[Architecture Overview]
1074
+ * link:https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual] - Chapter 4: Graphics
1075
+ * link:https://www.w3.org/TR/SVG/paths.html[SVG Paths Specification]
1076
+ * link:https://pomax.github.io/bezierinfo/[A Primer on Bézier Curves]