postsvg 0.1.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +19 -0
  3. data/.rubocop_todo.yml +141 -0
  4. data/Gemfile +15 -0
  5. data/LICENSE +25 -0
  6. data/README.adoc +473 -0
  7. data/Rakefile +10 -0
  8. data/docs/POSTSCRIPT.adoc +13 -0
  9. data/docs/postscript/fundamentals.adoc +356 -0
  10. data/docs/postscript/graphics-model.adoc +406 -0
  11. data/docs/postscript/implementation-notes.adoc +314 -0
  12. data/docs/postscript/index.adoc +153 -0
  13. data/docs/postscript/operators/arithmetic.adoc +461 -0
  14. data/docs/postscript/operators/control-flow.adoc +230 -0
  15. data/docs/postscript/operators/dictionary.adoc +191 -0
  16. data/docs/postscript/operators/graphics-state.adoc +528 -0
  17. data/docs/postscript/operators/index.adoc +288 -0
  18. data/docs/postscript/operators/painting.adoc +475 -0
  19. data/docs/postscript/operators/path-construction.adoc +553 -0
  20. data/docs/postscript/operators/stack-manipulation.adoc +374 -0
  21. data/docs/postscript/operators/transformations.adoc +479 -0
  22. data/docs/postscript/svg-mapping.adoc +369 -0
  23. data/exe/postsvg +6 -0
  24. data/lib/postsvg/cli.rb +103 -0
  25. data/lib/postsvg/colors.rb +33 -0
  26. data/lib/postsvg/converter.rb +214 -0
  27. data/lib/postsvg/errors.rb +11 -0
  28. data/lib/postsvg/graphics_state.rb +158 -0
  29. data/lib/postsvg/interpreter.rb +891 -0
  30. data/lib/postsvg/matrix.rb +106 -0
  31. data/lib/postsvg/parser/postscript_parser.rb +87 -0
  32. data/lib/postsvg/parser/transform.rb +21 -0
  33. data/lib/postsvg/parser.rb +18 -0
  34. data/lib/postsvg/path_builder.rb +101 -0
  35. data/lib/postsvg/svg_generator.rb +78 -0
  36. data/lib/postsvg/tokenizer.rb +161 -0
  37. data/lib/postsvg/version.rb +5 -0
  38. data/lib/postsvg.rb +78 -0
  39. data/postsvg.gemspec +38 -0
  40. data/scripts/regenerate_fixtures.rb +28 -0
  41. metadata +118 -0
