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,58 @@
|
|
|
1
|
+
# 10 — PsVisitor (OCP dispatch)
|
|
2
|
+
|
|
3
|
+
## Status: [x]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
The legacy `Interpreter#execute_operator` is a 50-arm `case op`
|
|
8
|
+
statement. Adding a new operator means editing the statement; the OCP
|
|
9
|
+
forbids this. The new visitor uses Ruby's `Module#register` /
|
|
10
|
+
`Object#const_get` pattern (mirrors `emfsvg`'s `HANDLERS` hash but
|
|
11
|
+
uses the model's own double-dispatch so adding an operator is one
|
|
12
|
+
class).
|
|
13
|
+
|
|
14
|
+
## Tasks
|
|
15
|
+
|
|
16
|
+
- [x] `Postsvg::Visitors::PsVisitor` — `accept`s `Model::Operator`
|
|
17
|
+
subclasses via `op.accept(self, ctx)`. Each operator implements
|
|
18
|
+
`accept` to call `visitor.visit_<name>(self, ctx)`.
|
|
19
|
+
- [x] Visitor has one `visit_*` method per operator. Each lives in its
|
|
20
|
+
own file under `lib/postsvg/visitors/ps_visitor/<category>.rb` so
|
|
21
|
+
adding a category is a new file, not an edit.
|
|
22
|
+
- [x] Unknown operators (`Model::UnknownOperator`) hit
|
|
23
|
+
`visit_unknown`, which emits an XML comment
|
|
24
|
+
`<!-- unhandled: NAME -->` in verbose mode and is a no-op otherwise.
|
|
25
|
+
|
|
26
|
+
## Adding a new operator (worked example)
|
|
27
|
+
|
|
28
|
+
Suppose we want to add `setdash` (already exists; this is just the
|
|
29
|
+
shape of the work):
|
|
30
|
+
|
|
31
|
+
1. Create `lib/postsvg/model/operators/graphics_state/setdash.rb`:
|
|
32
|
+
```ruby
|
|
33
|
+
class Postsvg::Model::Operators::SetDash < Postsvg::Model::Operator
|
|
34
|
+
keyword "setdash"
|
|
35
|
+
def self.from_operands(stack)
|
|
36
|
+
offset = stack.pop_number
|
|
37
|
+
pattern = stack.pop_array
|
|
38
|
+
new(pattern: pattern, offset: offset)
|
|
39
|
+
end
|
|
40
|
+
attr_reader :pattern, :offset
|
|
41
|
+
def accept(visitor, ctx); visitor.visit_setdash(self, ctx); end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
2. Register in `lib/postsvg/model/operators/graphics_state.rb`:
|
|
45
|
+
`autoload :SetDash, "postsvg/model/operators/graphics_state/setdash"`.
|
|
46
|
+
3. Register keyword in
|
|
47
|
+
`lib/postsvg/model/operators/registry.rb`:
|
|
48
|
+
`"setdash" => SetDash`.
|
|
49
|
+
4. Implement `visit_setdash` in
|
|
50
|
+
`lib/postsvg/visitors/ps_visitor/graphics_state.rb`.
|
|
51
|
+
|
|
52
|
+
No existing visitor method changes. No `case` statement edit.
|
|
53
|
+
|
|
54
|
+
## Performance note
|
|
55
|
+
|
|
56
|
+
Method dispatch on real Ruby classes is faster than `case op` string
|
|
57
|
+
matching. The visitor will be measurably faster than the legacy
|
|
58
|
+
interpreter on large files.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# 11 — Comprehensive PS Operator Coverage
|
|
2
|
+
|
|
3
|
+
## Status: [~] (P0 subset complete; P1/P2/P3 ongoing)
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This file is the master list of PS operators we cover. Grouped by
|
|
8
|
+
PLRM chapter. Each operator links to its source page in
|
|
9
|
+
`../postscript-guide/docs/commands/<category>/index.adoc`.
|
|
10
|
+
|
|
11
|
+
## Priority P0 (must have for 0.2.0) — DONE
|
|
12
|
+
|
|
13
|
+
### Stack
|
|
14
|
+
- [x] `pop`, `exch`, `dup`, `index`, `roll`, `clear`, `count`
|
|
15
|
+
|
|
16
|
+
### Arithmetic / math
|
|
17
|
+
- [x] `add`, `sub`, `mul`, `div`, `idiv`, `mod`, `neg`, `abs`
|
|
18
|
+
- [x] `ceiling`, `floor`, `round`, `truncate`
|
|
19
|
+
- [x] `sqrt`, `atan`, `cos`, `sin`, `ln`, `log`, `exp`
|
|
20
|
+
|
|
21
|
+
### Boolean / bitwise
|
|
22
|
+
- [x] `eq`, `ne`, `gt`, `ge`, `lt`, `le`
|
|
23
|
+
- [x] `and`, `or`, `xor`, `not`, `bitshift`
|
|
24
|
+
|
|
25
|
+
### Path construction
|
|
26
|
+
- [x] `newpath`, `moveto`, `rmoveto`, `lineto`, `rlineto`
|
|
27
|
+
- [x] `curveto`, `rcurveto`, `arc`, `arcn`, `closepath`, `currentpoint`
|
|
28
|
+
|
|
29
|
+
### Painting
|
|
30
|
+
- [x] `stroke`, `fill`, `eofill`, `clip`, `eoclip`
|
|
31
|
+
|
|
32
|
+
### Color
|
|
33
|
+
- [x] `setgray`, `setrgbcolor`, `setcmykcolor`, `sethsbcolor`
|
|
34
|
+
|
|
35
|
+
### Graphics state
|
|
36
|
+
- [x] `gsave`, `grestore`, `setlinewidth`, `setlinecap`,
|
|
37
|
+
`setlinejoin`, `setmiterlimit`, `setdash`
|
|
38
|
+
|
|
39
|
+
### Transformations
|
|
40
|
+
- [x] `translate`, `scale`, `rotate`, `concat`, `matrix`,
|
|
41
|
+
`currentmatrix`, `setmatrix`, `transform`, `dtransform`,
|
|
42
|
+
`itransform`, `idtransform`
|
|
43
|
+
|
|
44
|
+
### Dictionary / control flow
|
|
45
|
+
- [x] `dict`, `begin`, `end`, `def`, `load`, `store`, `known`, `get`,
|
|
46
|
+
`put`
|
|
47
|
+
- [x] `if`, `ifelse`, `repeat`, `loop`, `for`, `exit`, `quit`,
|
|
48
|
+
`exec`, `stopped`
|
|
49
|
+
|
|
50
|
+
### Device
|
|
51
|
+
- [x] `showpage`, `copypage`, `nulldevice`
|
|
52
|
+
|
|
53
|
+
## Priority P1 (0.3.0)
|
|
54
|
+
|
|
55
|
+
### Strings, arrays, search
|
|
56
|
+
- [ ] `length`, `get`, `put`, `getinterval`, `putinterval`, `search`,
|
|
57
|
+
`token`, `anchorsearch`
|
|
58
|
+
- [ ] `string`, `substring`, `stringwidth`
|
|
59
|
+
|
|
60
|
+
### Type / conversion
|
|
61
|
+
- [ ] `type`, `cvlit`, `cvx`, `xcheck`, `cvs`, `cvn`, `cvr`, `cvi`,
|
|
62
|
+
`cvrs`
|
|
63
|
+
|
|
64
|
+
### Files
|
|
65
|
+
- [ ] `currentfile`, `read`, `readhexstring`, `readstring`, `closefile`,
|
|
66
|
+
`eof`, `flushfile`
|
|
67
|
+
|
|
68
|
+
## Priority P2 (0.4.0) — Fonts and text
|
|
69
|
+
|
|
70
|
+
- [ ] `findfont`, `scalefont`, `setfont`, `show`, `ashow`, `widthshow`,
|
|
71
|
+
`kshow`, `xshow`, `xyshow`, `yshow`, `stringwidth`
|
|
72
|
+
- [ ] `FontDirectory`, `StandardEncoding`, `ISOLatin1Encoding`
|
|
73
|
+
- [ ] `makefont`, `composefont`, `findencoding`
|
|
74
|
+
- [ ] CharPath, Type 3 fonts (rendered via outline procedures)
|
|
75
|
+
|
|
76
|
+
## Priority P3 (full PLRM)
|
|
77
|
+
|
|
78
|
+
### Level 2 / 3
|
|
79
|
+
- [ ] `image`, `colorimage`, `imagemask` — raster imagery
|
|
80
|
+
- [ ] Shading types 1–7 (`shfill`)
|
|
81
|
+
- [ ] Patterns: `makepattern`, `setpattern`
|
|
82
|
+
- [ ] Forms: `BeginData`, `ExecuteForm`
|
|
83
|
+
- [ ] Halftone: `setscreen`, `setcolorscreen`, `sethalftone`
|
|
84
|
+
- [ ] CID fonts, Type 0 (composite) fonts
|
|
85
|
+
- [ ] Filters: ASCIIHexDecode, ASCII85Decode, LZWDecode, FlateDecode,
|
|
86
|
+
RunLengthDecode, SubFileDecode
|
|
87
|
+
- [ ] Binary object format, binary tokens
|
|
88
|
+
- [ ] Page device: `setpagedevice`, `currentpagedevice`
|
|
89
|
+
- [ ] Resource management: `defineresource`, `findresource`,
|
|
90
|
+
`enumerate`, `resourcestatus`, `undefineresource`
|
|
91
|
+
- [ ] `setcolorspace`, `setcolor` (DeviceN, ICCBased)
|
|
92
|
+
|
|
93
|
+
## Acceptance per operator
|
|
94
|
+
|
|
95
|
+
- `Model::Operators::<Name>` class with typed operands.
|
|
96
|
+
- `from_operands(stack)` factory that pops operands and returns the
|
|
97
|
+
instance.
|
|
98
|
+
- `visit_<name>` in `PsVisitor` that mutates the graphics context /
|
|
99
|
+
path builder / svg builder.
|
|
100
|
+
- Spec under `spec/postsvg/model/operators/<name>_spec.rb` covering
|
|
101
|
+
operand parsing and visitor side-effects.
|
|
102
|
+
- Cross-reference in `docs/postscript/operators/<name>.adoc` (or
|
|
103
|
+
point at the existing page in `../postscript-guide`).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 12 — SVG Domain Model (`Postsvg::Svg::*`)
|
|
2
|
+
|
|
3
|
+
## Status: [x]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
The SVG→PS direction needs a typed intermediate, parallel to
|
|
8
|
+
`Postsvg::Model::*` on the PS side. Consuming raw Nokogiri nodes would
|
|
9
|
+
couple every translator to libxml2 quirks; building value objects up
|
|
10
|
+
front gives us:
|
|
11
|
+
|
|
12
|
+
- Stable equality for round-trip tests.
|
|
13
|
+
- A clean place to attach SVG-specific semantics (`paint`,
|
|
14
|
+
`stroke-dasharray`, `clip-path` reference resolution).
|
|
15
|
+
- A registration point for the OCP handler dispatch.
|
|
16
|
+
|
|
17
|
+
The shape is borrowed directly from `emfsvg/lib/emfsvg/svg/*`.
|
|
18
|
+
|
|
19
|
+
## Tasks
|
|
20
|
+
|
|
21
|
+
- [x] `Postsvg::Svg::Parser.call(svg_string) -> Svg::Document`.
|
|
22
|
+
- [x] `Postsvg::Svg::Document` — carries `root_element`, `viewbox`,
|
|
23
|
+
`width`, `height`, `clip_paths` registry (built from `<defs>`).
|
|
24
|
+
- [x] `Postsvg::Svg::Element` — base class with:
|
|
25
|
+
- `ELEMENT_NAME` constant
|
|
26
|
+
- `children`, `clip_path`, `transform`
|
|
27
|
+
- `self.register(name, subclass)` — populates a class-level registry
|
|
28
|
+
- `self.from_node(node)` — dispatches to registered subclass, falls
|
|
29
|
+
back to `OpenElement` so unknown tags descend into children
|
|
30
|
+
- [x] Elements (P0 set):
|
|
31
|
+
- `Svg::Elements::Svg` (root)
|
|
32
|
+
- `Svg::Elements::Group`
|
|
33
|
+
- `Svg::Elements::Path`
|
|
34
|
+
- `Svg::Elements::Rect`
|
|
35
|
+
- `Svg::Elements::Circle`
|
|
36
|
+
- `Svg::Elements::Ellipse`
|
|
37
|
+
- `Svg::Elements::Line`
|
|
38
|
+
- `Svg::Elements::Polyline`
|
|
39
|
+
- `Svg::Elements::Polygon`
|
|
40
|
+
- `Svg::Elements::Text` (+ `Tspan`)
|
|
41
|
+
- `Svg::Elements::Image`
|
|
42
|
+
- `Svg::Elements::Defs`
|
|
43
|
+
- `Svg::Elements::ClipPath`
|
|
44
|
+
- `Svg::Elements::LinearGradient`, `Svg::Elements::RadialGradient`
|
|
45
|
+
- [x] Value objects for cross-cutting concepts:
|
|
46
|
+
- `Svg::Color` — wraps `Postsvg::Color`
|
|
47
|
+
- `Svg::Paint` — solid color, `url(#id)` reference, or `none`
|
|
48
|
+
- `Svg::Stroke` — width, dasharray, dashoffset, linecap, linejoin,
|
|
49
|
+
miterlimit
|
|
50
|
+
- `Svg::PathData::Command` — single SVG path command; `PathData`
|
|
51
|
+
parses the `d` attribute into an array of commands
|
|
52
|
+
- `Svg::TransformList` — parses `transform="..."` into a list of
|
|
53
|
+
`Matrix` instances
|
|
54
|
+
- [x] `Svg::ClipPathRegistry.from_document(doc)` — indexes
|
|
55
|
+
`<clipPath id="…">` definitions by id for handler lookup.
|
|
56
|
+
|
|
57
|
+
## Out of scope
|
|
58
|
+
|
|
59
|
+
- CSS stylesheet parsing. SVG `<style>` blocks are not interpreted;
|
|
60
|
+
presentation attributes only. (Future work; tracked in
|
|
61
|
+
`23-level2-level3.md`.)
|
|
62
|
+
- Animation elements (`<animate>`, `<animateTransform>`). Skipped.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 13 — Translation Handlers (SVG → PS)
|
|
2
|
+
|
|
3
|
+
## Status: [x]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
The SVG → PS direction is the mirror of the PS → SVG renderer. Where
|
|
8
|
+
the renderer walks PS records and emits SVG, the translator walks SVG
|
|
9
|
+
elements and emits PS records. The records are then serialized by
|
|
10
|
+
`Postsvg::Serializer`.
|
|
11
|
+
|
|
12
|
+
The handler-per-element pattern (mirrors `emfsvg`'s
|
|
13
|
+
`Translation::Handlers::*`) keeps the dispatch OCP: new SVG element
|
|
14
|
+
type → new handler class + registration, no switch.
|
|
15
|
+
|
|
16
|
+
## Tasks
|
|
17
|
+
|
|
18
|
+
- [x] `Postsvg::Translation::PsRenderer` — top-level orchestrator.
|
|
19
|
+
Takes `Svg::Document`, returns `Model::Program`.
|
|
20
|
+
- [x] `Postsvg::Translation::HandlerRegistry` — element class →
|
|
21
|
+
handler class. Walks superclass chain so subclassing handlers
|
|
22
|
+
inherits behaviour.
|
|
23
|
+
- [x] `Postsvg::Translation::Context` — carries:
|
|
24
|
+
- `program` (the `Model::Program` being built)
|
|
25
|
+
- `graphics_stack` (mirrors renderer's)
|
|
26
|
+
- `clip_path_registry` (resolved references)
|
|
27
|
+
- `serializer_options` (eps:, page_size:)
|
|
28
|
+
- [x] `Postsvg::Translation::RecordEmitter` — thin helper that appends
|
|
29
|
+
`Model::Operator` instances to `program.body` in source order.
|
|
30
|
+
- [x] Handlers (P0 set):
|
|
31
|
+
- `Handlers::SvgHandler` — emits header: `%%BoundingBox`,
|
|
32
|
+
`newpath`, set up Y-flip
|
|
33
|
+
- `Handlers::GroupHandler` — `gsave`, transform concat, descend,
|
|
34
|
+
`grestore`
|
|
35
|
+
- `Handlers::PathHandler` — converts `PathData` commands to PS
|
|
36
|
+
`moveto`/`lineto`/`curveto`/`closepath`, then `stroke`/`fill`
|
|
37
|
+
based on `paint-*` attributes
|
|
38
|
+
- `Handlers::RectHandler` — synthesizes path: `newpath`,
|
|
39
|
+
`x y moveto`, `w 0 rlineto`, `0 h rlineto`, `-w 0 rlineto`,
|
|
40
|
+
`closepath`
|
|
41
|
+
- `Handlers::CircleHandler` — `arc`
|
|
42
|
+
- `Handlers::EllipseHandler` — `scale` + `arc` + restore
|
|
43
|
+
- `Handlers::LineHandler` — `moveto`, `lineto`, `stroke`
|
|
44
|
+
- `Handlers::PolylineHandler` / `Handlers::PolygonHandler` —
|
|
45
|
+
walk points
|
|
46
|
+
- `Handlers::TextHandler` — `findfont`/`scalefont`/`setfont`,
|
|
47
|
+
`moveto`, `show`
|
|
48
|
+
- `Handlers::ImageHandler` — emit `image` with `datasource`
|
|
49
|
+
(PNG bytes hex-encoded; full image support is P2)
|
|
50
|
+
- `Handlers::DefsHandler` — registers but does not paint
|
|
51
|
+
- `Handlers::ClipPathHandler` — defines clip via `clippath`/`clip`
|
|
52
|
+
|
|
53
|
+
## Handler contract
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class Handlers::PathHandler
|
|
57
|
+
def self.call(element, context)
|
|
58
|
+
# 1. Update graphics context from element's paint/stroke/transform.
|
|
59
|
+
# 2. Emit Model records (moveto/lineto/...).
|
|
60
|
+
# 3. Emit terminal operator (stroke/fill/clip).
|
|
61
|
+
# 4. Recurse into children if any.
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Handlers never touch the serializer directly. They build `Model::*`
|
|
67
|
+
records; the serializer consumes them later. This keeps the model the
|
|
68
|
+
single source of truth and enables future PS → PS transformations
|
|
69
|
+
(optimization, validation) without re-parsing.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 14 — PS Serializer
|
|
2
|
+
|
|
3
|
+
## Status: [x]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
The SVG→PS direction's last step. Walks a `Model::Program` and emits
|
|
8
|
+
PostScript source text. Also used internally by tests for
|
|
9
|
+
pretty-printing.
|
|
10
|
+
|
|
11
|
+
## Tasks
|
|
12
|
+
|
|
13
|
+
- [x] `Postsvg::Serializer.call(program, eps: false) -> String`.
|
|
14
|
+
- [x] Output structure:
|
|
15
|
+
```
|
|
16
|
+
%!PS-Adobe-3.0 [<eps: EPSF-3.0>]
|
|
17
|
+
%%Creator: Postsvg <version>
|
|
18
|
+
%%BoundingBox: LLX LLY URX URY
|
|
19
|
+
%%HiResBoundingBox: ... (when sub-integer precision matters)
|
|
20
|
+
%%EndComments
|
|
21
|
+
<body: literal/procedure/operator output>
|
|
22
|
+
showpage
|
|
23
|
+
%%EOF
|
|
24
|
+
```
|
|
25
|
+
- [x] BoundingBox computed by walking the program's path operations
|
|
26
|
+
with the current CTM (a second lightweight pass; the renderer does
|
|
27
|
+
the same walk for SVG viewbox sizing).
|
|
28
|
+
- [x] Operator serialization: each `Model::Operator` knows how to emit
|
|
29
|
+
itself via `Serializer::emit_<name>(op, io)`. Same OCP shape as the
|
|
30
|
+
visitor: a new operator adds a method, no switch edits.
|
|
31
|
+
- [x] String literal escaping: `( )`, `\\`, `\n`, `\r`, `\t`, octal
|
|
32
|
+
for non-printables. Mirrors PLRM §3.2.2.
|
|
33
|
+
- [x] Hex string alternative for binary data: `<DEADBEEF>`.
|
|
34
|
+
- [x] Number formatting through `FormatNumber`.
|
|
35
|
+
- [x] `eps: true` adds the `EPSF-3.0` header and a `%%EndProlog` /
|
|
36
|
+
`%%Page` block.
|
|
37
|
+
|
|
38
|
+
## Round-trip guarantee
|
|
39
|
+
|
|
40
|
+
For the supported subset, `Serializer.call(Parser.parse(
|
|
41
|
+
Lexer.tokenize(ps)))` produces output that re-parses to an equivalent
|
|
42
|
+
`Model::Program` (modulo formatting). This is the contract the
|
|
43
|
+
round-trip spec asserts.
|
|
44
|
+
|
|
45
|
+
## Out of scope
|
|
46
|
+
|
|
47
|
+
- Token-stream preservation (comments, whitespace) is not preserved
|
|
48
|
+
across round-trip. Input formatting is normalized.
|
|
49
|
+
- Binary PostScript (Level 2 binary object format). Future P3 work.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 15 — CLI + Public API
|
|
2
|
+
|
|
3
|
+
## Status: [x]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
The public surface needs to be:
|
|
8
|
+
|
|
9
|
+
1. Symmetric: `to_svg` / `to_ps` / `to_eps` — clear direction in the
|
|
10
|
+
name.
|
|
11
|
+
2. Predictable: `convert` is a synonym for the original direction
|
|
12
|
+
(PS→SVG) for backwards compatibility, but new code uses the
|
|
13
|
+
directional names.
|
|
14
|
+
3. Discoverable: CLI `--help` shows every command.
|
|
15
|
+
|
|
16
|
+
## Tasks
|
|
17
|
+
|
|
18
|
+
- [x] Public API (in `lib/postsvg.rb`):
|
|
19
|
+
```ruby
|
|
20
|
+
Postsvg.to_svg(ps_or_eps_string, **opts) # PS/EPS → SVG
|
|
21
|
+
Postsvg.to_svg_file(input_path, output_path=nil, **opts)
|
|
22
|
+
Postsvg.convert(...) # alias for to_svg (BC)
|
|
23
|
+
Postsvg.convert_file(...) # alias (BC)
|
|
24
|
+
|
|
25
|
+
Postsvg.to_ps(svg_string, eps: false, **opts) # SVG → PS
|
|
26
|
+
Postsvg.to_eps(svg_string, **opts) # SVG → EPS
|
|
27
|
+
Postsvg.to_ps_file(input_path, output_path=nil, **opts)
|
|
28
|
+
```
|
|
29
|
+
- [x] `Postsvg::Options` frozen struct shared by both directions:
|
|
30
|
+
`verbose:`, `width:`, `height:`, `viewbox_override:`, `eps:`.
|
|
31
|
+
- [x] CLI (Thor):
|
|
32
|
+
- `postsvg convert INPUT [OUTPUT]` — PS/EPS → SVG (BC)
|
|
33
|
+
- `postsvg to-svg INPUT [OUTPUT]` — explicit alias
|
|
34
|
+
- `postsvg to-ps INPUT [OUTPUT]` — SVG → PS
|
|
35
|
+
- `postsvg to-eps INPUT [OUTPUT]` — SVG → EPS
|
|
36
|
+
- `postsvg batch INPUT_DIR [OUTPUT_DIR]` — auto-detects by extension
|
|
37
|
+
- `postsvg version`
|
|
38
|
+
- [x] Auto-detection in `batch`: `.ps`/`.eps` → SVG; `.svg` → PS.
|
|
39
|
+
- [x] CLI exit codes: 0 success, 1 usage error, 2 conversion error.
|
|
40
|
+
|
|
41
|
+
## Deprecation path
|
|
42
|
+
|
|
43
|
+
- `Postsvg.convert` and `Postsvg.convert_file` remain as aliases. No
|
|
44
|
+
removal in 0.2; revisit at 1.0.
|
|
45
|
+
- The dormant `Postsvg::Converter` class is no longer autoloaded.
|
|
46
|
+
Callers needing it can `require "postsvg/legacy/converter"`
|
|
47
|
+
explicitly.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# 16 — Specs
|
|
2
|
+
|
|
3
|
+
## Status: [~]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Per the global rule: "Good specs throughout. Every public method
|
|
8
|
+
should have specs. Every behavioral edge case should be covered.
|
|
9
|
+
Specs use real model instances — never doubles."
|
|
10
|
+
|
|
11
|
+
## Layout
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
spec/
|
|
15
|
+
├── spec_helper.rb
|
|
16
|
+
├── postsvg_spec.rb # public API contract
|
|
17
|
+
├── postsvg/
|
|
18
|
+
│ ├── lexer_spec.rb # token-by-token
|
|
19
|
+
│ ├── parser_spec.rb # AST shape, DSC capture
|
|
20
|
+
│ ├── model/
|
|
21
|
+
│ │ ├── program_spec.rb
|
|
22
|
+
│ │ ├── token_spec.rb
|
|
23
|
+
│ │ └── operators/
|
|
24
|
+
│ │ ├── stack_spec.rb
|
|
25
|
+
│ │ ├── arithmetic_spec.rb
|
|
26
|
+
│ │ ├── path_spec.rb
|
|
27
|
+
│ │ └── ... # one per operator category
|
|
28
|
+
│ ├── graphics_context_spec.rb
|
|
29
|
+
│ ├── matrix_spec.rb
|
|
30
|
+
│ ├── color_spec.rb
|
|
31
|
+
│ ├── svg_builder_spec.rb
|
|
32
|
+
│ ├── renderer_spec.rb # PS → SVG
|
|
33
|
+
│ ├── visitors/
|
|
34
|
+
│ │ └── ps_visitor_spec.rb
|
|
35
|
+
│ ├── svg/
|
|
36
|
+
│ │ ├── parser_spec.rb
|
|
37
|
+
│ │ ├── document_spec.rb
|
|
38
|
+
│ │ └── elements/
|
|
39
|
+
│ │ └── *_spec.rb
|
|
40
|
+
│ ├── translation/
|
|
41
|
+
│ │ ├── handler_registry_spec.rb
|
|
42
|
+
│ │ ├── ps_renderer_spec.rb
|
|
43
|
+
│ │ └── handlers/
|
|
44
|
+
│ │ └── *_spec.rb
|
|
45
|
+
│ ├── serializer_spec.rb
|
|
46
|
+
│ ├── cli_spec.rb
|
|
47
|
+
│ └── integration_spec.rb # full-pipeline + fixtures
|
|
48
|
+
├── round_trip_spec.rb # SVG → PS → SVG
|
|
49
|
+
└── fixtures/
|
|
50
|
+
├── ps2svg/ # existing fixtures (kept)
|
|
51
|
+
├── eps2svg/ # existing fixtures (kept)
|
|
52
|
+
├── svg2ps/ # new: SVG → PS inputs + expected
|
|
53
|
+
└── round_trip/ # new: SVG ↔ PS pairs
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tasks
|
|
57
|
+
|
|
58
|
+
- [x] Each `Model::Operators::*` class has a spec for:
|
|
59
|
+
- `from_operands` parsing (correct pops, error on stack underflow)
|
|
60
|
+
- value equality
|
|
61
|
+
- [x] Each `Svg::Elements::*` class has a spec for `from_node`
|
|
62
|
+
(parses attributes correctly, drops nothing silently).
|
|
63
|
+
- [x] Each `Translation::Handlers::*` has a spec that builds a minimal
|
|
64
|
+
SVG element, runs the handler against a real `Context` (no double),
|
|
65
|
+
and asserts on the emitted `Model::Program` records.
|
|
66
|
+
- [x] `SvgBuilder` spec covers dedup (two identical clipPaths → one
|
|
67
|
+
`<defs>` entry) and XML escaping.
|
|
68
|
+
- [x] `Serializer` spec round-trips: serialize → lex → parse →
|
|
69
|
+
equality with original `Model::Program`.
|
|
70
|
+
- [x] `Renderer` spec covers PS→SVG for each fixture in
|
|
71
|
+
`spec/fixtures/ps2svg/` and `spec/fixtures/eps2svg/`.
|
|
72
|
+
- [x] `Cli` spec uses `StringIO` capture of stdout; writes inputs to
|
|
73
|
+
`Dir.tmpdir`; cleans up.
|
|
74
|
+
- [x] `Round-trip` spec: for each SVG in `spec/fixtures/round_trip/`,
|
|
75
|
+
`to_ps` then `to_svg` and assert geometric equivalence (within 1px
|
|
76
|
+
tolerance; uses `Canon` matcher configured in spec_helper).
|
|
77
|
+
|
|
78
|
+
## Anti-patterns banned
|
|
79
|
+
|
|
80
|
+
- `double(...)`, `instance_double(...)`, `allow_any_instance_of`.
|
|
81
|
+
- Mocking the file system with `FakeFS`. Use `Dir.tmpdir`.
|
|
82
|
+
- Mocking time. Pass `Time.at(fixed)` to constructors that need it.
|
|
83
|
+
- `subject { described_class }` with implicit receiver — explicit
|
|
84
|
+
`expect(described_class.foo).to ...`.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 17 — Docs Sync
|
|
2
|
+
|
|
3
|
+
## Status: [ ]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
The README, `docs/architecture.adoc`, and most of `docs/api-reference`
|
|
8
|
+
currently describe the dormant Parslet pipeline. The CLAUDE.md flags
|
|
9
|
+
this; we need to either:
|
|
10
|
+
|
|
11
|
+
(a) Update them to describe the live pipeline (preferred).
|
|
12
|
+
(b) Clearly mark them as describing a planned pipeline.
|
|
13
|
+
|
|
14
|
+
(a) wins because users actually consult these docs.
|
|
15
|
+
|
|
16
|
+
## Tasks
|
|
17
|
+
|
|
18
|
+
- [ ] README.adoc:
|
|
19
|
+
- Replace "three-stage architecture" diagram with the new pipeline
|
|
20
|
+
diagram (from `00-architecture.md`).
|
|
21
|
+
- Add `Postsvg.to_ps` / `Postsvg.to_eps` examples.
|
|
22
|
+
- Update CLI examples to include `to-ps`, `to-eps`.
|
|
23
|
+
- Update limitations to reflect current actual coverage.
|
|
24
|
+
- [ ] docs/architecture.adoc:
|
|
25
|
+
- Describe Lexer / Parser / Model / Renderer / Visitor / SvgBuilder.
|
|
26
|
+
- Describe Svg::* / Translation / Serializer.
|
|
27
|
+
- Add the "MECE responsibility split" table from
|
|
28
|
+
`00-architecture.md`.
|
|
29
|
+
- [ ] docs/api-reference.adoc:
|
|
30
|
+
- Per-method yardoc-style entries for the public API.
|
|
31
|
+
- [ ] docs/cli-reference.adoc:
|
|
32
|
+
- All commands; auto-detected batch.
|
|
33
|
+
- [ ] docs/postscript/svg-mapping.adoc:
|
|
34
|
+
- How each PS operator maps to SVG (one row per operator).
|
|
35
|
+
- [ ] docs/postscript/implementation-notes.adoc:
|
|
36
|
+
- The design choices in `00-architecture.md` summarized for users.
|
|
37
|
+
- [ ] docs/CHANGELOG.md:
|
|
38
|
+
- 0.2.0 entry with new SVG → PS / EPS direction and breaking
|
|
39
|
+
changes (if any).
|
|
40
|
+
|
|
41
|
+
## What NOT to touch
|
|
42
|
+
|
|
43
|
+
- `docs/postscript/operators/*.adoc` — these are content pages, not
|
|
44
|
+
architecture docs. They stay unless an operator's documented
|
|
45
|
+
behaviour changed (it shouldn't).
|
|
46
|
+
- `CLAUDE.md` — already accurate; update only if the architecture
|
|
47
|
+
diverges from what it says.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 18 — Performance and Determinism
|
|
2
|
+
|
|
3
|
+
## Status: [ ]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Pure-Ruby string manipulation can be slow. The legacy `Interpreter`
|
|
8
|
+
allocates a new `PathBuilder` on every `reset`; the new pipeline can
|
|
9
|
+
do better. Determinism matters for round-trip tests and for
|
|
10
|
+
reproducible builds.
|
|
11
|
+
|
|
12
|
+
## Tasks
|
|
13
|
+
|
|
14
|
+
- [ ] **Frozen-string literals everywhere.** Add `# frozen_string_literal: true` to every `.rb` file.
|
|
15
|
+
- [ ] **`FormatNumber`** is the single source for float formatting.
|
|
16
|
+
Profile with `benchmark-ips` against representative PS files
|
|
17
|
+
(`spec/fixtures/ps2svg/example_full.ps` is the heaviest).
|
|
18
|
+
- [ ] **`SvgBuilder`** uses a single `String` accumulator (`String#<<`).
|
|
19
|
+
Avoid `Array#join`.
|
|
20
|
+
- [ ] **Dedup registry** uses an interned `String` key (frozen) so
|
|
21
|
+
hash lookup is O(1) with no allocation.
|
|
22
|
+
- [ ] **Matrix multiplication** is inlined in `Matrix#multiply` (already
|
|
23
|
+
the case) — no object allocation per component.
|
|
24
|
+
- [ ] **Procedure invocation** uses an explicit depth counter rather
|
|
25
|
+
than relying on Ruby's stack (which would `SystemStackError` on
|
|
26
|
+
pathological inputs at ~3000 deep).
|
|
27
|
+
- [ ] **GC pressure**: pass `Model::Operator` instances by reference,
|
|
28
|
+
never deep-copy them between passes.
|
|
29
|
+
- [ ] **Profiling harness**: `scripts/profile.rb INPUT.ps` runs the
|
|
30
|
+
pipeline under `stackprof` and prints the top 20 hotspots. Useful
|
|
31
|
+
for regression detection.
|
|
32
|
+
- [ ] **Memory bound**: `Renderer::MAX_OUTPUT_BYTES` (100 MB default,
|
|
33
|
+
configurable). Raise `RenderError` past this.
|
|
34
|
+
|
|
35
|
+
## Determinism checklist
|
|
36
|
+
|
|
37
|
+
- Two runs of the same input → byte-equal output.
|
|
38
|
+
- IDs (`clip1`, `grad1`, `pattern1`) start at 1 on every
|
|
39
|
+
`SvgBuilder.new`.
|
|
40
|
+
- No `Time.now`, no `SecureRandom`, no `Object#object_id` in IDs.
|
|
41
|
+
- No iteration over `Hash` whose order depends on insertion of
|
|
42
|
+
non-deterministic keys.
|
|
43
|
+
|
|
44
|
+
## Out of scope
|
|
45
|
+
|
|
46
|
+
- Streaming output (incremental SVG emission for huge files). Future
|
|
47
|
+
work; would require SvgBuilder to expose an `each_chunk` enumerator.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 19 — Error Model
|
|
2
|
+
|
|
3
|
+
## Status: [ ]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Today there is one error class (`Postsvg::Error`) plus three trivial
|
|
8
|
+
subclasses. Operators silently swallow bad operands
|
|
9
|
+
(`safe_pop_number(0)` returns 0). This makes debugging impossible and
|
|
10
|
+
makes the gem unsafe for untrusted input.
|
|
11
|
+
|
|
12
|
+
## Tasks
|
|
13
|
+
|
|
14
|
+
- [ ] Typed errors, one per failure mode:
|
|
15
|
+
- `Postsvg::ParseError` — lexer/parser failed.
|
|
16
|
+
- `LexError < ParseError` — bad character / unterminated string.
|
|
17
|
+
- `SyntaxError < ParseError` — unbalanced braces, missing operands.
|
|
18
|
+
- `Postsvg::RenderError` — PS → SVG execution failed.
|
|
19
|
+
- `StackUnderflowError < RenderError`.
|
|
20
|
+
- `UndefinedOperatorError < RenderError` (vs. `UnknownOperator`,
|
|
21
|
+
which is recoverable).
|
|
22
|
+
- `RecursionLimitError < RenderError`.
|
|
23
|
+
- `Postsvg::TranslationError` — SVG → PS failed.
|
|
24
|
+
- `UnsupportedElementError < TranslationError`.
|
|
25
|
+
- `UnresolvedReferenceError < TranslationError` (`url(#missing)`).
|
|
26
|
+
- `Postsvg::SerializeError` — Model → PS failed.
|
|
27
|
+
- `Postsvg::SizeLimitError < RenderError` — exceeded
|
|
28
|
+
`MAX_OUTPUT_BYTES`.
|
|
29
|
+
- [ ] All errors carry:
|
|
30
|
+
- `source_position` (line, column) when known
|
|
31
|
+
- `operator_name` when applicable
|
|
32
|
+
- `original` (wrapped exception) when applicable
|
|
33
|
+
- [ ] No `rescue StandardError` blanket catches in the public API.
|
|
34
|
+
Catch specific errors; re-raise everything else.
|
|
35
|
+
- [ ] Legacy `safe_pop_number(default)` is removed; underflow raises
|
|
36
|
+
`StackUnderflowError`. Operators that legitimately handle missing
|
|
37
|
+
operands (e.g. optional dictionary args) use explicit `stack.maybe_pop`.
|
|
38
|
+
|
|
39
|
+
## Why typed errors
|
|
40
|
+
|
|
41
|
+
- Users can catch the specific failure they care about.
|
|
42
|
+
- Tests can assert `expect { ... }.to raise_error(ParseError)` without
|
|
43
|
+
matching an unrelated crash.
|
|
44
|
+
- The CLI can format different errors differently (syntax error →
|
|
45
|
+
point at file:line; unsupported element → suggest workaround).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 20 — Fonts and Text
|
|
2
|
+
|
|
3
|
+
## Status: [ ]
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
The legacy `Interpreter` emits a stub `<text>` element from `show`,
|
|
8
|
+
which is not even close to correct — PS fonts have metrics, kerning,
|
|
9
|
+
encoding, and often glyph-specific procedures. For the SVG→PS
|
|
10
|
+
direction, mapping `<text>` back to PostScript `show` requires a font
|
|
11
|
+
the target device knows about.
|
|
12
|
+
|
|
13
|
+
## Scope by priority
|
|
14
|
+
|
|
15
|
+
### P2a — Names and basic show (0.4.0)
|
|
16
|
+
|
|
17
|
+
- [ ] Built-in PS font registry: Courier, Helvetica, Times-Roman +
|
|
18
|
+
oblique/bold variants. Map SVG generic families (`serif`,
|
|
19
|
+
`sans-serif`, `monospace`) to these.
|
|
20
|
+
- [ ] `findfont` / `scalefont` / `setfont` parsing in the PS direction.
|
|
21
|
+
- [ ] `show` / `stringwidth` operators (without kerning).
|
|
22
|
+
- [ ] SVG `<text>` → PS: emit `findfont`, `scalefont`, `setfont`,
|
|
23
|
+
`moveto`, `show`.
|
|
24
|
+
|
|
25
|
+
### P2b — Outline text (0.5.0)
|
|
26
|
+
|
|
27
|
+
- [ ] For fonts whose metrics we know (Helvetica, Courier, Times),
|
|
28
|
+
convert `<text>` to outlines (path operations) for fidelity.
|
|
29
|
+
Optional flag: `Postsvg.to_svg(ps, text_mode: :outlines)`.
|
|
30
|
+
- [ ] `charpath` operator.
|
|
31
|
+
|
|
32
|
+
### P3 — Full font machinery
|
|
33
|
+
|
|
34
|
+
- [ ] Type 1 font parsing (`.pfa` / `.pfb`).
|
|
35
|
+
- [ ] Type 3 font procedures (rendered via their PaintProc).
|
|
36
|
+
- [ ] CID fonts (composite, vertical writing modes).
|
|
37
|
+
- [ ] `ashow`, `widthshow`, `kshow`, `xshow`, `xyshow`, `yshow`.
|
|
38
|
+
- [ ] Encoding vectors: `StandardEncoding`, `ISOLatin1Encoding`,
|
|
39
|
+
custom encodings via `defineencoding`.
|
|
40
|
+
|
|
41
|
+
## Reference
|
|
42
|
+
|
|
43
|
+
- `../postscript-guide/docs/commands/font-text/index.adoc` — operator
|
|
44
|
+
reference.
|
|
45
|
+
- `../postscript-guide/docs/usage/advanced/fonts-text.adoc` — tutorial.
|
|
46
|
+
- PLRM Chapter 5 (Fonts) and Chapter 9 (Typesetter's Model).
|