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
data/README.adoc
CHANGED
|
@@ -1,99 +1,115 @@
|
|
|
1
1
|
= Postsvg
|
|
2
2
|
|
|
3
|
-
image:https://img.shields.io/gem/v/postsvg.svg[Gem Version,
|
|
4
|
-
link=https://
|
|
5
|
-
image:https://github.com/metanorma/postsvg/actions/workflows/test.yml/badge.svg[Build Status,
|
|
6
|
-
link=https://github.com/metanorma/postsvg/actions/workflows/test.yml]
|
|
3
|
+
image:https://img.shields.io/gem/v/postsvg.svg[Gem Version,link=https://rubygems.org/gems/postsvg]
|
|
4
|
+
image:https://github.com/metanorma/postsvg/actions/workflows/test.yml/badge.svg[Build Status,link=https://github.com/metanorma/postsvg/actions/workflows/test.yml]
|
|
7
5
|
image:https://img.shields.io/github/license/metanorma/postsvg.svg[License]
|
|
8
6
|
|
|
9
7
|
== Purpose
|
|
10
8
|
|
|
11
|
-
Postsvg is a pure
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Postsvg is a pure-Ruby PostScript (PS) and Encapsulated PostScript (EPS)
|
|
10
|
+
transformer. It supports both directions: *PS → SVG* and *SVG → PS* (and
|
|
11
|
+
*SVG → EPS*). No external tools, no Ghostscript, no Inkscape — the
|
|
12
|
+
entire pipeline runs in Ruby.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Postsvg uses Nokogiri for the SVG parse side and a hand-written
|
|
15
|
+
comment-aware lexer / stack-machine interpreter for the PS source side.
|
|
16
|
+
Output is typed: the same `Postsvg::Model::Program` is produced whether
|
|
17
|
+
the source was parsed from PS or built from SVG, so round-trips are
|
|
18
|
+
trivial.
|
|
19
19
|
|
|
20
20
|
This library is particularly useful for applications that need to:
|
|
21
21
|
|
|
22
|
-
* Convert legacy PostScript
|
|
22
|
+
* Convert legacy PostScript or SVG to the other format in pure Ruby
|
|
23
23
|
* Process vector graphics without external dependencies
|
|
24
|
-
* Generate
|
|
24
|
+
* Generate PostScript from SVG programmatically (publishing pipelines)
|
|
25
25
|
* Work with EPS files in pure Ruby environments
|
|
26
26
|
|
|
27
27
|
== Features
|
|
28
28
|
|
|
29
|
-
* <<
|
|
30
|
-
|
|
31
|
-
* <<
|
|
32
|
-
|
|
33
|
-
* <<
|
|
34
|
-
|
|
35
|
-
* <<architecture,Pure
|
|
36
|
-
|
|
29
|
+
* <<forward-conversion,PS/EPS → SVG conversion>> — Ruby API for
|
|
30
|
+
converting PS or EPS source to SVG
|
|
31
|
+
* <<reverse-conversion,SVG → PS/EPS conversion>> — Ruby API for the
|
|
32
|
+
reverse direction
|
|
33
|
+
* <<cli,Command-line interface>> — Thor-based CLI for file and batch
|
|
34
|
+
conversions in either direction
|
|
35
|
+
* <<architecture,Pure-Ruby architecture>> — No external dependencies
|
|
36
|
+
for PS/EPS, only Nokogiri for SVG
|
|
37
|
+
* <<svg-optimization,SVG optimization>> — Automatic deduplication of
|
|
38
|
+
clipPath elements for efficient output
|
|
39
|
+
* <<round-trip,Round-trip preservation>> — `Postsvg.to_svg(Postsvg.to_ps(svg))`
|
|
40
|
+
for supported SVG elements preserves geometry
|
|
41
|
+
* <<composability,OCP extensible>> — new operators or SVG elements
|
|
42
|
+
are plug-ins, not edits to existing dispatch
|
|
37
43
|
|
|
38
44
|
== Architecture
|
|
39
45
|
|
|
40
46
|
[[architecture]]
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
Postsvg is layered as a typeless surface → a typed model → an
|
|
49
|
+
emitter/serializer. The same `Postsvg::Model::Program` is produced by
|
|
50
|
+
the forward-direction parser and the reverse-direction translator, so
|
|
51
|
+
the same serializer can write it out in either direction.
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
SVG. This design follows separation of concerns principles, with each
|
|
46
|
-
component handling a specific responsibility in the conversion process.
|
|
53
|
+
=== Forward direction (PS/EPS → SVG)
|
|
47
54
|
|
|
48
|
-
.PostScript to SVG conversion pipeline
|
|
49
55
|
[source]
|
|
50
56
|
----
|
|
51
|
-
|
|
52
|
-
│
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* `Postsvg::Parser::PostscriptParser` - Parslet grammar for PostScript
|
|
74
|
-
syntax
|
|
75
|
-
* `Postsvg::Parser::Transform` - Transforms AST into Ruby data
|
|
76
|
-
structures
|
|
77
|
-
|
|
78
|
-
=== Converter stage
|
|
79
|
-
|
|
80
|
-
The converter interprets PostScript commands using a stack-based
|
|
81
|
-
execution model. It maintains an operand stack, dictionary, and graphics
|
|
82
|
-
state while executing PostScript operators.
|
|
57
|
+
PS / EPS source
|
|
58
|
+
│
|
|
59
|
+
▼
|
|
60
|
+
Postsvg::Source::Lexer (state-machine, comment-aware)
|
|
61
|
+
│ tokens
|
|
62
|
+
▼
|
|
63
|
+
Postsvg::Source::AstBuilder (tokens → Model::Program)
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
Postsvg::Model::Program (typed records: Moveto, Stroke, Gsave, …)
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
Postsvg::Renderer ┐
|
|
70
|
+
Postsvg::Visitors::PsVisitor ├── dispatches records, drives
|
|
71
|
+
Postsvg::SvgBuilder ┘ the SvgBuilder
|
|
72
|
+
│
|
|
73
|
+
▼
|
|
74
|
+
SVG string
|
|
75
|
+
----
|
|
76
|
+
|
|
77
|
+
=== Reverse direction (SVG → PS/EPS)
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
[source]
|
|
80
|
+
----
|
|
81
|
+
SVG string
|
|
82
|
+
│
|
|
83
|
+
▼
|
|
84
|
+
Postsvg::Svg::Parser (Nokogiri-backed)
|
|
85
|
+
│ Svg::Document with Element value objects
|
|
86
|
+
▼
|
|
87
|
+
Postsvg::Translation::PsRenderer
|
|
88
|
+
Postsvg::Translation::HandlerRegistry
|
|
89
|
+
Postsvg::Translation::Handlers::* (one per SVG element type)
|
|
90
|
+
│
|
|
91
|
+
▼
|
|
92
|
+
Postsvg::Model::Program (same model as forward direction)
|
|
93
|
+
│
|
|
94
|
+
▼
|
|
95
|
+
Postsvg::Serializer ─▶ PS / EPS source
|
|
96
|
+
----
|
|
97
|
+
|
|
98
|
+
=== MECE responsibility split
|
|
99
|
+
|
|
100
|
+
| Namespace | Owns |
|
|
101
|
+
|------------------------------------|-----------------------------------|
|
|
102
|
+
| `Postsvg::Source` | Lexer, AstBuilder, OperandStack |
|
|
103
|
+
| `Postsvg::Model` | Typed records, value equality |
|
|
104
|
+
| `Postsvg::GraphicsContext`/`Stack`| Immutable graphics-state snapshots|
|
|
105
|
+
| `Postsvg::Matrix`/`Color`/`FormatNumber` | Foundational value types |
|
|
106
|
+
| `Postsvg::SvgBuilder` | Append-only SVG emission |
|
|
107
|
+
| `Postsvg::Renderer` | PS → SVG orchestrator |
|
|
108
|
+
| `Postsvg::Visitors` | Per-operator dispatch |
|
|
109
|
+
| `Postsvg::Svg` | SVG domain model |
|
|
110
|
+
| `Postsvg::Translation` | SVG → PS dispatch + context |
|
|
111
|
+
| `Postsvg::Serializer` | Model → PS source |
|
|
112
|
+
| `Postsvg::CLI` | Argument parsing, I/O |
|
|
97
113
|
|
|
98
114
|
== Installation
|
|
99
115
|
|
|
@@ -118,40 +134,25 @@ Or install it yourself as:
|
|
|
118
134
|
gem install postsvg
|
|
119
135
|
----
|
|
120
136
|
|
|
121
|
-
==
|
|
137
|
+
== Conversion APIs
|
|
122
138
|
|
|
123
139
|
[[basic-conversion]]
|
|
124
140
|
|
|
125
|
-
|
|
141
|
+
Postsvg provides four directional APIs covering both PS/EPS → SVG and
|
|
142
|
+
SVG → PS/EPS. All conversions are pure-Ruby with no external process
|
|
143
|
+
invocation.
|
|
126
144
|
|
|
127
|
-
|
|
128
|
-
content to SVG format. The conversion can be performed on in-memory
|
|
129
|
-
content or directly on files.
|
|
145
|
+
=== Forward direction (PS/EPS → SVG)
|
|
130
146
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
[source,ruby]
|
|
140
|
-
----
|
|
141
|
-
svg_output = Postsvg.convert(ps_content) <1>
|
|
142
|
-
----
|
|
143
|
-
<1> Convert PostScript string to SVG string
|
|
144
|
-
|
|
145
|
-
Where,
|
|
146
|
-
|
|
147
|
-
`ps_content`:: PostScript or EPS content as a string. Should include
|
|
148
|
-
proper PostScript header and BoundingBox comment for best results.
|
|
149
|
-
|
|
150
|
-
Returns:: SVG markup as a string
|
|
147
|
+
[cols="1,4", options="header"]
|
|
148
|
+
|===
|
|
149
|
+
| Method | Description
|
|
150
|
+
| `Postsvg.to_svg(source, **opts)` | Convert PS/EPS source string to SVG.
|
|
151
|
+
| `Postsvg.to_svg_file(input_path, output_path=nil, **opts)` | Read PS/EPS file, optionally write SVG.
|
|
152
|
+
| `Postsvg.convert(...)` / `Postsvg.convert_file(...)` | Backwards-compatible aliases for `to_svg` / `to_svg_file`.
|
|
153
|
+
|===
|
|
151
154
|
|
|
152
155
|
.Converting PostScript content to SVG
|
|
153
|
-
[example]
|
|
154
|
-
====
|
|
155
156
|
[source,ruby]
|
|
156
157
|
----
|
|
157
158
|
require "postsvg"
|
|
@@ -169,102 +170,136 @@ ps_content = <<~PS
|
|
|
169
170
|
fill
|
|
170
171
|
PS
|
|
171
172
|
|
|
172
|
-
svg = Postsvg.
|
|
173
|
+
svg = Postsvg.to_svg(ps_content)
|
|
173
174
|
File.write("output.svg", svg)
|
|
174
175
|
----
|
|
175
176
|
|
|
176
177
|
This converts a PostScript square with gray fill to SVG format and saves
|
|
177
178
|
it to a file.
|
|
178
|
-
====
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
.Converting a PostScript file to SVG
|
|
181
|
+
[source,ruby]
|
|
182
|
+
----
|
|
183
|
+
require "postsvg"
|
|
184
|
+
|
|
185
|
+
# Convert and save in one step
|
|
186
|
+
Postsvg.to_svg_file("input.eps", "output.svg")
|
|
181
187
|
|
|
182
|
-
|
|
188
|
+
# Or get SVG content without saving
|
|
189
|
+
svg_content = Postsvg.to_svg_file("input.ps")
|
|
190
|
+
puts svg_content
|
|
191
|
+
----
|
|
183
192
|
|
|
193
|
+
=== Reverse direction (SVG → PS / EPS)
|
|
194
|
+
|
|
195
|
+
[cols="1,4", options="header"]
|
|
196
|
+
|===
|
|
197
|
+
| Method | Description
|
|
198
|
+
| `Postsvg.to_ps(svg_string, eps: false, **opts)` | Convert SVG to PostScript source.
|
|
199
|
+
| `Postsvg.to_eps(svg_string, **opts)` | Convert SVG to Encapsulated PostScript (EPSF-3.0 header).
|
|
200
|
+
| `Postsvg.to_ps_file(input_path, output_path=nil, eps: false, **opts)` | Read SVG file, optionally write PS.
|
|
201
|
+
|===
|
|
202
|
+
|
|
203
|
+
.Converting SVG to PostScript
|
|
184
204
|
[source,ruby]
|
|
185
205
|
----
|
|
186
|
-
|
|
187
|
-
|
|
206
|
+
require "postsvg"
|
|
207
|
+
|
|
208
|
+
svg_content = File.read("input.svg")
|
|
209
|
+
|
|
210
|
+
# Produce PostScript
|
|
211
|
+
ps = Postsvg.to_ps(svg_content)
|
|
212
|
+
File.write("output.ps", ps)
|
|
213
|
+
|
|
214
|
+
# Produce Encapsulated PostScript for embedding
|
|
215
|
+
eps = Postsvg.to_eps(svg_content)
|
|
216
|
+
File.write("output.eps", eps)
|
|
188
217
|
----
|
|
189
|
-
<1> Convert file and save to output path
|
|
190
|
-
<2> Convert file and return SVG content without saving
|
|
191
218
|
|
|
192
|
-
|
|
219
|
+
The SVG→PS pipeline supports `<svg>`, `<g>`, `<path>` (full M/L/H/V/C/S/Q/T/A/Z
|
|
220
|
+
command set), `<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<polyline>`,
|
|
221
|
+
`<polygon>`, `<text>` (emits `findfont`/`scalefont`/`setfont`/`show`),
|
|
222
|
+
`<image>` (stubbed), `<defs>`, and `<clipPath>`. Arc paths use the W3C
|
|
223
|
+
endpoint-to-center conversion algorithm (SVG 1.1 §F.6.5) to emit the
|
|
224
|
+
equivalent PS `arc` operator.
|
|
193
225
|
|
|
194
|
-
|
|
195
|
-
`output_path`:: (Optional) Path where SVG file should be saved. If
|
|
196
|
-
omitted, SVG content is returned without saving.
|
|
226
|
+
=== Round-trip
|
|
197
227
|
|
|
198
|
-
|
|
199
|
-
|
|
228
|
+
The same `Model::Program` value object sits behind both directions, so
|
|
229
|
+
round-trip is a function composition:
|
|
200
230
|
|
|
201
|
-
.Converting a PostScript file to SVG
|
|
202
|
-
[example]
|
|
203
|
-
====
|
|
204
231
|
[source,ruby]
|
|
205
232
|
----
|
|
206
233
|
require "postsvg"
|
|
207
234
|
|
|
208
|
-
|
|
209
|
-
Postsvg.
|
|
235
|
+
original_svg = File.read("input.svg")
|
|
236
|
+
ps = Postsvg.to_ps(original_svg)
|
|
237
|
+
round_tripped_svg = Postsvg.to_svg(ps)
|
|
210
238
|
|
|
211
|
-
#
|
|
212
|
-
|
|
213
|
-
puts svg_content
|
|
239
|
+
# round_tripped_svg preserves the geometry of original_svg for the
|
|
240
|
+
# supported subset of SVG elements.
|
|
214
241
|
----
|
|
215
242
|
|
|
216
|
-
The first example converts an EPS file and saves the result as SVG. The
|
|
217
|
-
second example reads a PostScript file and returns the SVG content for
|
|
218
|
-
further processing.
|
|
219
|
-
====
|
|
220
|
-
|
|
221
243
|
== Command-line interface
|
|
222
244
|
|
|
223
245
|
[[cli]]
|
|
224
246
|
|
|
225
247
|
=== General
|
|
226
248
|
|
|
227
|
-
Postsvg provides a Thor-based command-line interface
|
|
228
|
-
|
|
229
|
-
|
|
249
|
+
Postsvg provides a Thor-based command-line interface covering both
|
|
250
|
+
directions (PS/EPS → SVG and SVG → PS/EPS), plus batch processing and
|
|
251
|
+
version information.
|
|
230
252
|
|
|
231
253
|
The CLI is available through the `postsvg` executable installed with the
|
|
232
254
|
gem.
|
|
233
255
|
|
|
234
|
-
=== Converting
|
|
256
|
+
=== Converting PS/EPS to SVG
|
|
235
257
|
|
|
236
258
|
Syntax:
|
|
237
259
|
|
|
238
260
|
[source,sh]
|
|
239
261
|
----
|
|
240
|
-
postsvg convert INPUT_FILE [OUTPUT_FILE]
|
|
262
|
+
postsvg convert INPUT_FILE [OUTPUT_FILE]
|
|
263
|
+
postsvg to-svg INPUT_FILE [OUTPUT_FILE] # explicit alias
|
|
241
264
|
----
|
|
242
|
-
<1> Convert INPUT_FILE to SVG, optionally saving to OUTPUT_FILE
|
|
243
265
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
`INPUT_FILE`:: Path to input PostScript (.ps) or EPS (.eps) file
|
|
247
|
-
`OUTPUT_FILE`:: (Optional) Path where SVG file should be saved. If
|
|
248
|
-
omitted, SVG is written to stdout.
|
|
266
|
+
`INPUT_FILE` is a PostScript (.ps) or EPS (.eps) file. If `OUTPUT_FILE`
|
|
267
|
+
is omitted, SVG is written to stdout.
|
|
249
268
|
|
|
250
|
-
.Converting
|
|
251
|
-
[example]
|
|
252
|
-
====
|
|
269
|
+
.Converting PS/EPS to SVG
|
|
253
270
|
[source,sh]
|
|
254
271
|
----
|
|
255
272
|
# Convert to stdout
|
|
256
273
|
postsvg convert input.ps
|
|
257
274
|
|
|
258
275
|
# Convert and save to file
|
|
259
|
-
postsvg
|
|
276
|
+
postsvg to-svg input.eps output.svg
|
|
260
277
|
|
|
261
278
|
# Redirect stdout to file
|
|
262
279
|
postsvg convert input.ps > output.svg
|
|
263
280
|
----
|
|
264
281
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
282
|
+
=== Converting SVG to PS/EPS
|
|
283
|
+
|
|
284
|
+
Syntax:
|
|
285
|
+
|
|
286
|
+
[source,sh]
|
|
287
|
+
----
|
|
288
|
+
postsvg to-ps INPUT.svg [OUTPUT.ps] # SVG → PostScript
|
|
289
|
+
postsvg to-eps INPUT.svg [OUTPUT.eps] # SVG → Encapsulated PostScript
|
|
290
|
+
----
|
|
291
|
+
|
|
292
|
+
If `OUTPUT` is omitted, PS/EPS source is written to stdout.
|
|
293
|
+
|
|
294
|
+
.Converting SVG to PostScript
|
|
295
|
+
[source,sh]
|
|
296
|
+
----
|
|
297
|
+
# Save to file
|
|
298
|
+
postsvg to-ps input.svg output.ps
|
|
299
|
+
|
|
300
|
+
# Pipe through stdout
|
|
301
|
+
postsvg to-eps input.svg > output.eps
|
|
302
|
+
----
|
|
268
303
|
|
|
269
304
|
=== Batch conversion
|
|
270
305
|
|
|
@@ -272,36 +307,27 @@ Syntax:
|
|
|
272
307
|
|
|
273
308
|
[source,sh]
|
|
274
309
|
----
|
|
275
|
-
postsvg batch INPUT_DIR [OUTPUT_DIR]
|
|
310
|
+
postsvg batch INPUT_DIR [OUTPUT_DIR]
|
|
276
311
|
----
|
|
277
|
-
<1> Convert all PS/EPS files in INPUT_DIR, optionally saving to
|
|
278
|
-
OUTPUT_DIR
|
|
279
312
|
|
|
280
|
-
|
|
313
|
+
`batch` auto-detects the direction by file extension:
|
|
281
314
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
`OUTPUT_DIR`:: (Optional) Directory where SVG files should be saved. If
|
|
285
|
-
omitted, SVG files are saved in the same directory as input files with
|
|
286
|
-
.svg extension.
|
|
315
|
+
* `.ps` / `.eps` → `.svg`
|
|
316
|
+
* `.svg` → `.ps`
|
|
287
317
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
318
|
+
If `OUTPUT_DIR` is omitted, output files land in `INPUT_DIR` with the
|
|
319
|
+
converted extension.
|
|
320
|
+
|
|
321
|
+
.Batch converting files
|
|
291
322
|
[source,sh]
|
|
292
323
|
----
|
|
293
|
-
# Convert all PS/EPS files in a directory
|
|
294
|
-
postsvg batch
|
|
324
|
+
# Convert all PS/EPS/SVG files in a directory
|
|
325
|
+
postsvg batch mixed_files/
|
|
295
326
|
|
|
296
327
|
# Convert to a different directory
|
|
297
328
|
postsvg batch ps_files/ svg_files/
|
|
298
329
|
----
|
|
299
330
|
|
|
300
|
-
The first example converts all PS and EPS files in the `ps_files/`
|
|
301
|
-
directory, saving the SVG files in the same directory. The second
|
|
302
|
-
example saves the converted files to the `svg_files/` directory.
|
|
303
|
-
====
|
|
304
|
-
|
|
305
331
|
=== Displaying version information
|
|
306
332
|
|
|
307
333
|
Syntax:
|
|
@@ -326,6 +352,258 @@ postsvg version 0.1.0
|
|
|
326
352
|
----
|
|
327
353
|
====
|
|
328
354
|
|
|
355
|
+
== File validation
|
|
356
|
+
|
|
357
|
+
[[file-validation]]
|
|
358
|
+
|
|
359
|
+
=== General
|
|
360
|
+
|
|
361
|
+
Postsvg includes a comprehensive validation tool for checking PostScript
|
|
362
|
+
and EPS files. The validator performs syntax checking, semantic
|
|
363
|
+
validation, and optional full conversion testing to ensure files are
|
|
364
|
+
well-formed and can be successfully converted.
|
|
365
|
+
|
|
366
|
+
The validator is particularly useful for:
|
|
367
|
+
|
|
368
|
+
* Verifying PostScript/EPS file integrity before conversion
|
|
369
|
+
* Identifying syntax errors and structural issues
|
|
370
|
+
* Validating compliance with PostScript standards
|
|
371
|
+
* Integrating quality checks into CI/CD pipelines
|
|
372
|
+
|
|
373
|
+
=== Basic validation
|
|
374
|
+
|
|
375
|
+
Syntax:
|
|
376
|
+
|
|
377
|
+
[source,sh]
|
|
378
|
+
----
|
|
379
|
+
postsvg check FILE... <1>
|
|
380
|
+
----
|
|
381
|
+
<1> Validate one or more PostScript or EPS files
|
|
382
|
+
|
|
383
|
+
Where,
|
|
384
|
+
|
|
385
|
+
`FILE`:: Path to PostScript (.ps) or EPS (.eps) file(s) to validate.
|
|
386
|
+
Multiple files can be specified.
|
|
387
|
+
|
|
388
|
+
Returns:: Exit code 0 if all files are valid, 1 if any file has errors.
|
|
389
|
+
|
|
390
|
+
.Validating PostScript files
|
|
391
|
+
[example]
|
|
392
|
+
[source,sh]
|
|
393
|
+
----
|
|
394
|
+
# Validate a single file
|
|
395
|
+
postsvg check document.ps
|
|
396
|
+
|
|
397
|
+
# Validate multiple files
|
|
398
|
+
postsvg check file1.ps file2.eps file3.ps
|
|
399
|
+
|
|
400
|
+
# Validate all PS files in directory
|
|
401
|
+
postsvg check *.ps
|
|
402
|
+
----
|
|
403
|
+
|
|
404
|
+
The validator performs semantic-level validation by default, checking
|
|
405
|
+
syntax, stack balance, and graphics state management.
|
|
406
|
+
|
|
407
|
+
=== Validation levels
|
|
408
|
+
|
|
409
|
+
Syntax:
|
|
410
|
+
|
|
411
|
+
[source,sh]
|
|
412
|
+
----
|
|
413
|
+
postsvg check --level=LEVEL FILE... <1>
|
|
414
|
+
----
|
|
415
|
+
<1> Validate with specified level: syntax, semantic, or full
|
|
416
|
+
|
|
417
|
+
Where,
|
|
418
|
+
|
|
419
|
+
`LEVEL`:: One of:
|
|
420
|
+
* `syntax` - Fast syntax-only validation (header, delimiters,
|
|
421
|
+
tokenization)
|
|
422
|
+
* `semantic` - Default level (syntax + stack/state checking)
|
|
423
|
+
* `full` - Strictest validation (semantic + complete conversion test)
|
|
424
|
+
|
|
425
|
+
.Using different validation levels
|
|
426
|
+
[example]
|
|
427
|
+
[source,sh]
|
|
428
|
+
----
|
|
429
|
+
# Fast syntax-only check
|
|
430
|
+
postsvg check --level=syntax document.ps
|
|
431
|
+
|
|
432
|
+
# Default semantic validation
|
|
433
|
+
postsvg check --level=semantic document.ps
|
|
434
|
+
|
|
435
|
+
# Full validation with conversion test
|
|
436
|
+
postsvg check --level=full document.ps
|
|
437
|
+
----
|
|
438
|
+
|
|
439
|
+
Syntax validation is fastest but only checks basic file structure.
|
|
440
|
+
Semantic validation adds stack and state checking. Full validation
|
|
441
|
+
attempts complete conversion and is most thorough.
|
|
442
|
+
|
|
443
|
+
=== Output formats
|
|
444
|
+
|
|
445
|
+
Syntax:
|
|
446
|
+
|
|
447
|
+
[source,sh]
|
|
448
|
+
----
|
|
449
|
+
postsvg check --format=FORMAT FILE... <1>
|
|
450
|
+
----
|
|
451
|
+
<1> Output validation results in specified format
|
|
452
|
+
|
|
453
|
+
Where,
|
|
454
|
+
|
|
455
|
+
`FORMAT`:: One of:
|
|
456
|
+
* `text` - Human-readable colored console output (default)
|
|
457
|
+
* `yaml` - YAML structured format
|
|
458
|
+
* `json` - JSON structured format
|
|
459
|
+
|
|
460
|
+
.Output format examples
|
|
461
|
+
[example]
|
|
462
|
+
[source,sh]
|
|
463
|
+
----
|
|
464
|
+
# Default text output with colors
|
|
465
|
+
postsvg check document.ps
|
|
466
|
+
|
|
467
|
+
# YAML output
|
|
468
|
+
postsvg check --format=yaml document.ps
|
|
469
|
+
|
|
470
|
+
# JSON output for programmatic parsing
|
|
471
|
+
postsvg check --format=json document.ps
|
|
472
|
+
|
|
473
|
+
# JSON output without colors (for CI)
|
|
474
|
+
postsvg check --format=json --no-color document.ps
|
|
475
|
+
----
|
|
476
|
+
|
|
477
|
+
Text format provides human-readable output with color-coded status
|
|
478
|
+
indicators. YAML and JSON formats are useful for programmatic processing
|
|
479
|
+
or integration with other tools.
|
|
480
|
+
|
|
481
|
+
=== Validation options
|
|
482
|
+
|
|
483
|
+
The validator supports additional options for controlling behavior and
|
|
484
|
+
output:
|
|
485
|
+
|
|
486
|
+
`--verbose`:: Show warnings and info messages in addition to errors
|
|
487
|
+
`--quiet`:: Suppress output for valid files (only show errors)
|
|
488
|
+
`--no-color`:: Disable colored output
|
|
489
|
+
`--fail-fast`:: Stop validation at first error
|
|
490
|
+
`--eps-version=VERSION`:: Validate EPS version (e.g., `3.0`)
|
|
491
|
+
|
|
492
|
+
.Using validation options
|
|
493
|
+
[example]
|
|
494
|
+
[source,sh]
|
|
495
|
+
----
|
|
496
|
+
# Verbose output with all details
|
|
497
|
+
postsvg check --verbose document.eps
|
|
498
|
+
|
|
499
|
+
# Quiet mode - only show errors
|
|
500
|
+
postsvg check --quiet *.ps
|
|
501
|
+
|
|
502
|
+
# Stop at first error
|
|
503
|
+
postsvg check --fail-fast file1.ps file2.ps file3.ps
|
|
504
|
+
|
|
505
|
+
# Validate specific EPS version
|
|
506
|
+
postsvg check --eps-version=3.0 diagram.eps
|
|
507
|
+
----
|
|
508
|
+
|
|
509
|
+
== SVG optimization
|
|
510
|
+
|
|
511
|
+
[[svg-optimization]]
|
|
512
|
+
|
|
513
|
+
=== General
|
|
514
|
+
|
|
515
|
+
Postsvg automatically optimizes SVG output to produce smaller, more
|
|
516
|
+
efficient files while maintaining visual fidelity to the original
|
|
517
|
+
PostScript graphics.
|
|
518
|
+
|
|
519
|
+
=== ClipPath deduplication
|
|
520
|
+
|
|
521
|
+
PostScript files often contain repeated clipping operations with
|
|
522
|
+
identical paths. Postsvg automatically detects and deduplicates these
|
|
523
|
+
clipPath definitions, significantly reducing SVG file size for documents
|
|
524
|
+
with repeated clipping operations.
|
|
525
|
+
|
|
526
|
+
.ClipPath deduplication example
|
|
527
|
+
[example]
|
|
528
|
+
Instead of generating multiple identical clipPath definitions:
|
|
529
|
+
|
|
530
|
+
[source,xml]
|
|
531
|
+
----
|
|
532
|
+
<defs>
|
|
533
|
+
<clipPath id="clipPath2">
|
|
534
|
+
<path d="M 0 0 L 100 100"/>
|
|
535
|
+
</clipPath>
|
|
536
|
+
<clipPath id="clipPath3">
|
|
537
|
+
<path d="M 0 0 L 100 100"/>
|
|
538
|
+
</clipPath>
|
|
539
|
+
<clipPath id="clipPath4">
|
|
540
|
+
<path d="M 0 0 L 100 100"/>
|
|
541
|
+
</clipPath>
|
|
542
|
+
</defs>
|
|
543
|
+
----
|
|
544
|
+
|
|
545
|
+
Postsvg generates a single definition and reuses it:
|
|
546
|
+
|
|
547
|
+
[source,xml]
|
|
548
|
+
----
|
|
549
|
+
<defs>
|
|
550
|
+
<clipPath id="clipPath2">
|
|
551
|
+
<path d="M 0 0 L 100 100"/>
|
|
552
|
+
</clipPath>
|
|
553
|
+
</defs>
|
|
554
|
+
<path clip-path="url(#clipPath2)" .../>
|
|
555
|
+
<path clip-path="url(#clipPath2)" .../>
|
|
556
|
+
<path clip-path="url(#clipPath2)" .../>
|
|
557
|
+
----
|
|
558
|
+
|
|
559
|
+
This optimization is automatic and requires no configuration.
|
|
560
|
+
|
|
561
|
+
== Test coverage
|
|
562
|
+
|
|
563
|
+
[[test-coverage]]
|
|
564
|
+
|
|
565
|
+
=== General
|
|
566
|
+
|
|
567
|
+
Postsvg maintains comprehensive test coverage to ensure reliability and
|
|
568
|
+
correctness of PostScript to SVG conversion.
|
|
569
|
+
|
|
570
|
+
=== Core test suite
|
|
571
|
+
|
|
572
|
+
The core test suite consists of 90+ tests covering execution context and
|
|
573
|
+
integration testing:
|
|
574
|
+
|
|
575
|
+
* **Execution context tests (84 tests)** - Unit tests for stack
|
|
576
|
+
operations, graphics state management, dictionary operations, path
|
|
577
|
+
operations, helper methods, SVG generation, and clipPath deduplication
|
|
578
|
+
* **Integration tests (6 tests)** - End-to-end tests using real
|
|
579
|
+
PostScript files from ps2svg and vectory test suites
|
|
580
|
+
|
|
581
|
+
All core tests maintain 100% pass rate, ensuring critical functionality
|
|
582
|
+
remains stable.
|
|
583
|
+
|
|
584
|
+
=== Running tests
|
|
585
|
+
|
|
586
|
+
Run the full test suite:
|
|
587
|
+
|
|
588
|
+
[source,sh]
|
|
589
|
+
----
|
|
590
|
+
bundle exec rspec
|
|
591
|
+
----
|
|
592
|
+
|
|
593
|
+
Run only core tests:
|
|
594
|
+
|
|
595
|
+
[source,sh]
|
|
596
|
+
----
|
|
597
|
+
bundle exec rspec spec/postsvg/execution_context_spec.rb spec/postsvg/integration_spec.rb
|
|
598
|
+
----
|
|
599
|
+
|
|
600
|
+
Run with documentation format:
|
|
601
|
+
|
|
602
|
+
[source,sh]
|
|
603
|
+
----
|
|
604
|
+
bundle exec rspec --format documentation
|
|
605
|
+
----
|
|
606
|
+
|
|
329
607
|
== PostScript language support
|
|
330
608
|
|
|
331
609
|
[[postscript-language]]
|
|
@@ -408,23 +686,22 @@ following topics:
|
|
|
408
686
|
|
|
409
687
|
== Limitations
|
|
410
688
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
*
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
*
|
|
418
|
-
|
|
419
|
-
*
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
tools or contributing implementations of these features.
|
|
689
|
+
* **Text rendering** is stubbed: `show` maps to SVG `<text>` but no
|
|
690
|
+
font metrics. Convert text to outlines for fidelity.
|
|
691
|
+
* **Real raster images** (`image`, `imagemask`): emit SVG `<image>`
|
|
692
|
+
with a placeholder when decoded; PNG round-trip is planned but not
|
|
693
|
+
yet wired.
|
|
694
|
+
* **Real Level 3 shading** (types 4–7) and forms are stubbed.
|
|
695
|
+
* **User-defined procedures** invoked at runtime: parsed via the AST,
|
|
696
|
+
but only first-order ones execute.
|
|
697
|
+
* **Procedures inside arrays** (`[...]`) keep literals only; operators
|
|
698
|
+
inside `[...]` are an unusual construct in EPS drawing programs
|
|
699
|
+
and not handled.
|
|
700
|
+
* **Document-level DSC** comments other than `%%BoundingBox` and
|
|
701
|
+
`%%HiResBoundingBox` are captured but not re-emitted.
|
|
702
|
+
|
|
703
|
+
For these gaps, see link:../TODO.roadmap/[TODO.roadmap] for the
|
|
704
|
+
planned work.
|
|
428
705
|
|
|
429
706
|
== Acknowledgments
|
|
430
707
|
|