@@ -0,0 +1,406 @@
1
+ = PostScript Graphics Model
2
+
3
+ == General
4
+
5
+ The PostScript graphics model provides a powerful framework for describing
6
+ two-dimensional vector graphics. It consists of three main components:
7
+
8
+ * Current path - The geometric shape being constructed
9
+ * Graphics state - Parameters affecting how shapes are rendered
10
+ * Painting model - How paths are converted to visible output
11
+
12
+ Understanding this model is essential for working with PostScript graphics.
13
+
14
+ == Current Path
15
+
16
+ === General
17
+
18
+ The current path is a sequence of connected and disconnected geometric shapes
19
+ that define the outline of an object. The path is not visible until you apply
20
+ a painting operation like `stroke` or `fill`.
21
+
22
+ A path consists of one or more subpaths, where each subpath is either open or
23
+ closed.
24
+
25
+ === Path construction
26
+
27
+ Paths are built incrementally using path construction operators:
28
+
29
+ [source,postscript]
30
+ ----
31
+ newpath % Initialize empty path
32
+ 10 10 moveto % Begin new subpath at (10, 10)
33
+ 90 10 lineto % Add line segment to (90, 10)
34
+ 90 90 lineto % Add line segment to (90, 90)
35
+ 10 90 lineto % Add line segment to (10, 90)
36
+ closepath % Close subpath back to start
37
+ ----
38
+
39
+ This creates a rectangular path with four line segments.
40
+
41
+ === Path segments
42
+
43
+ Straight lines:: Created with `moveto` and `lineto`
44
+ Curves:: Created with `curveto` using Bézier curves
45
+ Arcs:: Created with `arc` and `arcn` for circular segments
46
+
47
+ [example]
48
+ ====
49
+ [source,postscript]
50
+ ----
51
+ newpath
52
+ 50 50 moveto % Start point
53
+ 50 150 curveto % Control point 1
54
+ 150 150 curveto % Control point 2
55
+ 150 50 lineto % End point: curved then straight
56
+ ----
57
+
58
+ This creates a path with both curved and straight segments.
59
+ ====
60
+
61
+ === Subpaths
62
+
63
+ A path can contain multiple subpaths, each started with `moveto`:
64
+
65
+ [example]
66
+ ====
67
+ [source,postscript]
68
+ ----
69
+ newpath
70
+ % First subpath: outer rectangle
71
+ 0 0 moveto
72
+ 100 0 lineto
73
+ 100 100 lineto
74
+ 0 100 lineto
75
+ closepath
76
+
77
+ % Second subpath: inner rectangle (hole)
78
+ 25 25 moveto
79
+ 75 25 lineto
80
+ 75 75 lineto
81
+ 25 75 lineto
82
+ closepath
83
+
84
+ fill % Fills outer, creates hole with inner
85
+ ----
86
+
87
+ Multiple subpaths enable complex shapes with holes.
88
+ ====
89
+
90
+ == Graphics State
91
+
92
+ === General
93
+
94
+ The graphics state is a collection of parameters that control how graphics
95
+ operations are performed. It includes colors, line attributes, clipping paths,
96
+ and transformation matrices.
97
+
98
+ The graphics state can be saved and restored using `gsave` and `grestore`,
99
+ enabling temporary changes without losing the previous state.
100
+
101
+ === Graphics state parameters
102
+
103
+ ==== Current transformation matrix (CTM)
104
+
105
+ A 3×2 matrix that maps user space coordinates to device space:
106
+
107
+ [source]
108
+ ----
109
+ [ a b 0 ]
110
+ [ c d 0 ]
111
+ [ e f 1 ]
112
+ ----
113
+
114
+ Where `[a b c d e f]` represents the transformation.
115
+
116
+ ==== Color parameters
117
+
118
+ Fill color:: Color used for `fill` operations
119
+ Stroke color:: Color used for `stroke` operations
120
+
121
+ Both are set with color operators like `setrgbcolor` or `setgray`.
122
+
123
+ ==== Line parameters
124
+
125
+ Line width:: Thickness of stroked lines (set with `setlinewidth`)
126
+ Line cap:: Shape of line ends (set with `setlinecap`)
127
+ Line join:: Shape of line corners (set with `setlinejoin`)
128
+ Miter limit:: Limit for miter joins (set with `setmiterlimit`)
129
+ Dash pattern:: Pattern for dashed lines (set with `setdash`)
130
+
131
+ ==== Clipping path
132
+
133
+ The clipping path restricts the visible region. Only graphics within the
134
+ clipping path are rendered.
135
+
136
+ === Graphics state stack
137
+
138
+ [example]
139
+ ====
140
+ [source,postscript]
141
+ ----
142
+ 1 setlinewidth % Set line width to 1
143
+ 0 0 0 setrgbcolor % Set color to black
144
+
145
+ gsave % Save current state
146
+ 5 setlinewidth % Change to width 5
147
+ 1 0 0 setrgbcolor % Change to red
148
+ % Draw with thick red lines
149
+ grestore % Restore: back to width 1, black
150
+
151
+ % Now drawing with original settings
152
+ ----
153
+
154
+ The `gsave`/`grestore` pair maintains state isolation.
155
+ ====
156
+
157
+ == Painting Model
158
+
159
+ === General
160
+
161
+ Painting operations convert the current path into visible marks on the page.
162
+ The two primary operations are stroking and filling.
163
+
164
+ [[stroke-operation]]
165
+ === Stroking
166
+
167
+ The `stroke` operator paints a line along the current path using the current
168
+ stroke color and line attributes.
169
+
170
+ [source,postscript]
171
+ ----
172
+ newpath
173
+ 10 10 moveto
174
+ 90 90 lineto
175
+ 2 setlinewidth % Set line width
176
+ 1 0 0 setrgbcolor % Set color to red
177
+ stroke % Paint the line
178
+ ----
179
+
180
+ Stroking uses:
181
+
182
+ * Stroke color
183
+ * Line width
184
+ * Line cap style
185
+ * Line join style
186
+ * Dash pattern
187
+
188
+ [[fill-operation]]
189
+ === Filling
190
+
191
+ The `fill` operator fills the interior of the current path with the current
192
+ fill color.
193
+
194
+ [source,postscript]
195
+ ----
196
+ newpath
197
+ 50 50 moveto
198
+ 150 50 lineto
199
+ 100 150 lineto
200
+ closepath
201
+ 0 0 1 setrgbcolor % Set color to blue
202
+ fill % Fill the triangle
203
+ ----
204
+
205
+ === Fill rules
206
+
207
+ PostScript supports two fill rules for determining what is "inside" a path:
208
+
209
+ Non-zero winding rule:: Default rule, used by `fill`
210
+ Even-odd rule:: Alternative rule, used by `eofill`
211
+
212
+ [example]
213
+ ====
214
+ [source,postscript]
215
+ ----
216
+ % Two overlapping squares
217
+ newpath
218
+ 0 0 moveto 100 0 lineto 100 100 lineto 0 100 lineto closepath
219
+ 50 50 moveto 150 50 lineto 150 150 lineto 50 150 lineto closepath
220
+
221
+ fill % Uses non-zero winding: both squares filled
222
+ % OR
223
+ eofill % Uses even-odd: overlap creates hole
224
+ ----
225
+
226
+ The even-odd rule treats overlapping regions differently than the non-zero
227
+ winding rule.
228
+ ====
229
+
230
+ === Clipping
231
+
232
+ The `clip` operator establishes a clipping path that restricts subsequent
233
+ drawing operations.
234
+
235
+ [example]
236
+ ====
237
+ [source,postscript]
238
+ ----
239
+ gsave
240
+ % Define clipping region
241
+ newpath
242
+ 50 50 100 0 360 arc % Circle
243
+ clip % Establish as clip path
244
+
245
+ % Draw something - only visible within circle
246
+ newpath
247
+ 0 0 moveto
248
+ 200 200 lineto
249
+ stroke
250
+ grestore % Remove clipping restriction
251
+ ----
252
+
253
+ Clipping is useful for masking graphics to specific regions.
254
+ ====
255
+
256
+ == Coordinate Transformations
257
+
258
+ === General
259
+
260
+ Coordinate transformations modify the current transformation matrix (CTM),
261
+ affecting how coordinates are interpreted in subsequent operations.
262
+
263
+ === Transformation types
264
+
265
+ [[translation]]
266
+ ==== Translation
267
+
268
+ Move the origin to a new location:
269
+
270
+ [source,postscript]
271
+ ----
272
+ 100 50 translate % Move origin to (100, 50)
273
+ 0 0 moveto % Now at (100, 50) in original space
274
+ ----
275
+
276
+ [[scaling]]
277
+ ==== Scaling
278
+
279
+ Scale the coordinate system:
280
+
281
+ [source,postscript]
282
+ ----
283
+ 2 2 scale % Double all dimensions
284
+ 50 50 moveto % Actually at (100, 100)
285
+ 25 0 rlineto % Draws line of length 50
286
+ ----
287
+
288
+ [[rotation]]
289
+ ==== Rotation
290
+
291
+ Rotate the coordinate system:
292
+
293
+ [source,postscript]
294
+ ----
295
+ 45 rotate % Rotate 45 degrees counterclockwise
296
+ 100 0 moveto % Moves at 45-degree angle
297
+ ----
298
+
299
+ === Transformation order
300
+
301
+ Transformations are cumulative and apply in the order specified:
302
+
303
+ [example]
304
+ ====
305
+ [source,postscript]
306
+ ----
307
+ gsave
308
+ 100 100 translate % Step 1: Move origin to (100, 100)
309
+ 45 rotate % Step 2: Rotate around new origin
310
+ 2 2 scale % Step 3: Scale in rotated space
311
+
312
+ % Draw a square - it will be:
313
+ % - Doubled in size (scale)
314
+ % - Rotated 45 degrees (rotate)
315
+ % - Positioned at (100, 100) (translate)
316
+ newpath
317
+ 0 0 moveto
318
+ 50 0 lineto
319
+ 50 50 lineto
320
+ 0 50 lineto
321
+ closepath
322
+ stroke
323
+ grestore
324
+ ----
325
+
326
+ The order matters: translating then rotating gives different results than
327
+ rotating then translating.
328
+ ====
329
+
330
+ === Transformation matrices
331
+
332
+ Advanced users can manipulate the CTM directly using matrix operators:
333
+
334
+ [source,postscript]
335
+ ----
336
+ [a b c d e f] concat % Concatenate matrix to CTM
337
+ matrix currentmatrix % Get current CTM
338
+ [...] setmatrix % Set CTM directly
339
+ ----
340
+
341
+ == Device Independence
342
+
343
+ === General
344
+
345
+ PostScript's graphics model is device-independent, meaning the same PostScript
346
+ code produces consistent output on different devices (printers, displays).
347
+
348
+ The CTM handles conversion from user space (coordinates in your program) to
349
+ device space (pixels or dots on the output device).
350
+
351
+ === Resolution independence
352
+
353
+ [example]
354
+ ====
355
+ A circle with radius 50 points:
356
+
357
+ [source,postscript]
358
+ ----
359
+ 50 50 50 0 360 arc
360
+ ----
361
+
362
+ This produces:
363
+
364
+ * On 72 dpi screen: circle with ~50 pixel radius
365
+ * On 300 dpi printer: circle with ~208 pixel radius
366
+ * On 1200 dpi imagesetter: circle with ~833 pixel radius
367
+
368
+ The physical size (approximately 0.69 inches diameter) remains constant.
369
+ ====
370
+
371
+ == Paint Order
372
+
373
+ === General
374
+
375
+ PostScript follows a painter's algorithm: later operations paint over earlier
376
+ ones.
377
+
378
+ [example]
379
+ ====
380
+ [source,postscript]
381
+ ----
382
+ % Draw red square
383
+ newpath
384
+ 0 0 moveto 100 0 lineto 100 100 lineto 0 100 lineto closepath
385
+ 1 0 0 setrgbcolor
386
+ fill
387
+
388
+ % Draw blue circle - will overlap red square
389
+ newpath
390
+ 50 50 30 0 360 arc
391
+ 0 0 1 setrgbcolor
392
+ fill
393
+ ----
394
+
395
+ The blue circle appears on top of the red square because it was drawn later.
396
+ ====
397
+
398
+ == See Also
399
+
400
+ * link:fundamentals.adoc[Language Fundamentals] - PostScript language basics
401
+ * link:operators/path-construction.adoc[Path Construction] - Building paths
402
+ * link:operators/painting.adoc[Painting Operators] - Stroking and filling
403
+ * link:operators/graphics-state.adoc[Graphics State] - State management
404
+ * link:operators/transformations.adoc[Transformations] - Coordinate transforms
405
+ * link:svg-mapping.adoc[SVG Mapping] - How PostScript maps to SVG
406
+ * link:index.adoc[Back to PostScript Quick Reference]
@@ -0,0 +1,314 @@
1
+ = Postsvg Implementation Notes
2
+
3
+ == General
4
+
5
+ This document describes implementation-specific details of the Postsvg library,
6
+ including supported features, current limitations, architecture overview, and
7
+ testing approach.
8
+
9
+ == Supported Features
10
+
11
+ === Fully implemented operators
12
+
13
+ ==== Path construction
14
+
15
+ * `newpath` - Initialize empty path
16
+ * `moveto` - Begin new subpath
17
+ * `lineto` - Append straight line
18
+ * `curveto` - Append cubic Bézier curve
19
+ * `closepath` - Close current subpath
20
+
21
+ ==== Painting
22
+
23
+ * `stroke` - Stroke current path
24
+ * `fill` - Fill current path with non-zero winding rule
25
+
26
+ ==== Graphics state
27
+
28
+ * `gsave` - Save graphics state
29
+ * `grestore` - Restore graphics state
30
+ * `setlinewidth` - Set line width
31
+ * `setrgbcolor` - Set RGB color
32
+ * `setgray` - Set grayscale color
33
+
34
+ ==== Transformations
35
+
36
+ * `translate` - Translate coordinate system
37
+ * `scale` - Scale coordinate system
38
+ * `rotate` - Rotate coordinate system
39
+
40
+ ==== Stack and arithmetic
41
+
42
+ * Basic stack operations: `dup`, `pop`, `exch`
43
+ * Arithmetic operators: `add`, `sub`, `mul`, `div`
44
+
45
+ === Partially implemented
46
+
47
+ ==== Path construction
48
+
49
+ * `arc` - Basic circular arc support (counterclockwise)
50
+ * `arcn` - Basic circular arc support (clockwise)
51
+
52
+ Limited to simple cases; complex arc operations may not work correctly.
53
+
54
+ ==== Control flow
55
+
56
+ * `if`, `ifelse` - Basic conditionals
57
+ * `for`, `repeat` - Basic loops
58
+
59
+ Limited support for procedures; complex control structures may fail.
60
+
61
+ == Current Limitations
62
+
63
+ === Not yet implemented
64
+
65
+ ==== Text operations
66
+
67
+ * `show` - Show text string
68
+ * `findfont` - Find font
69
+ * `setfont` - Set current font
70
+ * `scalefont` - Scale font
71
+
72
+ Text in PostScript files is ignored.
73
+
74
+ ==== Image operations
75
+
76
+ * `image` - Paint sampled image
77
+ * `imagemask` - Paint image mask
78
+
79
+ Images are not supported.
80
+
81
+ ==== Pattern and gradient operations
82
+
83
+ * `makepattern` - Create pattern
84
+ * `setpattern` - Set pattern as color
85
+
86
+ Patterns and gradients are not supported.
87
+
88
+ ==== Advanced graphics state
89
+
90
+ * `setlinecap` - Set line cap style
91
+ * `setlinejoin` - Set line join style
92
+ * `setdash` - Set dash pattern
93
+ * `clip`, `eoclip` - Clipping paths
94
+
95
+ These operators are recognized but may not affect output.
96
+
97
+ ==== Dictionary and scope
98
+
99
+ * Limited dictionary support
100
+ * Procedure definitions work only in simple cases
101
+
102
+ === Known issues
103
+
104
+ ==== Arc conversion
105
+
106
+ Arcs are converted to line segments rather than SVG arc commands in some cases,
107
+ which may increase output size.
108
+
109
+ ==== Transformation accuracy
110
+
111
+ Complex transformation sequences may accumulate floating-point errors.
112
+
113
+ ==== Color precision
114
+
115
+ Colors are converted to 8-bit RGB hex values, potentially losing precision from
116
+ PostScript's floating-point representation.
117
+
118
+ == Architecture Overview
119
+
120
+ === Component diagram
121
+
122
+ [source]
123
+ ----
124
+ ┌────────────────────────────────────────┐
125
+ │ Postsvg Module │
126
+ │ │
127
+ │ convert(ps_content) → svg_string │
128
+ │ convert_file(input, output) │
129
+ └────────────┬───────────────────────────┘
130
+
131
+ ┌───────┴────────┐
132
+ │ │
133
+ ┌────▼─────┐ ┌─────▼────────┐
134
+ │ Parser │ │ Converter │
135
+ │ │ │ │
136
+ │ Parslet │───>│ Interpreter │
137
+ │ Grammar │ │ │
138
+ └──────────┘ └──────┬───────┘
139
+
140
+ ┌────────┴────────┐
141
+ │ │
142
+ ┌─────▼──────┐ ┌──────▼────────┐
143
+ │ Graphics │ │ SVG │
144
+ │ State │──>│ Generator │
145
+ └────────────┘ └───────────────┘
146
+ ----
147
+
148
+ === Parser (Parslet-based)
149
+
150
+ Location:: `lib/postsvg/parser/postscript_parser.rb`
151
+ Purpose:: Tokenizes and parses PostScript syntax
152
+
153
+ The parser uses Parslet, a PEG (Parsing Expression Grammar) library, to build
154
+ an abstract syntax tree from PostScript source code.
155
+
156
+ === Converter (Interpreter)
157
+
158
+ Location:: `lib/postsvg/converter.rb`
159
+ Purpose:: Interprets PostScript commands
160
+
161
+ The converter maintains:
162
+
163
+ * Operand stack for PostScript execution
164
+ * Dictionary for variable storage
165
+ * Graphics state object
166
+
167
+ === Graphics State
168
+
169
+ Location:: `lib/postsvg/graphics_state.rb`
170
+ Purpose:: Tracks graphics parameters
171
+
172
+ Maintains:
173
+
174
+ * Current path segments
175
+ * Fill and stroke colors
176
+ * Line width
177
+ * Transformation matrix
178
+ * Graphics state stack
179
+
180
+ === SVG Generator
181
+
182
+ Location:: `lib/postsvg/svg_generator.rb`
183
+ Purpose:: Generates SVG output
184
+
185
+ Converts the interpreted graphics operations into SVG XML format.
186
+
187
+ == Testing Approach
188
+
189
+ === Test organization
190
+
191
+ [source]
192
+ ----
193
+ spec/
194
+ ├── postsvg_spec.rb # Integration tests
195
+ ├── fixtures/
196
+ │ ├── ps2svg/ # Reference files from ps2svg
197
+ │ │ ├── colors.ps
198
+ │ │ ├── colors_expected.svg
199
+ │ │ └── ...
200
+ │ └── eps2svg/ # Reference files from vectory
201
+ │ ├── img.eps
202
+ │ └── ref.svg
203
+ └── postsvg/
204
+ ├── parser_spec.rb # Parser unit tests
205
+ ├── graphics_state_spec.rb # Graphics state tests
206
+ ├── converter_spec.rb # Converter tests (TBD)
207
+ └── svg_generator_spec.rb # Generator tests (TBD)
208
+ ----
209
+
210
+ === Reference test suites
211
+
212
+ Postsvg uses test fixtures from two sources:
213
+
214
+ 1. **ps2svg TypeScript project** - Comprehensive PostScript test cases
215
+ 2. **vectory gem** - Real-world EPS/PS files
216
+
217
+ Tests verify that Postsvg produces equivalent SVG output to these reference
218
+ implementations.
219
+
220
+ == Usage Patterns
221
+
222
+ === Basic conversion
223
+
224
+ [source,ruby]
225
+ ----
226
+ require 'postsvg'
227
+
228
+ # Convert PostScript string to SVG
229
+ ps_content = File.read('input.ps')
230
+ svg_output = Postsvg.convert(ps_content)
231
+
232
+ # Save result
233
+ File.write('output.svg', svg_output)
234
+ ----
235
+
236
+ === File conversion
237
+
238
+ [source,ruby]
239
+ ----
240
+ # Convert file directly
241
+ Postsvg.convert_file('input.eps', 'output.svg')
242
+
243
+ # Or get SVG without saving
244
+ svg = Postsvg.convert_file('input.ps')
245
+ ----
246
+
247
+ === Error handling
248
+
249
+ [source,ruby]
250
+ ----
251
+ begin
252
+ svg = Postsvg.convert(ps_content)
253
+ rescue Postsvg::ParseError => e
254
+ puts "Parse error: #{e.message}"
255
+ rescue Postsvg::ConversionError => e
256
+ puts "Conversion error: #{e.message}"
257
+ end
258
+ ----
259
+
260
+ == Performance Considerations
261
+
262
+ === Memory usage
263
+
264
+ The converter maintains the entire graphics state in memory. Large PostScript
265
+ files with many path operations may consume significant memory.
266
+
267
+ === Processing speed
268
+
269
+ Parslet-based parsing is slower than hand-written parsers but provides better
270
+ error handling and maintainability.
271
+
272
+ For batch conversion of many files, consider parallel processing.
273
+
274
+ === Output size
275
+
276
+ SVG output may be larger than the original PostScript for files with:
277
+
278
+ * Many small line segments
279
+ * Complex curves converted to multiple segments
280
+ * Repeated path operations
281
+
282
+ == Future Enhancements
283
+
284
+ === Planned features
285
+
286
+ * Complete arc implementation using SVG arc commands
287
+ * Text rendering support
288
+ * Clipping path support
289
+ * Dash pattern support
290
+ * Better procedure handling
291
+
292
+ === Architectural improvements
293
+
294
+ * Incremental SVG generation to reduce memory usage
295
+ * Optional path simplification
296
+ * Caching of repeated patterns
297
+ * Better error recovery
298
+
299
+ == Contributing
300
+
301
+ When contributing to Postsvg:
302
+
303
+ 1. Follow Ribose Ruby coding standards
304
+ 2. Add tests for new operators
305
+ 3. Update this documentation
306
+ 4. Ensure all tests pass: `bundle exec rspec`
307
+ 5. Run linter: `bundle exec rubocop`
308
+
309
+ == See Also
310
+
311
+ * link:fundamentals.adoc[PostScript Fundamentals]
312
+ * link:svg-mapping.adoc[SVG Mapping]
313
+ * link:../../README.adoc[Postsvg README]
314
+ * link:index.adoc[Back to PostScript Quick Reference]