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,861 @@
1
+ = Graphics State Management
2
+ :page-nav_order: 2
3
+ :page-parent: Core Concepts
4
+
5
+ == Purpose
6
+
7
+ This document provides a comprehensive explanation of graphics state management in Postsvg. The graphics state is central to PostScript's imaging model, tracking all parameters that affect how graphics are rendered. Understanding graphics state management is essential for working with complex PostScript documents and troubleshooting rendering issues.
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:coordinate-systems.adoc[Coordinate Systems]
15
+ * link:../architecture/graphics-state-model.adoc[Graphics State Implementation]
16
+
17
+ == Concepts
18
+
19
+ **Graphics State**:: A data structure containing all parameters that control how graphics are rendered, including colors, line attributes, paths, and transformations.
20
+
21
+ **State Stack**:: A stack of saved graphics states enabling nested modifications with automatic restoration via `gsave`/`grestore`.
22
+
23
+ **Current Transformation Matrix (CTM)**:: A 2D affine transformation matrix that maps user space coordinates to device space.
24
+
25
+ **Current Path**:: The path being constructed by path operators, maintained as part of graphics state.
26
+
27
+ **Immutable State Pattern**:: Graphics states are cloned when saved, preventing unintended modifications to saved states.
28
+
29
+ == Graphics State Components
30
+
31
+ The graphics state consists of multiple interconnected components that control every aspect of rendering.
32
+
33
+ .Graphics State Structure
34
+ [source]
35
+ ----
36
+ Graphics State
37
+
38
+ ├── Path State
39
+ │ ├── Current Path: Array of path segments
40
+ │ └── Current Point: (x, y) coordinates
41
+
42
+ ├── Color State
43
+ │ ├── Fill Color: RGB/Grayscale/CMYK
44
+ │ ├── Stroke Color: RGB/Grayscale/CMYK
45
+ │ └── Color Space: DeviceRGB/DeviceGray/DeviceCMYK
46
+
47
+ ├── Line Attributes
48
+ │ ├── Line Width: Stroke thickness
49
+ │ ├── Line Cap: butt/round/square
50
+ │ ├── Line Join: miter/round/bevel
51
+ │ ├── Miter Limit: Maximum miter length
52
+ │ └── Dash Pattern: [array offset]
53
+
54
+ ├── Transformation Matrix
55
+ │ └── CTM: [a b c d e f]
56
+
57
+ ├── Clipping Path
58
+ │ └── Clip Stack: Array of clipping regions
59
+
60
+ ├── Text State
61
+ │ ├── Font: Current font dictionary
62
+ │ ├── Font Size: Text size in user space
63
+ │ └── Text Position: Last text rendering position
64
+
65
+ └── Advanced Features (Level 3)
66
+ ├── Blend Mode: Compositing mode
67
+ ├── Soft Mask: Transparency mask
68
+ ├── Opacity Alpha: Overall opacity
69
+ └── Shape Alpha: Shape opacity
70
+ ----
71
+
72
+ == Path State
73
+
74
+ === Current Path
75
+
76
+ The current path is a sequence of path segments being constructed. It remains part of the graphics state until explicitly painted or cleared.
77
+
78
+ **Path Segments:**
79
+
80
+ * **moveto** - Starts a new subpath at coordinates (x, y)
81
+ * **lineto** - Adds a straight line to (x, y)
82
+ * **curveto** - Adds a cubic Bézier curve
83
+ * **closepath** - Closes the current subpath
84
+
85
+ .Path State Example
86
+ [example]
87
+ ====
88
+ [source,postscript]
89
+ ----
90
+ newpath % Clear current path
91
+ 100 100 moveto % Start subpath at (100, 100)
92
+ 200 100 lineto % Add line to (200, 100)
93
+ 200 200 lineto % Add line to (200, 200)
94
+ closepath % Close back to (100, 100)
95
+ % Path is now complete but not yet painted
96
+ ----
97
+
98
+ Current path state after these operations:
99
+
100
+ [source,ruby]
101
+ ----
102
+ {
103
+ current_path: [
104
+ { type: :moveto, x: 100, y: 100 },
105
+ { type: :lineto, x: 200, y: 100 },
106
+ { type: :lineto, x: 200, y: 200 },
107
+ { type: :closepath }
108
+ ],
109
+ current_x: 100, # Back to start after closepath
110
+ current_y: 100
111
+ }
112
+ ----
113
+ ====
114
+
115
+ === Current Point
116
+
117
+ The current point is the last position touched by a path construction operator. It's used as the starting point for the next path segment.
118
+
119
+ **Rules:**
120
+
121
+ * Set by `moveto`, `lineto`, `curveto`, and relative variants
122
+ * Reset by `closepath` to the subpath's starting point
123
+ * Undefined after `newpath` (must use `moveto` before other operators)
124
+
125
+ .Current Point Tracking
126
+ [example]
127
+ ====
128
+ [source,postscript]
129
+ ----
130
+ newpath
131
+ % Current point: undefined
132
+
133
+ 50 50 moveto
134
+ % Current point: (50, 50)
135
+
136
+ 100 0 rlineto % Relative line
137
+ % Current point: (150, 50) = (50 + 100, 50 + 0)
138
+
139
+ 0 50 rlineto
140
+ % Current point: (150, 100) = (150 + 0, 50 + 50)
141
+ ----
142
+ ====
143
+
144
+ == Color State
145
+
146
+ === Color Spaces
147
+
148
+ Postsvg supports multiple color spaces:
149
+
150
+ **DeviceRGB**:: Three components (red, green, blue) each in range [0, 1]
151
+
152
+ **DeviceGray**:: Single component (gray) in range [0, 1]
153
+
154
+ **DeviceCMYK**:: Four components (cyan, magenta, yellow, black) each in range [0, 1]
155
+
156
+ === Fill vs. Stroke Colors
157
+
158
+ Graphics state maintains separate colors for filling and stroking:
159
+
160
+ * **Fill Color** - Used by `fill`, `eofill`
161
+ * **Stroke Color** - Used by `stroke`
162
+
163
+ .Setting Colors
164
+ [example]
165
+ ====
166
+ [source,postscript]
167
+ ----
168
+ % Grayscale (applies to both fill and stroke)
169
+ 0.5 setgray
170
+ % fill_color: { r: 0.5, g: 0.5, b: 0.5 }
171
+ % stroke_color: { r: 0.5, g: 0.5, b: 0.5 }
172
+
173
+ % RGB (applies to both fill and stroke)
174
+ 1 0 0 setrgbcolor % Red
175
+ % fill_color: { r: 1, g: 0, b: 0 }
176
+ % stroke_color: { r: 1, g: 0, b: 0 }
177
+
178
+ % CMYK (applies to both fill and stroke)
179
+ 0 1 1 0 setcmykcolor % Red in CMYK
180
+ ----
181
+ ====
182
+
183
+ === Color Representation
184
+
185
+ In the [`GraphicsState`](../../lib/postsvg/graphics_state.rb:5) class:
186
+
187
+ [source,ruby]
188
+ ----
189
+ # RGB color
190
+ { r: 1.0, g: 0.0, b: 0.0 } # Red
191
+
192
+ # Grayscale (stored as RGB)
193
+ { r: 0.5, g: 0.5, b: 0.5 } # 50% gray
194
+
195
+ # Conversion to SVG hex
196
+ rgb_to_hex(1.0, 0.0, 0.0) # "#ff0000"
197
+ ----
198
+
199
+ == Line Attributes
200
+
201
+ Line attributes control how paths are stroked.
202
+
203
+ === Line Width
204
+
205
+ The thickness of stroked lines, specified in user space units.
206
+
207
+ [source,postscript]
208
+ ----
209
+ 1 setlinewidth % Default (1 unit wide)
210
+ 2.5 setlinewidth % 2.5 units wide
211
+ 0 setlinewidth % Thinnest possible (1 device pixel)
212
+ ----
213
+
214
+ **Implementation:**
215
+
216
+ [source,ruby]
217
+ ----
218
+ # In GraphicsState
219
+ @line_width = 1.0 # Default
220
+
221
+ # Set by setlinewidth operator
222
+ context.graphics_state[:stroke_width] = width
223
+ ----
224
+
225
+ === Line Cap
226
+
227
+ Determines how the ends of stroked lines are rendered.
228
+
229
+ **Values:**
230
+
231
+ * `0` - Butt cap (square end at endpoint)
232
+ * `1` - Round cap (semicircular end)
233
+ * `2` - Projecting square cap (extends beyond endpoint)
234
+
235
+ .Line Cap Styles
236
+ [source]
237
+ ----
238
+ Butt Cap (0): Round Cap (1): Square Cap (2):
239
+ ───────── ─────────╮ ──────────┐
240
+ ╰ │
241
+
242
+ ----
243
+
244
+ [source,postscript]
245
+ ----
246
+ 0 setlinecap % Butt
247
+ 1 setlinecap % Round
248
+ 2 setlinecap % Square
249
+ ----
250
+
251
+ === Line Join
252
+
253
+ Determines how corners are rendered when two line segments meet.
254
+
255
+ **Values:**
256
+
257
+ * `0` - Miter join (pointed corner, default)
258
+ * `1` - Round join (circular arc)
259
+ * `2` - Bevel join (flat corner)
260
+
261
+ .Line Join Styles
262
+ [source]
263
+ ----
264
+ Miter Join (0): Round Join (1): Bevel Join (2):
265
+ ╱╲ ╱╲ ╱╲
266
+ ╱ ╲ ╱ ╲ ╱ ╲
267
+ ╱ ╲ ╱ ╲ ╱ ╲
268
+ ----
269
+
270
+ [source,postscript]
271
+ ----
272
+ 0 setlinejoin % Miter
273
+ 1 setlinejoin % Round
274
+ 2 setlinejoin % Bevel
275
+ ----
276
+
277
+ === Dash Pattern
278
+
279
+ Controls dashed/dotted line rendering.
280
+
281
+ **Syntax:**
282
+
283
+ [source,postscript]
284
+ ----
285
+ [array] offset setdash
286
+ ----
287
+
288
+ **Where:**
289
+
290
+ `array`:: Array of on/off lengths (in user space units)
291
+ `offset`:: Distance into pattern to start
292
+
293
+ .Dash Patterns
294
+ [example]
295
+ ====
296
+ [source,postscript]
297
+ ----
298
+ % Solid line (default)
299
+ [] 0 setdash
300
+
301
+ % Evenly dashed (3 units on, 3 units off)
302
+ [3 3] 0 setdash
303
+
304
+ % Dash-dot pattern (5 on, 2 off, 1 on, 2 off)
305
+ [5 2 1 2] 0 setdash
306
+
307
+ % Offset pattern by 2 units
308
+ [3 3] 2 setdash
309
+ ----
310
+
311
+ Visual representation:
312
+
313
+ [source]
314
+ ----
315
+ [] 0: ────────────────────────────
316
+
317
+ [3 3] 0: ─── ─── ─── ─── ───
318
+
319
+ [5 2 1 2] 0: ───── ─ ───── ─ ─────
320
+
321
+ [3 3] 2: ─ ─── ─── ─── ───
322
+ ----
323
+ ====
324
+
325
+ === Miter Limit
326
+
327
+ Controls when miter joins are beveled instead of extended to a point.
328
+
329
+ [source,postscript]
330
+ ----
331
+ 10 setmiterlimit % Default
332
+ 4 setmiterlimit % More aggressive beveling
333
+ ----
334
+
335
+ When the miter length would exceed `miter_limit × line_width`, the join is beveled instead.
336
+
337
+ == Current Transformation Matrix (CTM)
338
+
339
+ The CTM transforms coordinates from user space to device space. It's a 2D affine transformation matrix represented as `[a b c d e f]`.
340
+
341
+ === Matrix Representation
342
+
343
+ [source]
344
+ ----
345
+ [ a b 0 ] [ x ] [ ax + cy + e ]
346
+ [ c d 0 ] × [ y ] = [ bx + dy + f ]
347
+ [ e f 1 ] [ 1 ] [ 1 ]
348
+ ----
349
+
350
+ **Components:**
351
+
352
+ * `a, d` - Scaling factors
353
+ * `b, c` - Rotation/skew factors
354
+ * `e, f` - Translation offsets
355
+
356
+ === Identity Matrix
357
+
358
+ The default CTM is the identity matrix:
359
+
360
+ [source,ruby]
361
+ ----
362
+ [1, 0, 0, 1, 0, 0] # No transformation
363
+ ----
364
+
365
+ This means:
366
+ * No scaling (1:1)
367
+ * No rotation
368
+ * No translation
369
+
370
+ === Common Transformations
371
+
372
+ **Translation:**
373
+
374
+ [source,postscript]
375
+ ----
376
+ 10 20 translate
377
+ % CTM = [1 0 0 1 10 20]
378
+ % Shifts origin to (10, 20)
379
+ ----
380
+
381
+ **Scaling:**
382
+
383
+ [source,postscript]
384
+ ----
385
+ 2 3 scale
386
+ % CTM = [2 0 0 3 0 0]
387
+ % Doubles X, triples Y
388
+ ----
389
+
390
+ **Rotation:**
391
+
392
+ [source,postscript]
393
+ ----
394
+ 45 rotate
395
+ % CTM = [cos(45°) sin(45°) -sin(45°) cos(45°) 0 0]
396
+ % CTM ≈ [0.707 0.707 -0.707 0.707 0 0]
397
+ % Rotates 45° counter-clockwise
398
+ ----
399
+
400
+ === Transformation Composition
401
+
402
+ Transformations are applied by matrix multiplication. The order matters!
403
+
404
+ .Transformation Order
405
+ [example]
406
+ ====
407
+ [source,postscript]
408
+ ----
409
+ % Translate THEN rotate
410
+ 10 20 translate
411
+ 45 rotate
412
+ % Points are first translated, then rotated around new origin
413
+
414
+ % Rotate THEN translate
415
+ 45 rotate
416
+ 10 20 translate
417
+ % Points are first rotated around origin, then translated
418
+ ----
419
+
420
+ These produce different results:
421
+
422
+ [source]
423
+ ----
424
+ Translate → Rotate: Rotate → Translate:
425
+
426
+ Original (0,0) Original (0,0)
427
+ │ │
428
+ ▼ translate(10,20) ▼ rotate(45°)
429
+ New origin (10,20) Rotated
430
+ │ │
431
+ ▼ rotate(45°) ▼ translate(10,20)
432
+ Rotated around (10,20) Moved to (10,20)
433
+ ----
434
+ ====
435
+
436
+ === CTM Implementation
437
+
438
+ In Postsvg, the CTM is managed by the [`Matrix`](../../lib/postsvg/matrix.rb:6) class:
439
+
440
+ [source,ruby]
441
+ ----
442
+ # Create identity matrix
443
+ ctm = Matrix.new
444
+ # => { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }
445
+
446
+ # Apply translation
447
+ ctm.translate(10, 20)
448
+
449
+ # Apply scaling
450
+ ctm.scale(2, 2)
451
+
452
+ # Apply rotation
453
+ ctm.rotate(45) # degrees
454
+
455
+ # Transform a point
456
+ point = ctm.apply_point(100, 100)
457
+ # => { x: transformed_x, y: transformed_y }
458
+ ----
459
+
460
+ == Graphics State Stack
461
+
462
+ The graphics state stack enables nested state modifications with automatic restoration.
463
+
464
+ === Save and Restore Operations
465
+
466
+ **gsave**:: Pushes a copy of the current graphics state onto the stack
467
+
468
+ **grestore**:: Pops the stack and restores the previous state
469
+
470
+ .Graphics State Stack Visualization
471
+ [source]
472
+ ----
473
+ Initial State: linewidth=1, color=black, CTM=identity
474
+
475
+ ├─ gsave ──────────────────────────────────┐
476
+ │ Modified State: linewidth=2, color=red │
477
+ │ │ │
478
+ │ ├─ gsave ─────────────────────────┐ │
479
+ │ │ Modified: linewidth=5, color=blue │
480
+ │ │ (draw with blue, width 5) │ │
481
+ │ │ │ │
482
+ │ └─ grestore ◄──────────────────────┘ │
483
+ │ Back to: linewidth=2, color=red │
484
+ │ (draw with red, width 2) │
485
+ │ │
486
+ └─ grestore ◄──────────────────────────────┘
487
+ Back to: linewidth=1, color=black
488
+ (draw with original settings)
489
+ ----
490
+
491
+ === Stack Implementation
492
+
493
+ The stack is implemented in [`ExecutionContext`](../../lib/postsvg/execution_context.rb:9):
494
+
495
+ [source,ruby]
496
+ ----
497
+ # Save graphics state
498
+ def save_graphics_state
499
+ @g_stack << {
500
+ graphics_state: clone_graphics_state(@graphics_state),
501
+ current_x: @current_x,
502
+ current_y: @current_y,
503
+ saved_ctm: clone_matrix(@graphics_state[:ctm]),
504
+ path_builder: @path_builder.dup
505
+ }
506
+ end
507
+
508
+ # Restore graphics state
509
+ def restore_graphics_state
510
+ return if @g_stack.empty?
511
+
512
+ saved = @g_stack.pop
513
+ @graphics_state = saved[:graphics_state]
514
+ @current_x = saved[:current_x]
515
+ @current_y = saved[:current_y]
516
+ @path_builder = saved[:path_builder]
517
+ end
518
+ ----
519
+
520
+ === Saved State Components
521
+
522
+ When `gsave` is executed, these components are saved:
523
+
524
+ * Current transformation matrix (CTM)
525
+ * Current path and current point
526
+ * Clipping path
527
+ * Fill and stroke colors
528
+ * Line attributes (width, cap, join, dash, miter limit)
529
+ * Font and text settings
530
+ * Advanced features (blend mode, opacity, etc.)
531
+
532
+ === Common Usage Patterns
533
+
534
+ **Temporary Modifications:**
535
+
536
+ [source,postscript]
537
+ ----
538
+ gsave
539
+ 2 setlinewidth
540
+ 1 0 0 setrgbcolor
541
+ % Draw with thick red lines
542
+ grestore
543
+ % Original settings restored
544
+ ----
545
+
546
+ **Isolated Transformations:**
547
+
548
+ [source,postscript]
549
+ ----
550
+ gsave
551
+ 100 100 translate
552
+ 45 rotate
553
+ % Draw rotated around (100, 100)
554
+ grestore
555
+ % No rotation for subsequent drawing
556
+ ----
557
+
558
+ **Nested Modifications:**
559
+
560
+ [source,postscript]
561
+ ----
562
+ gsave % Save state A
563
+ 1 0 0 setrgbcolor % Red
564
+ gsave % Save state B (red)
565
+ 0 1 0 setrgbcolor % Green
566
+ % Draw in green
567
+ grestore % Restore to red
568
+ % Draw in red
569
+ grestore % Restore to original
570
+ ----
571
+
572
+ == State Modification Examples
573
+
574
+ === Complete State Lifecycle
575
+
576
+ .Full State Management Example
577
+ [example]
578
+ ====
579
+ [source,postscript]
580
+ ----
581
+ % Initial state
582
+ newpath
583
+ 100 100 moveto
584
+ 200 100 lineto
585
+ % State: path=[(M 100,100)(L 200,100)], linewidth=1, color=black
586
+
587
+ gsave
588
+ % Save initial state
589
+ 2 setlinewidth
590
+ 0.5 setgray
591
+ 200 200 lineto
592
+ stroke
593
+ % Painted with linewidth=2, gray=0.5
594
+ grestore
595
+
596
+ % State restored: linewidth=1, color=black, path cleared by stroke
597
+
598
+ % New path
599
+ newpath
600
+ 50 50 moveto
601
+ 150 150 lineto
602
+ stroke
603
+ % Painted with original linewidth=1, color=black
604
+ ----
605
+
606
+ State transitions:
607
+
608
+ [source]
609
+ ----
610
+ Step 1: Initial
611
+ path: [(M 100,100)(L 200,100)]
612
+ linewidth: 1
613
+ color: black
614
+
615
+ Step 2: gsave
616
+ Stack: [initial_state]
617
+ Current: same as initial
618
+
619
+ Step 3: Modify
620
+ linewidth: 2
621
+ gray: 0.5
622
+ path: [(M 100,100)(L 200,100)(L 200,200)]
623
+
624
+ Step 4: stroke
625
+ → Paints path with current settings
626
+ → Clears current path
627
+ path: []
628
+
629
+ Step 5: grestore
630
+ → Pops stack
631
+ linewidth: 1
632
+ color: black
633
+ path: [] (not restored after stroke)
634
+
635
+ Step 6: New path
636
+ path: [(M 50,50)(L 150,150)]
637
+
638
+ Step 7: stroke
639
+ → Paints with original settings
640
+ ----
641
+ ====
642
+
643
+ === Transformation State
644
+
645
+ .Transformation with State Stack
646
+ [example]
647
+ ====
648
+ [source,postscript]
649
+ ----
650
+ % Draw square at origin
651
+ newpath
652
+ 0 0 moveto
653
+ 100 0 lineto
654
+ 100 100 lineto
655
+ 0 100 lineto
656
+ closepath
657
+ stroke
658
+
659
+ % Draw rotated square
660
+ gsave
661
+ 45 rotate
662
+ newpath
663
+ 0 0 moveto
664
+ 100 0 lineto
665
+ 100 100 lineto
666
+ 0 100 lineto
667
+ closepath
668
+ stroke
669
+ grestore
670
+
671
+ % Draw translated square (no rotation)
672
+ 150 0 translate
673
+ newpath
674
+ 0 0 moveto
675
+ 100 0 lineto
676
+ 100 100 lineto
677
+ 0 100 lineto
678
+ closepath
679
+ stroke
680
+ ----
681
+
682
+ CTM progression:
683
+
684
+ [source]
685
+ ----
686
+ Initial: [1 0 0 1 0 0] (identity)
687
+ After gsave: [1 0 0 1 0 0] (same)
688
+ After rotate: [0.707 0.707 -0.707 0.707 0 0]
689
+ After grestore: [1 0 0 1 0 0] (restored)
690
+ After translate: [1 0 0 1 150 0] (new transform)
691
+ ----
692
+ ====
693
+
694
+ == Advanced State Features
695
+
696
+ === Clipping Path Stack
697
+
698
+ The clipping path restricts where graphics can be painted. It's maintained as a stack to support nested clipping.
699
+
700
+ [source,postscript]
701
+ ----
702
+ gsave
703
+ % Define clipping path
704
+ newpath
705
+ 50 50 100 100 rectpath
706
+ clip
707
+ newpath
708
+ % Only paint inside rectangle
709
+ 0 0 moveto
710
+ 200 200 lineto
711
+ stroke % Only visible inside clip region
712
+ grestore
713
+ % Clipping path removed
714
+ ----
715
+
716
+ === Blend Modes and Transparency
717
+
718
+ PostScript Level 3 adds transparency features:
719
+
720
+ **Blend Mode:**
721
+
722
+ [source,postscript]
723
+ ----
724
+ /Normal setblendmode % Default
725
+ /Multiply setblendmode % Multiply colors
726
+ /Screen setblendmode % Screen blend
727
+ ----
728
+
729
+ **Opacity:**
730
+
731
+ [source,postscript]
732
+ ----
733
+ 0.5 setopacityalpha % 50% opaque
734
+ 0.8 setshapealpha % 80% shape opacity
735
+ ----
736
+
737
+ == Performance Considerations
738
+
739
+ === State Cloning
740
+
741
+ When `gsave` is called, the entire graphics state is cloned:
742
+
743
+ [source,ruby]
744
+ ----
745
+ def clone_graphics_state(state)
746
+ {
747
+ ctm: clone_matrix(state[:ctm]),
748
+ fill: state[:fill],
749
+ stroke: state[:stroke],
750
+ stroke_width: state[:stroke_width],
751
+ # ... all other components
752
+ }
753
+ end
754
+ ----
755
+
756
+ **Performance Impact:**
757
+
758
+ * Time: O(n) where n = number of state components
759
+ * Space: O(n × d) where d = stack depth
760
+ * Tip: Minimize deep nesting of `gsave`/`grestore`
761
+
762
+ === State Stack Depth
763
+
764
+ Graphics state stack depth is typically limited (usually 31 levels in PostScript interpreters).
765
+
766
+ .Monitoring Stack Depth
767
+ [example]
768
+ ====
769
+ [source,ruby]
770
+ ----
771
+ # Check stack depth
772
+ puts "Graphics state stack depth: #{context.instance_variable_get(:@g_stack).length}"
773
+
774
+ # Avoid excessive nesting
775
+ # Bad:
776
+ 100.times { gsave } # Don't do this!
777
+
778
+ # Good:
779
+ # Use gsave/grestore only when needed for isolation
780
+ ----
781
+ ====
782
+
783
+ == Troubleshooting Graphics State Issues
784
+
785
+ === Common Problems
786
+
787
+ **Unmatched gsave/grestore:**
788
+
789
+ [source,postscript]
790
+ ----
791
+ gsave
792
+ % Some operations
793
+ % Missing grestore!
794
+ % Stack accumulates saved states
795
+ ----
796
+
797
+ **Solution:** Always match `gsave` with `grestore`
798
+
799
+ **State Not Restoring:**
800
+
801
+ [source,postscript]
802
+ ----
803
+ gsave
804
+ 2 setlinewidth
805
+ stroke % Clears path!
806
+ grestore
807
+ % Path is NOT restored (stroke cleared it)
808
+ ----
809
+
810
+ **Solution:** Understand that `stroke` and `fill` clear the current path regardless of state stack
811
+
812
+ **Transformation Confusion:**
813
+
814
+ [source,postscript]
815
+ ----
816
+ 10 20 translate
817
+ 45 rotate
818
+ % Rotation happens around (10, 20), not (0, 0)
819
+ ----
820
+
821
+ **Solution:** Remember transformations compose; order matters
822
+
823
+ === Debugging State
824
+
825
+ .Inspecting Graphics State
826
+ [example]
827
+ ====
828
+ [source,ruby]
829
+ ----
830
+ # In execution context
831
+ state = context.graphics_state
832
+
833
+ puts "Current state:"
834
+ puts " CTM: #{state[:ctm].inspect}"
835
+ puts " Fill: #{state[:fill]}"
836
+ puts " Stroke: #{state[:stroke]}"
837
+ puts " Stroke width: #{state[:stroke_width]}"
838
+ puts " Line cap: #{state[:line_cap]}"
839
+ puts " Line join: #{state[:line_join]}"
840
+ puts " Clip stack depth: #{state[:clip_stack].length}"
841
+
842
+ # Check state stack
843
+ puts "Saved states: #{context.instance_variable_get(:@g_stack).length}"
844
+ ----
845
+ ====
846
+
847
+ == Next Steps
848
+
849
+ * Learn about link:coordinate-systems.adoc[Coordinate Systems] for CTM details
850
+ * Explore link:path-operations.adoc[Path Operations] for path state
851
+ * Review link:conversion-pipeline.adoc[Conversion Pipeline] for state flow
852
+ * See link:../architecture/graphics-state-model.adoc[Graphics State Implementation]
853
+ * Check link:../api-reference/graphics-state.adoc[GraphicsState API Reference]
854
+
855
+ == Bibliography
856
+
857
+ * link:../architecture/graphics-state-model.adoc[Graphics State Model Architecture]
858
+ * link:coordinate-systems.adoc[Coordinate Systems and Transformations]
859
+ * link:path-operations.adoc[Path Operations]
860
+ * link:../api-reference/execution-context.adoc[ExecutionContext API Reference]
861
+ * link:https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf[PostScript Language Reference Manual] - Chapter 4: Graphics