postsvg 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +105 -0
- data/CLAUDE.md +173 -0
- data/Gemfile +2 -3
- data/README.adoc +456 -179
- data/Rakefile +100 -0
- data/TODO.roadmap/00-architecture.md +139 -0
- data/TODO.roadmap/01-autoload-migration.md +39 -0
- data/TODO.roadmap/02-isolate-dormant-code.md +51 -0
- data/TODO.roadmap/03-domain-model.md +66 -0
- data/TODO.roadmap/04-lexer.md +40 -0
- data/TODO.roadmap/05-parser.md +43 -0
- data/TODO.roadmap/06-graphics-state.md +45 -0
- data/TODO.roadmap/07-matrix-and-color.md +37 -0
- data/TODO.roadmap/08-svg-builder.md +51 -0
- data/TODO.roadmap/09-renderer.md +52 -0
- data/TODO.roadmap/10-visitor.md +58 -0
- data/TODO.roadmap/11-operator-coverage.md +103 -0
- data/TODO.roadmap/12-svg-domain.md +62 -0
- data/TODO.roadmap/13-translation-handlers.md +69 -0
- data/TODO.roadmap/14-ps-serializer.md +49 -0
- data/TODO.roadmap/15-cli-and-public-api.md +47 -0
- data/TODO.roadmap/16-specs.md +84 -0
- data/TODO.roadmap/17-docs-sync.md +47 -0
- data/TODO.roadmap/18-performance-and-determinism.md +47 -0
- data/TODO.roadmap/19-error-model.md +45 -0
- data/TODO.roadmap/20-font-and-text.md +46 -0
- data/TODO.roadmap/21-images.md +45 -0
- data/TODO.roadmap/22-forms-and-resources.md +25 -0
- data/TODO.roadmap/23-level2-level3.md +52 -0
- data/TODO.roadmap/24-ci-and-release.md +42 -0
- data/TODO.roadmap/README.md +77 -0
- data/docs/.gitignore +29 -0
- data/docs/CHANGELOG.md +114 -0
- data/docs/COMPLETE_DOCUMENTATION_STATUS.md +376 -0
- data/docs/DEPLOYMENT.md +456 -0
- data/docs/DEPLOYMENT_INSTRUCTIONS.md +229 -0
- data/docs/DOCUMENTATION_PLAN.md +425 -0
- data/docs/FINAL_SUMMARY.md +657 -0
- data/docs/Gemfile +15 -0
- data/docs/README.md +327 -0
- data/docs/_config.yml +99 -0
- data/docs/advanced-topics.adoc +370 -0
- data/docs/api-reference/colors.adoc +705 -0
- data/docs/api-reference/converter.adoc +699 -0
- data/docs/api-reference/execution-context.adoc +1210 -0
- data/docs/api-reference/graphics-state.adoc +1070 -0
- data/docs/api-reference/interpreter.adoc +810 -0
- data/docs/api-reference/matrix.adoc +1179 -0
- data/docs/api-reference/path-builder.adoc +1284 -0
- data/docs/api-reference/postsvg-module.adoc +388 -0
- data/docs/api-reference/svg-generator.adoc +891 -0
- data/docs/api-reference/tokenizer.adoc +925 -0
- data/docs/api-reference.adoc +221 -0
- data/docs/architecture/command-registry.adoc +1191 -0
- data/docs/architecture/conversion-pipeline.adoc +746 -0
- data/docs/architecture/design-decisions.adoc +999 -0
- data/docs/architecture/generator-stage.adoc +1115 -0
- data/docs/architecture/graphics-state-model.adoc +1089 -0
- data/docs/architecture/interpreter-stage.adoc +1125 -0
- data/docs/architecture/parser-stage.adoc +1051 -0
- data/docs/architecture.adoc +354 -0
- data/docs/cli-reference/batch-command.adoc +616 -0
- data/docs/cli-reference/check-command.adoc +677 -0
- data/docs/cli-reference/cli-options.adoc +802 -0
- data/docs/cli-reference/convert-command.adoc +462 -0
- data/docs/cli-reference/version-command.adoc +296 -0
- data/docs/cli-reference.adoc +317 -0
- data/docs/concepts/conversion-pipeline.adoc +903 -0
- data/docs/concepts/coordinate-systems.adoc +836 -0
- data/docs/concepts/graphics-state.adoc +861 -0
- data/docs/concepts/path-operations.adoc +1076 -0
- data/docs/concepts/postscript-language.adoc +859 -0
- data/docs/concepts/svg-generation.adoc +937 -0
- data/docs/concepts.adoc +198 -0
- data/docs/contributing.adoc +443 -0
- data/docs/development.adoc +420 -0
- data/docs/faq.adoc +493 -0
- data/docs/getting-started/basic-usage.adoc +538 -0
- data/docs/getting-started/common-workflows.adoc +577 -0
- data/docs/getting-started/first-conversion.adoc +492 -0
- data/docs/getting-started/installation.adoc +534 -0
- data/docs/getting-started.adoc +94 -0
- data/docs/index.adoc +248 -0
- data/docs/optimization.adoc +196 -0
- data/docs/ps2svg_compatibility.adoc +149 -0
- data/docs/quick-reference.adoc +453 -0
- data/docs/sitemap.adoc +337 -0
- data/docs/troubleshooting.adoc +486 -0
- data/docs/validation.adoc +772 -0
- data/exe/postsvg +1 -0
- data/lib/postsvg/cli.rb +104 -57
- data/lib/postsvg/color.rb +132 -0
- data/lib/postsvg/errors.rb +68 -3
- data/lib/postsvg/format_number.rb +22 -0
- data/lib/postsvg/graphics_context.rb +80 -0
- data/lib/postsvg/graphics_stack.rb +43 -0
- data/lib/postsvg/model/literals/array.rb +41 -0
- data/lib/postsvg/model/literals/dictionary.rb +34 -0
- data/lib/postsvg/model/literals/hex.rb +37 -0
- data/lib/postsvg/model/literals/name.rb +40 -0
- data/lib/postsvg/model/literals/number.rb +36 -0
- data/lib/postsvg/model/literals/procedure.rb +41 -0
- data/lib/postsvg/model/literals/string.rb +30 -0
- data/lib/postsvg/model/literals.rb +19 -0
- data/lib/postsvg/model/operator.rb +58 -0
- data/lib/postsvg/model/operators/arithmetic.rb +264 -0
- data/lib/postsvg/model/operators/boolean.rb +182 -0
- data/lib/postsvg/model/operators/color.rb +74 -0
- data/lib/postsvg/model/operators/container.rb +186 -0
- data/lib/postsvg/model/operators/control_flow.rb +119 -0
- data/lib/postsvg/model/operators/device.rb +21 -0
- data/lib/postsvg/model/operators/dictionary.rb +118 -0
- data/lib/postsvg/model/operators/font.rb +121 -0
- data/lib/postsvg/model/operators/graphics_state.rb +84 -0
- data/lib/postsvg/model/operators/painting.rb +29 -0
- data/lib/postsvg/model/operators/path.rb +169 -0
- data/lib/postsvg/model/operators/stack.rb +72 -0
- data/lib/postsvg/model/operators/transformations.rb +103 -0
- data/lib/postsvg/model/operators.rb +89 -0
- data/lib/postsvg/model/program.rb +68 -0
- data/lib/postsvg/model/token.rb +43 -0
- data/lib/postsvg/model.rb +17 -0
- data/lib/postsvg/options.rb +29 -0
- data/lib/postsvg/renderer.rb +85 -0
- data/lib/postsvg/serializer.rb +325 -0
- data/lib/postsvg/source/ast_builder.rb +308 -0
- data/lib/postsvg/source/lexer.rb +322 -0
- data/lib/postsvg/source/operand_stack.rb +55 -0
- data/lib/postsvg/source.rb +21 -0
- data/lib/postsvg/svg/attribute_parser.rb +45 -0
- data/lib/postsvg/svg/clip_path_registry.rb +44 -0
- data/lib/postsvg/svg/document.rb +22 -0
- data/lib/postsvg/svg/element.rb +84 -0
- data/lib/postsvg/svg/elements/circle.rb +36 -0
- data/lib/postsvg/svg/elements/clip_path.rb +26 -0
- data/lib/postsvg/svg/elements/defs.rb +24 -0
- data/lib/postsvg/svg/elements/ellipse.rb +38 -0
- data/lib/postsvg/svg/elements/group.rb +37 -0
- data/lib/postsvg/svg/elements/image.rb +35 -0
- data/lib/postsvg/svg/elements/line.rb +36 -0
- data/lib/postsvg/svg/elements/path.rb +32 -0
- data/lib/postsvg/svg/elements/polygon.rb +12 -0
- data/lib/postsvg/svg/elements/polyline.rb +32 -0
- data/lib/postsvg/svg/elements/rect.rb +44 -0
- data/lib/postsvg/svg/elements/svg.rb +39 -0
- data/lib/postsvg/svg/elements/text.rb +42 -0
- data/lib/postsvg/svg/elements.rb +31 -0
- data/lib/postsvg/svg/paint.rb +34 -0
- data/lib/postsvg/svg/parser.rb +39 -0
- data/lib/postsvg/svg/path_data/command.rb +27 -0
- data/lib/postsvg/svg/path_data/parser.rb +82 -0
- data/lib/postsvg/svg/path_data.rb +17 -0
- data/lib/postsvg/svg/stroke.rb +31 -0
- data/lib/postsvg/svg/transform_list.rb +59 -0
- data/lib/postsvg/svg.rb +22 -0
- data/lib/postsvg/svg_builder.rb +249 -0
- data/lib/postsvg/translation/arc_converter.rb +86 -0
- data/lib/postsvg/translation/bounding_box.rb +59 -0
- data/lib/postsvg/translation/context.rb +34 -0
- data/lib/postsvg/translation/handler_registry.rb +38 -0
- data/lib/postsvg/translation/handlers/circle_handler.rb +28 -0
- data/lib/postsvg/translation/handlers/clip_path_handler.rb +13 -0
- data/lib/postsvg/translation/handlers/defs_handler.rb +15 -0
- data/lib/postsvg/translation/handlers/ellipse_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/group_handler.rb +21 -0
- data/lib/postsvg/translation/handlers/image_handler.rb +20 -0
- data/lib/postsvg/translation/handlers/line_handler.rb +23 -0
- data/lib/postsvg/translation/handlers/open_handler.rb +18 -0
- data/lib/postsvg/translation/handlers/path_handler.rb +356 -0
- data/lib/postsvg/translation/handlers/polygon_handler.rb +33 -0
- data/lib/postsvg/translation/handlers/polyline_handler.rb +31 -0
- data/lib/postsvg/translation/handlers/rect_handler.rb +27 -0
- data/lib/postsvg/translation/handlers/shared.rb +110 -0
- data/lib/postsvg/translation/handlers/svg_handler.rb +25 -0
- data/lib/postsvg/translation/handlers/text_handler.rb +56 -0
- data/lib/postsvg/translation/handlers.rb +25 -0
- data/lib/postsvg/translation/ps_renderer.rb +105 -0
- data/lib/postsvg/translation/record_emitter.rb +35 -0
- data/lib/postsvg/translation.rb +17 -0
- data/lib/postsvg/version.rb +1 -1
- data/lib/postsvg/visitors/ps_visitor/arithmetic.rb +125 -0
- data/lib/postsvg/visitors/ps_visitor/boolean.rb +105 -0
- data/lib/postsvg/visitors/ps_visitor/color.rb +53 -0
- data/lib/postsvg/visitors/ps_visitor/common.rb +66 -0
- data/lib/postsvg/visitors/ps_visitor/container.rb +164 -0
- data/lib/postsvg/visitors/ps_visitor/control_flow.rb +110 -0
- data/lib/postsvg/visitors/ps_visitor/device.rb +20 -0
- data/lib/postsvg/visitors/ps_visitor/dictionary.rb +89 -0
- data/lib/postsvg/visitors/ps_visitor/font.rb +93 -0
- data/lib/postsvg/visitors/ps_visitor/graphics_state.rb +55 -0
- data/lib/postsvg/visitors/ps_visitor/painting.rb +90 -0
- data/lib/postsvg/visitors/ps_visitor/path.rb +112 -0
- data/lib/postsvg/visitors/ps_visitor/stack.rb +47 -0
- data/lib/postsvg/visitors/ps_visitor/transformations.rb +101 -0
- data/lib/postsvg/visitors/ps_visitor.rb +208 -0
- data/lib/postsvg/visitors.rb +9 -0
- data/lib/postsvg.rb +93 -59
- data/lychee.toml +86 -0
- metadata +216 -11
- data/postsvg.gemspec +0 -38
|
@@ -0,0 +1,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
|