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,1284 @@
|
|
|
1
|
+
= PathBuilder Class
|
|
2
|
+
:page-nav_order: 8
|
|
3
|
+
:page-parent: API Reference
|
|
4
|
+
|
|
5
|
+
== Purpose
|
|
6
|
+
|
|
7
|
+
The [`PathBuilder`](../../lib/postsvg/path_builder.rb:7) class constructs SVG path data strings from PostScript path operations. It provides a fluent interface for building complex SVG paths through incremental commands while maintaining proper formatting and coordinate tracking.
|
|
8
|
+
|
|
9
|
+
== References
|
|
10
|
+
|
|
11
|
+
* link:../index.adoc[Documentation Home]
|
|
12
|
+
* link:../api-reference.adoc[API Reference Overview]
|
|
13
|
+
* link:svg-generator.adoc[SvgGenerator Class]
|
|
14
|
+
* link:matrix.adoc[Matrix Class]
|
|
15
|
+
* link:interpreter.adoc[Interpreter Class]
|
|
16
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
17
|
+
|
|
18
|
+
== Concepts
|
|
19
|
+
|
|
20
|
+
**SVG Path Data**:: A string representation of shapes using commands like `M` (moveto), `L` (lineto), `C` (curveto), and `Z` (closepath).
|
|
21
|
+
|
|
22
|
+
**Current Point**:: The position of the drawing cursor after the last path command, used as the starting point for the next command.
|
|
23
|
+
|
|
24
|
+
**Absolute vs Relative Commands**:: Absolute commands use fixed coordinates, while relative commands use offsets from the current point.
|
|
25
|
+
|
|
26
|
+
**Path Segment**:: An individual drawing command (move, line, curve, or close) that forms part of a complete path.
|
|
27
|
+
|
|
28
|
+
**Subpath**:: A continuous sequence of path segments that may be disconnected from other subpaths in the same path element.
|
|
29
|
+
|
|
30
|
+
== Class Overview
|
|
31
|
+
|
|
32
|
+
The [`PathBuilder`](../../lib/postsvg/path_builder.rb:7) class is defined in [`lib/postsvg/path_builder.rb`](../../lib/postsvg/path_builder.rb:1).
|
|
33
|
+
|
|
34
|
+
**Responsibilities:**
|
|
35
|
+
|
|
36
|
+
* Build SVG path data strings incrementally
|
|
37
|
+
* Track current drawing position
|
|
38
|
+
* Provide both absolute and relative path commands
|
|
39
|
+
* Format coordinates for clean SVG output
|
|
40
|
+
* Support path introspection and manipulation
|
|
41
|
+
* Enable path cloning and resetting
|
|
42
|
+
|
|
43
|
+
**Dependencies:**
|
|
44
|
+
|
|
45
|
+
* [`Matrix`](../../lib/postsvg/matrix.rb:5) - For coordinate transformations (imported but deprecated in favor of direct coordinate handling)
|
|
46
|
+
|
|
47
|
+
**Design Characteristics:**
|
|
48
|
+
|
|
49
|
+
* Fluent interface pattern for method chaining
|
|
50
|
+
* Stateful coordinate tracking
|
|
51
|
+
* Immutable path string parts for reliability
|
|
52
|
+
* Clean numeric formatting
|
|
53
|
+
|
|
54
|
+
== Class Methods
|
|
55
|
+
|
|
56
|
+
=== new
|
|
57
|
+
|
|
58
|
+
Create a new PathBuilder instance.
|
|
59
|
+
|
|
60
|
+
**Syntax:**
|
|
61
|
+
|
|
62
|
+
[source,ruby]
|
|
63
|
+
----
|
|
64
|
+
builder = Postsvg::PathBuilder.new <1>
|
|
65
|
+
----
|
|
66
|
+
<1> Initialize an empty path builder
|
|
67
|
+
|
|
68
|
+
**Returns:**
|
|
69
|
+
|
|
70
|
+
New `PathBuilder` instance with:
|
|
71
|
+
* Empty path parts array
|
|
72
|
+
* Initial position at (0, 0)
|
|
73
|
+
* No last command
|
|
74
|
+
|
|
75
|
+
**Source:**
|
|
76
|
+
|
|
77
|
+
[`lib/postsvg/path_builder.rb:10-15`](../../lib/postsvg/path_builder.rb:10)
|
|
78
|
+
|
|
79
|
+
.Create path builder
|
|
80
|
+
[example]
|
|
81
|
+
====
|
|
82
|
+
[source,ruby]
|
|
83
|
+
----
|
|
84
|
+
require 'postsvg'
|
|
85
|
+
|
|
86
|
+
# Create builder
|
|
87
|
+
builder = Postsvg::PathBuilder.new
|
|
88
|
+
|
|
89
|
+
# Builder is ready to accept path commands
|
|
90
|
+
# Current position: (0, 0)
|
|
91
|
+
# Path data: "" (empty)
|
|
92
|
+
----
|
|
93
|
+
====
|
|
94
|
+
|
|
95
|
+
== Instance Methods
|
|
96
|
+
|
|
97
|
+
=== move_to
|
|
98
|
+
|
|
99
|
+
Move to an absolute position without drawing.
|
|
100
|
+
|
|
101
|
+
**Syntax:**
|
|
102
|
+
|
|
103
|
+
[source,ruby]
|
|
104
|
+
----
|
|
105
|
+
builder.move_to(x, y) <1>
|
|
106
|
+
----
|
|
107
|
+
<1> Move drawing cursor to absolute coordinates
|
|
108
|
+
|
|
109
|
+
**Where:**
|
|
110
|
+
|
|
111
|
+
`x`:: X-coordinate in user units (Integer or Float)
|
|
112
|
+
|
|
113
|
+
`y`:: Y-coordinate in user units (Integer or Float)
|
|
114
|
+
|
|
115
|
+
**Returns:**
|
|
116
|
+
|
|
117
|
+
`nil` (modifies internal state)
|
|
118
|
+
|
|
119
|
+
**Side Effects:**
|
|
120
|
+
|
|
121
|
+
* Updates `@current_x` and `@current_y`
|
|
122
|
+
* Sets `@last_command` to `:move`
|
|
123
|
+
* Appends `"M x y"` to path parts
|
|
124
|
+
|
|
125
|
+
**Source:**
|
|
126
|
+
|
|
127
|
+
[`lib/postsvg/path_builder.rb:21-26`](../../lib/postsvg/path_builder.rb:21)
|
|
128
|
+
|
|
129
|
+
.Move to absolute position
|
|
130
|
+
[example]
|
|
131
|
+
====
|
|
132
|
+
[source,ruby]
|
|
133
|
+
----
|
|
134
|
+
builder = Postsvg::PathBuilder.new
|
|
135
|
+
|
|
136
|
+
builder.move_to(100, 50)
|
|
137
|
+
puts builder.to_path # → "M 100 50"
|
|
138
|
+
|
|
139
|
+
# Current position is now (100, 50)
|
|
140
|
+
----
|
|
141
|
+
====
|
|
142
|
+
|
|
143
|
+
.Start new subpath
|
|
144
|
+
[example]
|
|
145
|
+
====
|
|
146
|
+
[source,ruby]
|
|
147
|
+
----
|
|
148
|
+
builder = Postsvg::PathBuilder.new
|
|
149
|
+
|
|
150
|
+
# First subpath
|
|
151
|
+
builder.move_to(10, 10)
|
|
152
|
+
builder.line_to(90, 10)
|
|
153
|
+
builder.line_to(90, 90)
|
|
154
|
+
builder.close
|
|
155
|
+
|
|
156
|
+
# Second subpath
|
|
157
|
+
builder.move_to(20, 20)
|
|
158
|
+
builder.line_to(80, 20)
|
|
159
|
+
builder.line_to(80, 80)
|
|
160
|
+
builder.close
|
|
161
|
+
|
|
162
|
+
puts builder.to_path
|
|
163
|
+
# → "M 10 10 L 90 10 L 90 90 Z M 20 20 L 80 20 L 80 80 Z"
|
|
164
|
+
----
|
|
165
|
+
====
|
|
166
|
+
|
|
167
|
+
=== move_to_rel
|
|
168
|
+
|
|
169
|
+
Move to a relative position without drawing.
|
|
170
|
+
|
|
171
|
+
**Syntax:**
|
|
172
|
+
|
|
173
|
+
[source,ruby]
|
|
174
|
+
----
|
|
175
|
+
builder.move_to_rel(dx, dy) <1>
|
|
176
|
+
----
|
|
177
|
+
<1> Move cursor relative to current position
|
|
178
|
+
|
|
179
|
+
**Where:**
|
|
180
|
+
|
|
181
|
+
`dx`:: X-offset from current position (Integer or Float)
|
|
182
|
+
|
|
183
|
+
`dy`:: Y-offset from current position (Integer or Float)
|
|
184
|
+
|
|
185
|
+
**Returns:**
|
|
186
|
+
|
|
187
|
+
`nil` (modifies internal state)
|
|
188
|
+
|
|
189
|
+
**Side Effects:**
|
|
190
|
+
|
|
191
|
+
* Sets `@last_command` to `:move`
|
|
192
|
+
* Appends `"m dx dy"` to path parts
|
|
193
|
+
* Does NOT update `@current_x` and `@current_y` (relies on SVG to track)
|
|
194
|
+
|
|
195
|
+
**Source:**
|
|
196
|
+
|
|
197
|
+
[`lib/postsvg/path_builder.rb:28-31`](../../lib/postsvg/path_builder.rb:28)
|
|
198
|
+
|
|
199
|
+
.Relative move
|
|
200
|
+
[example]
|
|
201
|
+
====
|
|
202
|
+
[source,ruby]
|
|
203
|
+
----
|
|
204
|
+
builder = Postsvg::PathBuilder.new
|
|
205
|
+
|
|
206
|
+
builder.move_to(50, 50) # Absolute: (50, 50)
|
|
207
|
+
builder.line_to(100, 50) # Absolute: (100, 50)
|
|
208
|
+
builder.move_to_rel(0, 50) # Relative: +50 in y
|
|
209
|
+
|
|
210
|
+
puts builder.to_path
|
|
211
|
+
# → "M 50 50 L 100 50 m 0 50"
|
|
212
|
+
----
|
|
213
|
+
====
|
|
214
|
+
|
|
215
|
+
=== line_to
|
|
216
|
+
|
|
217
|
+
Draw a line to an absolute position.
|
|
218
|
+
|
|
219
|
+
**Syntax:**
|
|
220
|
+
|
|
221
|
+
[source,ruby]
|
|
222
|
+
----
|
|
223
|
+
builder.line_to(x, y) <1>
|
|
224
|
+
----
|
|
225
|
+
<1> Draw line from current position to absolute coordinates
|
|
226
|
+
|
|
227
|
+
**Where:**
|
|
228
|
+
|
|
229
|
+
`x`:: Target X-coordinate in user units (Integer or Float)
|
|
230
|
+
|
|
231
|
+
`y`:: Target Y-coordinate in user units (Integer or Float)
|
|
232
|
+
|
|
233
|
+
**Returns:**
|
|
234
|
+
|
|
235
|
+
`nil` (modifies internal state)
|
|
236
|
+
|
|
237
|
+
**Side Effects:**
|
|
238
|
+
|
|
239
|
+
* Updates `@current_x` and `@current_y` to target position
|
|
240
|
+
* Sets `@last_command` to `:line`
|
|
241
|
+
* Appends `"L x y"` to path parts
|
|
242
|
+
|
|
243
|
+
**Source:**
|
|
244
|
+
|
|
245
|
+
[`lib/postsvg/path_builder.rb:33-38`](../../lib/postsvg/path_builder.rb:33)
|
|
246
|
+
|
|
247
|
+
.Draw simple line
|
|
248
|
+
[example]
|
|
249
|
+
====
|
|
250
|
+
[source,ruby]
|
|
251
|
+
----
|
|
252
|
+
builder = Postsvg::PathBuilder.new
|
|
253
|
+
|
|
254
|
+
builder.move_to(0, 0)
|
|
255
|
+
builder.line_to(100, 0)
|
|
256
|
+
builder.line_to(100, 100)
|
|
257
|
+
|
|
258
|
+
puts builder.to_path
|
|
259
|
+
# → "M 0 0 L 100 0 L 100 100"
|
|
260
|
+
----
|
|
261
|
+
====
|
|
262
|
+
|
|
263
|
+
.Draw polyline
|
|
264
|
+
[example]
|
|
265
|
+
====
|
|
266
|
+
[source,ruby]
|
|
267
|
+
----
|
|
268
|
+
points = [[10, 10], [50, 30], [90, 10], [90, 90], [10, 90]]
|
|
269
|
+
|
|
270
|
+
builder = Postsvg::PathBuilder.new
|
|
271
|
+
builder.move_to(*points.first)
|
|
272
|
+
|
|
273
|
+
points[1..-1].each do |x, y|
|
|
274
|
+
builder.line_to(x, y)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
puts builder.to_path
|
|
278
|
+
# → "M 10 10 L 50 30 L 90 10 L 90 90 L 10 90"
|
|
279
|
+
----
|
|
280
|
+
====
|
|
281
|
+
|
|
282
|
+
=== line_to_rel
|
|
283
|
+
|
|
284
|
+
Draw a line to a relative position.
|
|
285
|
+
|
|
286
|
+
**Syntax:**
|
|
287
|
+
|
|
288
|
+
[source,ruby]
|
|
289
|
+
----
|
|
290
|
+
builder.line_to_rel(dx, dy) <1>
|
|
291
|
+
----
|
|
292
|
+
<1> Draw line using relative offset
|
|
293
|
+
|
|
294
|
+
**Where:**
|
|
295
|
+
|
|
296
|
+
`dx`:: X-offset from current position (Integer or Float)
|
|
297
|
+
|
|
298
|
+
`dy`:: Y-offset from current position (Integer or Float)
|
|
299
|
+
|
|
300
|
+
**Returns:**
|
|
301
|
+
|
|
302
|
+
`nil` (modifies internal state)
|
|
303
|
+
|
|
304
|
+
**Side Effects:**
|
|
305
|
+
|
|
306
|
+
* Sets `@last_command` to `:line`
|
|
307
|
+
* Appends `"l dx dy"` to path parts
|
|
308
|
+
|
|
309
|
+
**Source:**
|
|
310
|
+
|
|
311
|
+
[`lib/postsvg/path_builder.rb:40-43`](../../lib/postsvg/path_builder.rb:40)
|
|
312
|
+
|
|
313
|
+
.Draw with relative coordinates
|
|
314
|
+
[example]
|
|
315
|
+
====
|
|
316
|
+
[source,ruby]
|
|
317
|
+
----
|
|
318
|
+
builder = Postsvg::PathBuilder.new
|
|
319
|
+
|
|
320
|
+
builder.move_to(50, 50)
|
|
321
|
+
builder.line_to_rel(50, 0) # Right 50
|
|
322
|
+
builder.line_to_rel(0, 50) # Down 50
|
|
323
|
+
builder.line_to_rel(-50, 0) # Left 50
|
|
324
|
+
builder.line_to_rel(0, -50) # Up 50
|
|
325
|
+
|
|
326
|
+
puts builder.to_path
|
|
327
|
+
# → "M 50 50 l 50 0 l 0 50 l -50 0 l 0 -50"
|
|
328
|
+
# Creates a square using relative coordinates
|
|
329
|
+
----
|
|
330
|
+
====
|
|
331
|
+
|
|
332
|
+
=== curve_to
|
|
333
|
+
|
|
334
|
+
Draw a cubic Bézier curve to an absolute position.
|
|
335
|
+
|
|
336
|
+
**Syntax:**
|
|
337
|
+
|
|
338
|
+
[source,ruby]
|
|
339
|
+
----
|
|
340
|
+
builder.curve_to(x1, y1, x2, y2, x, y) <1>
|
|
341
|
+
----
|
|
342
|
+
<1> Draw curve with two control points and end point
|
|
343
|
+
|
|
344
|
+
**Where:**
|
|
345
|
+
|
|
346
|
+
`x1`, `y1`:: First control point coordinates (Integer or Float)
|
|
347
|
+
|
|
348
|
+
`x2`, `y2`:: Second control point coordinates (Integer or Float)
|
|
349
|
+
|
|
350
|
+
`x`, `y`:: End point coordinates (Integer or Float)
|
|
351
|
+
|
|
352
|
+
**Returns:**
|
|
353
|
+
|
|
354
|
+
`nil` (modifies internal state)
|
|
355
|
+
|
|
356
|
+
**Side Effects:**
|
|
357
|
+
|
|
358
|
+
* Updates `@current_x` and `@current_y` to end point (x, y)
|
|
359
|
+
* Sets `@last_command` to `:curve`
|
|
360
|
+
* Appends `"C x1 y1 x2 y2 x y"` to path parts
|
|
361
|
+
|
|
362
|
+
**Source:**
|
|
363
|
+
|
|
364
|
+
[`lib/postsvg/path_builder.rb:45-52`](../../lib/postsvg/path_builder.rb:45)
|
|
365
|
+
|
|
366
|
+
.Draw simple curve
|
|
367
|
+
[example]
|
|
368
|
+
====
|
|
369
|
+
[source,ruby]
|
|
370
|
+
----
|
|
371
|
+
builder = Postsvg::PathBuilder.new
|
|
372
|
+
|
|
373
|
+
# S-curve
|
|
374
|
+
builder.move_to(10, 80)
|
|
375
|
+
builder.curve_to(
|
|
376
|
+
40, 10, # First control point
|
|
377
|
+
60, 10, # Second control point
|
|
378
|
+
90, 80 # End point
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
puts builder.to_path
|
|
382
|
+
# → "M 10 80 C 40 10 60 10 90 80"
|
|
383
|
+
----
|
|
384
|
+
====
|
|
385
|
+
|
|
386
|
+
.Draw smooth wave
|
|
387
|
+
[example]
|
|
388
|
+
====
|
|
389
|
+
[source,ruby]
|
|
390
|
+
----
|
|
391
|
+
builder = Postsvg::PathBuilder.new
|
|
392
|
+
builder.move_to(0, 50)
|
|
393
|
+
|
|
394
|
+
# Multiple connected curves for wave pattern
|
|
395
|
+
[[20, 20, 40, 20, 50, 50],
|
|
396
|
+
[60, 80, 80, 80, 100, 50],
|
|
397
|
+
[120, 20, 140, 20, 150, 50]].each do |params|
|
|
398
|
+
builder.curve_to(*params)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
puts builder.to_path
|
|
402
|
+
# → "M 0 50 C 20 20 40 20 50 50 C 60 80 80 80 100 50 C 120 20 140 20 150 50"
|
|
403
|
+
----
|
|
404
|
+
====
|
|
405
|
+
|
|
406
|
+
=== curve_to_rel
|
|
407
|
+
|
|
408
|
+
Draw a cubic Bézier curve using relative coordinates.
|
|
409
|
+
|
|
410
|
+
**Syntax:**
|
|
411
|
+
|
|
412
|
+
[source,ruby]
|
|
413
|
+
----
|
|
414
|
+
builder.curve_to_rel(dx1, dy1, dx2, dy2, dx, dy) <1>
|
|
415
|
+
----
|
|
416
|
+
<1> Draw curve with relative control points and end point
|
|
417
|
+
|
|
418
|
+
**Where:**
|
|
419
|
+
|
|
420
|
+
`dx1`, `dy1`:: First control point offset from current position
|
|
421
|
+
|
|
422
|
+
`dx2`, `dy2`:: Second control point offset from current position
|
|
423
|
+
|
|
424
|
+
`dx`, `dy`:: End point offset from current position
|
|
425
|
+
|
|
426
|
+
**Returns:**
|
|
427
|
+
|
|
428
|
+
`nil` (modifies internal state)
|
|
429
|
+
|
|
430
|
+
**Source:**
|
|
431
|
+
|
|
432
|
+
[`lib/postsvg/path_builder.rb:54-59`](../../lib/postsvg/path_builder.rb:54)
|
|
433
|
+
|
|
434
|
+
.Relative curve drawing
|
|
435
|
+
[example]
|
|
436
|
+
====
|
|
437
|
+
[source,ruby]
|
|
438
|
+
----
|
|
439
|
+
builder = Postsvg::PathBuilder.new
|
|
440
|
+
builder.move_to(50, 50)
|
|
441
|
+
|
|
442
|
+
# Draw curve relative to current position
|
|
443
|
+
builder.curve_to_rel(
|
|
444
|
+
10, -20, # First control: (60, 30)
|
|
445
|
+
20, -20, # Second control: (70, 30)
|
|
446
|
+
30, 0 # End: (80, 50)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
puts builder.to_path
|
|
450
|
+
# → "M 50 50 c 10 -20 20 -20 30 0"
|
|
451
|
+
----
|
|
452
|
+
====
|
|
453
|
+
|
|
454
|
+
=== ellipse_to
|
|
455
|
+
|
|
456
|
+
Draw an elliptical arc to an absolute position.
|
|
457
|
+
|
|
458
|
+
**Syntax:**
|
|
459
|
+
|
|
460
|
+
[source,ruby]
|
|
461
|
+
----
|
|
462
|
+
builder.ellipse_to(rx, ry, rotation, large_arc, sweep, x, y) <1>
|
|
463
|
+
----
|
|
464
|
+
<1> Draw elliptical arc with specified parameters
|
|
465
|
+
|
|
466
|
+
**Where:**
|
|
467
|
+
|
|
468
|
+
`rx`:: X-radius of the ellipse (Float)
|
|
469
|
+
|
|
470
|
+
`ry`:: Y-radius of the ellipse (Float)
|
|
471
|
+
|
|
472
|
+
`rotation`:: Rotation angle of the ellipse in degrees (Float)
|
|
473
|
+
|
|
474
|
+
`large_arc`:: Use large arc (0 or 1)
|
|
475
|
+
* `0` - Arc spans ≤ 180°
|
|
476
|
+
* `1` - Arc spans > 180°
|
|
477
|
+
|
|
478
|
+
`sweep`:: Direction of arc (0 or 1)
|
|
479
|
+
* `0` - Counter-clockwise
|
|
480
|
+
* `1` - Clockwise
|
|
481
|
+
|
|
482
|
+
`x`, `y`:: End point coordinates (Integer or Float)
|
|
483
|
+
|
|
484
|
+
**Returns:**
|
|
485
|
+
|
|
486
|
+
`nil` (modifies internal state)
|
|
487
|
+
|
|
488
|
+
**Side Effects:**
|
|
489
|
+
|
|
490
|
+
* Updates `@current_x` and `@current_y` to end point
|
|
491
|
+
* Sets `@last_command` to `:arc`
|
|
492
|
+
* Appends `"A rx ry rotation large_arc sweep x y"` to path parts
|
|
493
|
+
|
|
494
|
+
**Source:**
|
|
495
|
+
|
|
496
|
+
[`lib/postsvg/path_builder.rb:61-67`](../../lib/postsvg/path_builder.rb:61)
|
|
497
|
+
|
|
498
|
+
.Draw circular arc
|
|
499
|
+
[example]
|
|
500
|
+
====
|
|
501
|
+
[source,ruby]
|
|
502
|
+
----
|
|
503
|
+
builder = Postsvg::PathBuilder.new
|
|
504
|
+
|
|
505
|
+
# Draw semicircle
|
|
506
|
+
builder.move_to(10, 50)
|
|
507
|
+
builder.ellipse_to(
|
|
508
|
+
40, 40, # Radii (same for circle)
|
|
509
|
+
0, # No rotation
|
|
510
|
+
0, # Small arc
|
|
511
|
+
1, # Clockwise
|
|
512
|
+
90, 50 # End point
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
puts builder.to_path
|
|
516
|
+
# → "M 10 50 A 40 40 0 0 1 90 50"
|
|
517
|
+
----
|
|
518
|
+
====
|
|
519
|
+
|
|
520
|
+
.Draw complex arc
|
|
521
|
+
[example]
|
|
522
|
+
====
|
|
523
|
+
[source,ruby]
|
|
524
|
+
----
|
|
525
|
+
builder = Postsvg::PathBuilder.new
|
|
526
|
+
|
|
527
|
+
# Draw rotated elliptical arc
|
|
528
|
+
builder.move_to(100, 100)
|
|
529
|
+
builder.ellipse_to(
|
|
530
|
+
50, 30, # Ellipse radii
|
|
531
|
+
45, # 45° rotation
|
|
532
|
+
1, # Large arc (>180°)
|
|
533
|
+
0, # Counter-clockwise
|
|
534
|
+
200, 200 # End point
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
puts builder.to_path
|
|
538
|
+
# → "M 100 100 A 50 30 45 1 0 200 200"
|
|
539
|
+
----
|
|
540
|
+
====
|
|
541
|
+
|
|
542
|
+
=== close
|
|
543
|
+
|
|
544
|
+
Close the current subpath by drawing a line to the starting point.
|
|
545
|
+
|
|
546
|
+
**Syntax:**
|
|
547
|
+
|
|
548
|
+
[source,ruby]
|
|
549
|
+
----
|
|
550
|
+
builder.close <1>
|
|
551
|
+
----
|
|
552
|
+
<1> Close current subpath
|
|
553
|
+
|
|
554
|
+
**Returns:**
|
|
555
|
+
|
|
556
|
+
`nil` (modifies internal state)
|
|
557
|
+
|
|
558
|
+
**Side Effects:**
|
|
559
|
+
|
|
560
|
+
* Sets `@last_command` to `:close`
|
|
561
|
+
* Appends `"Z"` to path parts
|
|
562
|
+
* Does not update current position (SVG handles this)
|
|
563
|
+
|
|
564
|
+
**Source:**
|
|
565
|
+
|
|
566
|
+
[`lib/postsvg/path_builder.rb:69-72`](../../lib/postsvg/path_builder.rb:69)
|
|
567
|
+
|
|
568
|
+
.Close simple shape
|
|
569
|
+
[example]
|
|
570
|
+
====
|
|
571
|
+
[source,ruby]
|
|
572
|
+
----
|
|
573
|
+
builder = Postsvg::PathBuilder.new
|
|
574
|
+
|
|
575
|
+
# Triangle
|
|
576
|
+
builder.move_to(50, 10)
|
|
577
|
+
builder.line_to(90, 90)
|
|
578
|
+
builder.line_to(10, 90)
|
|
579
|
+
builder.close # Completes triangle
|
|
580
|
+
|
|
581
|
+
puts builder.to_path
|
|
582
|
+
# → "M 50 10 L 90 90 L 10 90 Z"
|
|
583
|
+
----
|
|
584
|
+
====
|
|
585
|
+
|
|
586
|
+
.Multiple closed subpaths
|
|
587
|
+
[example]
|
|
588
|
+
====
|
|
589
|
+
[source,ruby]
|
|
590
|
+
----
|
|
591
|
+
builder = Postsvg::PathBuilder.new
|
|
592
|
+
|
|
593
|
+
# First shape
|
|
594
|
+
builder.move_to(10, 10)
|
|
595
|
+
builder.line_to(40, 10)
|
|
596
|
+
builder.line_to(40, 40)
|
|
597
|
+
builder.close
|
|
598
|
+
|
|
599
|
+
# Second shape
|
|
600
|
+
builder.move_to(60, 60)
|
|
601
|
+
builder.line_to(90, 60)
|
|
602
|
+
builder.line_to(90, 90)
|
|
603
|
+
builder.close
|
|
604
|
+
|
|
605
|
+
puts builder.to_path
|
|
606
|
+
# → "M 10 10 L 40 10 L 40 40 Z M 60 60 L 90 60 L 90 90 Z"
|
|
607
|
+
----
|
|
608
|
+
====
|
|
609
|
+
|
|
610
|
+
=== to_path
|
|
611
|
+
|
|
612
|
+
Convert accumulated path commands to SVG path data string.
|
|
613
|
+
|
|
614
|
+
**Syntax:**
|
|
615
|
+
|
|
616
|
+
[source,ruby]
|
|
617
|
+
----
|
|
618
|
+
path_data = builder.to_path <1>
|
|
619
|
+
----
|
|
620
|
+
<1> Generate SVG path data string
|
|
621
|
+
|
|
622
|
+
**Returns:**
|
|
623
|
+
|
|
624
|
+
String containing space-separated path commands (e.g., `"M 10 10 L 50 50 Z"`)
|
|
625
|
+
|
|
626
|
+
**Source:**
|
|
627
|
+
|
|
628
|
+
[`lib/postsvg/path_builder.rb:74-76`](../../lib/postsvg/path_builder.rb:74)
|
|
629
|
+
|
|
630
|
+
.Generate path data
|
|
631
|
+
[example]
|
|
632
|
+
====
|
|
633
|
+
[source,ruby]
|
|
634
|
+
----
|
|
635
|
+
builder = Postsvg::PathBuilder.new
|
|
636
|
+
builder.move_to(0, 0)
|
|
637
|
+
builder.line_to(100, 100)
|
|
638
|
+
|
|
639
|
+
path_data = builder.to_path
|
|
640
|
+
puts path_data # → "M 0 0 L 100 100"
|
|
641
|
+
|
|
642
|
+
# Use in SVG
|
|
643
|
+
svg = <<~SVG
|
|
644
|
+
<svg xmlns="http://www.w3.org/2000/svg">
|
|
645
|
+
<path d="#{path_data}" stroke="black" fill="none"/>
|
|
646
|
+
</svg>
|
|
647
|
+
SVG
|
|
648
|
+
----
|
|
649
|
+
====
|
|
650
|
+
|
|
651
|
+
=== length
|
|
652
|
+
|
|
653
|
+
Get the number of path command parts.
|
|
654
|
+
|
|
655
|
+
**Syntax:**
|
|
656
|
+
|
|
657
|
+
[source,ruby]
|
|
658
|
+
----
|
|
659
|
+
count = builder.length <1>
|
|
660
|
+
----
|
|
661
|
+
<1> Count path parts
|
|
662
|
+
|
|
663
|
+
**Returns:**
|
|
664
|
+
|
|
665
|
+
Integer count of path command parts
|
|
666
|
+
|
|
667
|
+
**Source:**
|
|
668
|
+
|
|
669
|
+
[`lib/postsvg/path_builder.rb:78-80`](../../lib/postsvg/path_builder.rb:78)
|
|
670
|
+
|
|
671
|
+
.Check path length
|
|
672
|
+
[example]
|
|
673
|
+
====
|
|
674
|
+
[source,ruby]
|
|
675
|
+
----
|
|
676
|
+
builder = Postsvg::PathBuilder.new
|
|
677
|
+
|
|
678
|
+
puts builder.length # → 0
|
|
679
|
+
|
|
680
|
+
builder.move_to(10, 10)
|
|
681
|
+
puts builder.length # → 1
|
|
682
|
+
|
|
683
|
+
builder.line_to(50, 50)
|
|
684
|
+
puts builder.length # → 2
|
|
685
|
+
|
|
686
|
+
builder.line_to(90, 10)
|
|
687
|
+
puts builder.length # → 3
|
|
688
|
+
----
|
|
689
|
+
====
|
|
690
|
+
|
|
691
|
+
=== empty?
|
|
692
|
+
|
|
693
|
+
Check if the path is empty.
|
|
694
|
+
|
|
695
|
+
**Syntax:**
|
|
696
|
+
|
|
697
|
+
[source,ruby]
|
|
698
|
+
----
|
|
699
|
+
is_empty = builder.empty? <1>
|
|
700
|
+
----
|
|
701
|
+
<1> Check for empty path
|
|
702
|
+
|
|
703
|
+
**Returns:**
|
|
704
|
+
|
|
705
|
+
Boolean (`true` if no path commands, `false` otherwise)
|
|
706
|
+
|
|
707
|
+
**Source:**
|
|
708
|
+
|
|
709
|
+
[`lib/postsvg/path_builder.rb:82-84`](../../lib/postsvg/path_builder.rb:82)
|
|
710
|
+
|
|
711
|
+
.Check if empty
|
|
712
|
+
[example]
|
|
713
|
+
====
|
|
714
|
+
[source,ruby]
|
|
715
|
+
----
|
|
716
|
+
builder = Postsvg::PathBuilder.new
|
|
717
|
+
|
|
718
|
+
puts builder.empty? # → true
|
|
719
|
+
|
|
720
|
+
builder.move_to(10, 10)
|
|
721
|
+
puts builder.empty? # → false
|
|
722
|
+
----
|
|
723
|
+
====
|
|
724
|
+
|
|
725
|
+
=== clear
|
|
726
|
+
|
|
727
|
+
Clear all path commands and reset state.
|
|
728
|
+
|
|
729
|
+
**Syntax:**
|
|
730
|
+
|
|
731
|
+
[source,ruby]
|
|
732
|
+
----
|
|
733
|
+
builder.clear <1>
|
|
734
|
+
----
|
|
735
|
+
<1> Reset path builder to initial state
|
|
736
|
+
|
|
737
|
+
**Returns:**
|
|
738
|
+
|
|
739
|
+
`nil`
|
|
740
|
+
|
|
741
|
+
**Side Effects:**
|
|
742
|
+
|
|
743
|
+
* Clears `@parts` array
|
|
744
|
+
* Resets `@last_command` to `nil`
|
|
745
|
+
* Resets `@current_x` and `@current_y` to 0.0
|
|
746
|
+
|
|
747
|
+
**Source:**
|
|
748
|
+
|
|
749
|
+
[`lib/postsvg/path_builder.rb:86-91`](../../lib/postsvg/path_builder.rb:86)
|
|
750
|
+
|
|
751
|
+
.Clear and reuse builder
|
|
752
|
+
[example]
|
|
753
|
+
====
|
|
754
|
+
[source,ruby]
|
|
755
|
+
----
|
|
756
|
+
builder = Postsvg::PathBuilder.new
|
|
757
|
+
builder.move_to(10, 10)
|
|
758
|
+
builder.line_to(50, 50)
|
|
759
|
+
|
|
760
|
+
puts builder.to_path # → "M 10 10 L 50 50"
|
|
761
|
+
|
|
762
|
+
# Clear for reuse
|
|
763
|
+
builder.clear
|
|
764
|
+
puts builder.empty? # → true
|
|
765
|
+
puts builder.to_path # → ""
|
|
766
|
+
|
|
767
|
+
# Build new path
|
|
768
|
+
builder.move_to(0, 0)
|
|
769
|
+
builder.line_to(100, 100)
|
|
770
|
+
puts builder.to_path # → "M 0 0 L 100 100"
|
|
771
|
+
----
|
|
772
|
+
====
|
|
773
|
+
|
|
774
|
+
=== reset
|
|
775
|
+
|
|
776
|
+
Create a new PathBuilder instance.
|
|
777
|
+
|
|
778
|
+
**Syntax:**
|
|
779
|
+
|
|
780
|
+
[source,ruby]
|
|
781
|
+
----
|
|
782
|
+
new_builder = builder.reset <1>
|
|
783
|
+
----
|
|
784
|
+
<1> Create fresh PathBuilder
|
|
785
|
+
|
|
786
|
+
**Returns:**
|
|
787
|
+
|
|
788
|
+
New `PathBuilder` instance (does NOT modify original)
|
|
789
|
+
|
|
790
|
+
**Source:**
|
|
791
|
+
|
|
792
|
+
[`lib/postsvg/path_builder.rb:93-95`](../../lib/postsvg/path_builder.rb:93)
|
|
793
|
+
|
|
794
|
+
NOTE: This method creates a NEW instance rather than resetting the current one. Use `clear` to reset the current builder.
|
|
795
|
+
|
|
796
|
+
.Reset vs Clear
|
|
797
|
+
[example]
|
|
798
|
+
====
|
|
799
|
+
[source,ruby]
|
|
800
|
+
----
|
|
801
|
+
builder = Postsvg::PathBuilder.new
|
|
802
|
+
builder.move_to(10, 10)
|
|
803
|
+
|
|
804
|
+
# reset creates NEW instance
|
|
805
|
+
new_builder = builder.reset
|
|
806
|
+
puts builder.to_path # → "M 10 10" (unchanged)
|
|
807
|
+
puts new_builder.to_path # → "" (new empty builder)
|
|
808
|
+
|
|
809
|
+
# clear modifies current instance
|
|
810
|
+
builder.clear
|
|
811
|
+
puts builder.to_path # → "" (cleared)
|
|
812
|
+
----
|
|
813
|
+
====
|
|
814
|
+
|
|
815
|
+
=== has_disconnected_subpath?
|
|
816
|
+
|
|
817
|
+
Check if path has a disconnected subpath.
|
|
818
|
+
|
|
819
|
+
**Syntax:**
|
|
820
|
+
|
|
821
|
+
[source,ruby]
|
|
822
|
+
----
|
|
823
|
+
is_disconnected = builder.has_disconnected_subpath? <1>
|
|
824
|
+
----
|
|
825
|
+
<1> Check for disconnected subpath
|
|
826
|
+
|
|
827
|
+
**Returns:**
|
|
828
|
+
|
|
829
|
+
Boolean:
|
|
830
|
+
* `true` - Path has data and last command is NOT move
|
|
831
|
+
* `false` - Path is empty OR last command is move
|
|
832
|
+
|
|
833
|
+
**Source:**
|
|
834
|
+
|
|
835
|
+
[`lib/postsvg/path_builder.rb:98-100`](../../lib/postsvg/path_builder.rb:98)
|
|
836
|
+
|
|
837
|
+
**Use Case:**
|
|
838
|
+
|
|
839
|
+
Detect when a new subpath should be started (typically after a paint operation).
|
|
840
|
+
|
|
841
|
+
.Check for disconnected subpath
|
|
842
|
+
[example]
|
|
843
|
+
====
|
|
844
|
+
[source,ruby]
|
|
845
|
+
----
|
|
846
|
+
builder = Postsvg::PathBuilder.new
|
|
847
|
+
|
|
848
|
+
puts builder.has_disconnected_subpath? # → false (empty)
|
|
849
|
+
|
|
850
|
+
builder.move_to(10, 10)
|
|
851
|
+
puts builder.has_disconnected_subpath? # → false (last is move)
|
|
852
|
+
|
|
853
|
+
builder.line_to(50, 50)
|
|
854
|
+
puts builder.has_disconnected_subpath? # → true (has data, last is line)
|
|
855
|
+
|
|
856
|
+
builder.move_to(100, 100)
|
|
857
|
+
puts builder.has_disconnected_subpath? # → false (last is move again)
|
|
858
|
+
----
|
|
859
|
+
====
|
|
860
|
+
|
|
861
|
+
=== has_content?
|
|
862
|
+
|
|
863
|
+
Check if path has actual drawing commands (not just moveto).
|
|
864
|
+
|
|
865
|
+
**Syntax:**
|
|
866
|
+
|
|
867
|
+
[source,ruby]
|
|
868
|
+
----
|
|
869
|
+
has_drawing = builder.has_content? <1>
|
|
870
|
+
----
|
|
871
|
+
<1> Check for meaningful content
|
|
872
|
+
|
|
873
|
+
**Returns:**
|
|
874
|
+
|
|
875
|
+
Boolean:
|
|
876
|
+
* `true` - Path has more than one command part
|
|
877
|
+
* `false` - Path is empty or has only one command
|
|
878
|
+
|
|
879
|
+
**Source:**
|
|
880
|
+
|
|
881
|
+
[`lib/postsvg/path_builder.rb:103-105`](../../lib/postsvg/path_builder.rb:103)
|
|
882
|
+
|
|
883
|
+
.Check for content
|
|
884
|
+
[example]
|
|
885
|
+
====
|
|
886
|
+
[source,ruby]
|
|
887
|
+
----
|
|
888
|
+
builder = Postsvg::PathBuilder.new
|
|
889
|
+
|
|
890
|
+
puts builder.has_content? # → false (empty)
|
|
891
|
+
|
|
892
|
+
builder.move_to(10, 10)
|
|
893
|
+
puts builder.has_content? # → false (only move)
|
|
894
|
+
|
|
895
|
+
builder.line_to(50, 50)
|
|
896
|
+
puts builder.has_content? # → true (has drawing command)
|
|
897
|
+
----
|
|
898
|
+
====
|
|
899
|
+
|
|
900
|
+
=== current_point
|
|
901
|
+
|
|
902
|
+
Get the current drawing position.
|
|
903
|
+
|
|
904
|
+
**Syntax:**
|
|
905
|
+
|
|
906
|
+
[source,ruby]
|
|
907
|
+
----
|
|
908
|
+
x, y = builder.current_point <1>
|
|
909
|
+
----
|
|
910
|
+
<1> Get current coordinates
|
|
911
|
+
|
|
912
|
+
**Returns:**
|
|
913
|
+
|
|
914
|
+
Array `[x, y]` containing current position coordinates (Float)
|
|
915
|
+
|
|
916
|
+
**Source:**
|
|
917
|
+
|
|
918
|
+
[`lib/postsvg/path_builder.rb:107-109`](../../lib/postsvg/path_builder.rb:107)
|
|
919
|
+
|
|
920
|
+
.Track current position
|
|
921
|
+
[example]
|
|
922
|
+
====
|
|
923
|
+
[source,ruby]
|
|
924
|
+
----
|
|
925
|
+
builder = Postsvg::PathBuilder.new
|
|
926
|
+
|
|
927
|
+
x, y = builder.current_point
|
|
928
|
+
puts "Start: (#{x}, #{y})" # → "Start: (0.0, 0.0)"
|
|
929
|
+
|
|
930
|
+
builder.move_to(50, 75)
|
|
931
|
+
x, y = builder.current_point
|
|
932
|
+
puts "After move: (#{x}, #{y})" # → "After move: (50, 75)"
|
|
933
|
+
|
|
934
|
+
builder.line_to(100, 125)
|
|
935
|
+
x, y = builder.current_point
|
|
936
|
+
puts "After line: (#{x}, #{y})" # → "After line: (100, 125)"
|
|
937
|
+
----
|
|
938
|
+
====
|
|
939
|
+
|
|
940
|
+
=== dup
|
|
941
|
+
|
|
942
|
+
Create a deep copy of the PathBuilder.
|
|
943
|
+
|
|
944
|
+
**Syntax:**
|
|
945
|
+
|
|
946
|
+
[source,ruby]
|
|
947
|
+
----
|
|
948
|
+
copy = builder.dup <1>
|
|
949
|
+
----
|
|
950
|
+
<1> Duplicate path builder
|
|
951
|
+
|
|
952
|
+
**Returns:**
|
|
953
|
+
|
|
954
|
+
New `PathBuilder` instance with copied state:
|
|
955
|
+
* Duplicated `@parts` array
|
|
956
|
+
* Copied `@last_command`
|
|
957
|
+
* Copied `@current_x` and `@current_y`
|
|
958
|
+
|
|
959
|
+
**Source:**
|
|
960
|
+
|
|
961
|
+
[`lib/postsvg/path_builder.rb:111-118`](../../lib/postsvg/path_builder.rb:111)
|
|
962
|
+
|
|
963
|
+
.Duplicate builder
|
|
964
|
+
[example]
|
|
965
|
+
====
|
|
966
|
+
[source,ruby]
|
|
967
|
+
----
|
|
968
|
+
builder = Postsvg::PathBuilder.new
|
|
969
|
+
builder.move_to(10, 10)
|
|
970
|
+
builder.line_to(50, 50)
|
|
971
|
+
|
|
972
|
+
# Create independent copy
|
|
973
|
+
copy = builder.dup
|
|
974
|
+
|
|
975
|
+
# Modify copy
|
|
976
|
+
copy.line_to(90, 90)
|
|
977
|
+
|
|
978
|
+
puts builder.to_path # → "M 10 10 L 50 50"
|
|
979
|
+
puts copy.to_path # → "M 10 10 L 50 50 L 90 90"
|
|
980
|
+
----
|
|
981
|
+
====
|
|
982
|
+
|
|
983
|
+
== Attributes
|
|
984
|
+
|
|
985
|
+
=== parts (read-only)
|
|
986
|
+
|
|
987
|
+
Access the array of path command parts.
|
|
988
|
+
|
|
989
|
+
**Syntax:**
|
|
990
|
+
|
|
991
|
+
[source,ruby]
|
|
992
|
+
----
|
|
993
|
+
parts_array = builder.parts <1>
|
|
994
|
+
----
|
|
995
|
+
<1> Get path parts array
|
|
996
|
+
|
|
997
|
+
**Returns:**
|
|
998
|
+
|
|
999
|
+
Array of strings, each representing one path command
|
|
1000
|
+
|
|
1001
|
+
**Source:**
|
|
1002
|
+
|
|
1003
|
+
[`lib/postsvg/path_builder.rb:8`](../../lib/postsvg/path_builder.rb:8)
|
|
1004
|
+
|
|
1005
|
+
.Inspect path parts
|
|
1006
|
+
[example]
|
|
1007
|
+
====
|
|
1008
|
+
[source,ruby]
|
|
1009
|
+
----
|
|
1010
|
+
builder = Postsvg::PathBuilder.new
|
|
1011
|
+
builder.move_to(10, 10)
|
|
1012
|
+
builder.line_to(50, 50)
|
|
1013
|
+
builder.close
|
|
1014
|
+
|
|
1015
|
+
puts builder.parts.inspect
|
|
1016
|
+
# → ["M 10 10", "L 50 50", "Z"]
|
|
1017
|
+
|
|
1018
|
+
puts builder.parts.join(" ")
|
|
1019
|
+
# → "M 10 10 L 50 50 Z" (same as to_path)
|
|
1020
|
+
----
|
|
1021
|
+
====
|
|
1022
|
+
|
|
1023
|
+
== Usage Patterns
|
|
1024
|
+
|
|
1025
|
+
=== Pattern 1: Simple Shape Construction
|
|
1026
|
+
|
|
1027
|
+
[source,ruby]
|
|
1028
|
+
----
|
|
1029
|
+
require 'postsvg'
|
|
1030
|
+
|
|
1031
|
+
def build_rectangle(x, y, width, height)
|
|
1032
|
+
builder = Postsvg::PathBuilder.new
|
|
1033
|
+
|
|
1034
|
+
builder.move_to(x, y)
|
|
1035
|
+
builder.line_to(x + width, y)
|
|
1036
|
+
builder.line_to(x + width, y + height)
|
|
1037
|
+
builder.line_to(x, y + height)
|
|
1038
|
+
builder.close
|
|
1039
|
+
|
|
1040
|
+
builder.to_path
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
rect_path = build_rectangle(10, 10, 80, 60)
|
|
1044
|
+
puts rect_path # → "M 10 10 L 90 10 L 90 70 L 10 70 Z"
|
|
1045
|
+
----
|
|
1046
|
+
|
|
1047
|
+
=== Pattern 2: Reusable Builder
|
|
1048
|
+
|
|
1049
|
+
[source,ruby]
|
|
1050
|
+
----
|
|
1051
|
+
require 'postsvg'
|
|
1052
|
+
|
|
1053
|
+
class ShapeBuilder
|
|
1054
|
+
def initialize
|
|
1055
|
+
@builder = Postsvg::PathBuilder.new
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
def add_circle(cx, cy, r, segments = 16)
|
|
1059
|
+
@builder.move_to(cx + r, cy)
|
|
1060
|
+
|
|
1061
|
+
(1..segments).each do |i|
|
|
1062
|
+
angle = (i * 2 * Math::PI) / segments
|
|
1063
|
+
x = cx + r * Math.cos(angle)
|
|
1064
|
+
y = cy + r * Math.sin(angle)
|
|
1065
|
+
@builder.line_to(x, y)
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
@builder.close
|
|
1069
|
+
self # Enable chaining
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
def to_path
|
|
1073
|
+
@builder.to_path
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
def clear
|
|
1077
|
+
@builder.clear
|
|
1078
|
+
self
|
|
1079
|
+
end
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
# Usage
|
|
1083
|
+
shapes = ShapeBuilder.new
|
|
1084
|
+
shapes.add_circle(50, 50, 30)
|
|
1085
|
+
puts shapes.to_path
|
|
1086
|
+
----
|
|
1087
|
+
|
|
1088
|
+
=== Pattern 3: Incremental Path Construction
|
|
1089
|
+
|
|
1090
|
+
[source,ruby]
|
|
1091
|
+
----
|
|
1092
|
+
require 'postsvg'
|
|
1093
|
+
|
|
1094
|
+
class IncrementalPath
|
|
1095
|
+
def initialize
|
|
1096
|
+
@builder = Postsvg::PathBuilder.new
|
|
1097
|
+
@started = false
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
def add_point(x, y)
|
|
1101
|
+
if @started
|
|
1102
|
+
@builder.line_to(x, y)
|
|
1103
|
+
else
|
|
1104
|
+
@builder.move_to(x, y)
|
|
1105
|
+
@started = true
|
|
1106
|
+
end
|
|
1107
|
+
self
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
def close_path
|
|
1111
|
+
@builder.close if @started
|
|
1112
|
+
@started = false
|
|
1113
|
+
self
|
|
1114
|
+
end
|
|
1115
|
+
|
|
1116
|
+
def to_path
|
|
1117
|
+
@builder.to_path
|
|
1118
|
+
end
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
# Usage - build path from data points
|
|
1122
|
+
path = IncrementalPath.new
|
|
1123
|
+
[[10, 10], [50, 30], [90, 10], [70, 50]].each do |x, y|
|
|
1124
|
+
path.add_point(x, y)
|
|
1125
|
+
end
|
|
1126
|
+
path.close_path
|
|
1127
|
+
|
|
1128
|
+
puts path.to_path
|
|
1129
|
+
----
|
|
1130
|
+
|
|
1131
|
+
=== Pattern 4: Complex Shape Library
|
|
1132
|
+
|
|
1133
|
+
[source,ruby]
|
|
1134
|
+
----
|
|
1135
|
+
require 'postsvg'
|
|
1136
|
+
|
|
1137
|
+
module Shapes
|
|
1138
|
+
def self.star(cx, cy, outer_r, inner_r, points = 5)
|
|
1139
|
+
builder = Postsvg::PathBuilder.new
|
|
1140
|
+
|
|
1141
|
+
points.times do |i|
|
|
1142
|
+
# Outer point
|
|
1143
|
+
angle = (i * 2 * Math::PI) / points - Math::PI / 2
|
|
1144
|
+
x = cx + outer_r * Math.cos(angle)
|
|
1145
|
+
y = cy + outer_r * Math.sin(angle)
|
|
1146
|
+
|
|
1147
|
+
if i == 0
|
|
1148
|
+
builder.move_to(x, y)
|
|
1149
|
+
else
|
|
1150
|
+
builder.line_to(x, y)
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
# Inner point
|
|
1154
|
+
angle += Math::PI / points
|
|
1155
|
+
x = cx + inner_r * Math.cos(angle)
|
|
1156
|
+
y = cy + inner_r * Math.sin(angle)
|
|
1157
|
+
builder.line_to(x, y)
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
builder.close
|
|
1161
|
+
builder.to_path
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
def self.rounded_rectangle(x, y, w, h, r)
|
|
1165
|
+
builder = Postsvg::PathBuilder.new
|
|
1166
|
+
|
|
1167
|
+
builder.move_to(x + r, y)
|
|
1168
|
+
builder.line_to(x + w - r, y)
|
|
1169
|
+
builder.ellipse_to(r, r, 0, 0, 1, x + w, y + r)
|
|
1170
|
+
builder.line_to(x + w, y + h - r)
|
|
1171
|
+
builder.ellipse_to(r, r, 0, 0, 1, x + w - r, y + h)
|
|
1172
|
+
builder.line_to(x + r, y + h)
|
|
1173
|
+
builder.ellipse_to(r, r, 0, 0, 1, x, y + h - r)
|
|
1174
|
+
builder.line_to(x, y + r)
|
|
1175
|
+
builder.ellipse_to(r, r, 0, 0, 1, x + r, y)
|
|
1176
|
+
builder.close
|
|
1177
|
+
|
|
1178
|
+
builder.to_path
|
|
1179
|
+
end
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
# Usage
|
|
1183
|
+
star_path = Shapes.star(100, 100, 50, 25, 5)
|
|
1184
|
+
rounded_rect = Shapes.rounded_rectangle(10, 10, 180, 80, 10)
|
|
1185
|
+
----
|
|
1186
|
+
|
|
1187
|
+
== Thread Safety
|
|
1188
|
+
|
|
1189
|
+
The `PathBuilder` class is **not thread-safe** due to mutable internal state. Each thread should have its own instance.
|
|
1190
|
+
|
|
1191
|
+
.Correct multi-threaded usage
|
|
1192
|
+
[example]
|
|
1193
|
+
====
|
|
1194
|
+
[source,ruby]
|
|
1195
|
+
----
|
|
1196
|
+
# Bad: Sharing builder across threads
|
|
1197
|
+
builder = Postsvg::PathBuilder.new
|
|
1198
|
+
threads = shapes.map do |shape|
|
|
1199
|
+
Thread.new do
|
|
1200
|
+
shape.points.each { |x, y| builder.line_to(x, y) }
|
|
1201
|
+
end
|
|
1202
|
+
end
|
|
1203
|
+
# RACE CONDITION!
|
|
1204
|
+
|
|
1205
|
+
# Good: Each thread creates own builder
|
|
1206
|
+
threads = shapes.map do |shape|
|
|
1207
|
+
Thread.new do
|
|
1208
|
+
builder = Postsvg::PathBuilder.new
|
|
1209
|
+
builder.move_to(*shape.points.first)
|
|
1210
|
+
shape.points[1..-1].each { |x, y| builder.line_to(x, y) }
|
|
1211
|
+
builder.to_path
|
|
1212
|
+
end
|
|
1213
|
+
end
|
|
1214
|
+
|
|
1215
|
+
paths = threads.map(&:value)
|
|
1216
|
+
----
|
|
1217
|
+
====
|
|
1218
|
+
|
|
1219
|
+
== Performance Considerations
|
|
1220
|
+
|
|
1221
|
+
**Time Complexity:**
|
|
1222
|
+
|
|
1223
|
+
* Adding command: O(1) - appends to array
|
|
1224
|
+
* `to_path`: O(n) where n = number of parts (join operation)
|
|
1225
|
+
* `dup`: O(n) where n = number of parts (array duplication)
|
|
1226
|
+
|
|
1227
|
+
**Space Complexity:**
|
|
1228
|
+
|
|
1229
|
+
* Memory: O(n) where n = number of path commands
|
|
1230
|
+
* Each command stored as formatted string
|
|
1231
|
+
|
|
1232
|
+
**Optimization Tips:**
|
|
1233
|
+
|
|
1234
|
+
1. **Reuse builders**: Use `clear` instead of creating new instances
|
|
1235
|
+
2. **Minimize to_path calls**: Generate path data only when needed
|
|
1236
|
+
3. **Use relative commands**: Can produce shorter path strings
|
|
1237
|
+
4. **Avoid unnecessary precision**: num_fmt handles this automatically
|
|
1238
|
+
|
|
1239
|
+
.Performance measurement
|
|
1240
|
+
[example]
|
|
1241
|
+
====
|
|
1242
|
+
[source,ruby]
|
|
1243
|
+
----
|
|
1244
|
+
require 'postsvg'
|
|
1245
|
+
require 'benchmark'
|
|
1246
|
+
|
|
1247
|
+
# Test path building performance
|
|
1248
|
+
builder = Postsvg::PathBuilder.new
|
|
1249
|
+
|
|
1250
|
+
time = Benchmark.measure do
|
|
1251
|
+
1000.times do |i|
|
|
1252
|
+
builder.move_to(i * 10, i * 5)
|
|
1253
|
+
builder.line_to(i * 10 + 50, i * 5 + 50)
|
|
1254
|
+
builder.close
|
|
1255
|
+
end
|
|
1256
|
+
end
|
|
1257
|
+
|
|
1258
|
+
puts "Built #{builder.length} commands in #{'%.3f' % time.real}s"
|
|
1259
|
+
puts "Rate: #{(builder.length / time.real).to_i} commands/sec"
|
|
1260
|
+
|
|
1261
|
+
gen_time = Benchmark.measure do
|
|
1262
|
+
@path = builder.to_path
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
puts "Generated path in #{'%.3f' % gen_time.real}s"
|
|
1266
|
+
puts "Path length: #{@path.length} characters"
|
|
1267
|
+
----
|
|
1268
|
+
====
|
|
1269
|
+
|
|
1270
|
+
== Next Steps
|
|
1271
|
+
|
|
1272
|
+
* Learn about link:svg-generator.adoc[SvgGenerator] for complete SVG document generation
|
|
1273
|
+
* Review link:matrix.adoc[Matrix Class] for coordinate transformations
|
|
1274
|
+
* See link:interpreter.adoc[Interpreter] for PostScript execution
|
|
1275
|
+
* Check link:../architecture.adoc[Architecture] for system design
|
|
1276
|
+
|
|
1277
|
+
== Bibliography
|
|
1278
|
+
|
|
1279
|
+
* link:svg-generator.adoc[SvgGenerator Documentation]
|
|
1280
|
+
* link:matrix.adoc[Matrix Documentation]
|
|
1281
|
+
* link:interpreter.adoc[Interpreter Documentation]
|
|
1282
|
+
* link:../architecture.adoc[Architecture Overview]
|
|
1283
|
+
* link:https://www.w3.org/TR/SVG2/paths.html[W3C SVG Paths Specification]
|
|
1284
|
+
* link:https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths[MDN SVG Path Tutorial]
|