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,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postsvg
|
|
4
|
+
# Top-level orchestrator for PS -> SVG. Owns lifecycle: BoundingBox
|
|
5
|
+
# pre-scan, builder open/close, visitor dispatch.
|
|
6
|
+
class Renderer
|
|
7
|
+
MAX_OUTPUT_BYTES = 100 * 1024 * 1024
|
|
8
|
+
|
|
9
|
+
def self.call(program, options = Options.new)
|
|
10
|
+
new(program, options).call
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :program, :options, :builder, :visitor
|
|
14
|
+
|
|
15
|
+
def initialize(program, options)
|
|
16
|
+
raise ArgumentError, "program must be a Model::Program" unless program.is_a?(Model::Program)
|
|
17
|
+
|
|
18
|
+
@program = program
|
|
19
|
+
@options = options
|
|
20
|
+
@builder = SvgBuilder.new
|
|
21
|
+
@visitor = Visitors::PsVisitor.new(builder: @builder)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
open_svg
|
|
26
|
+
wrap_y_flip do
|
|
27
|
+
visitor.visit_program(program)
|
|
28
|
+
rescue Postsvg::QuitSignal
|
|
29
|
+
# PS +quit+ ends program execution; suppress and finalize SVG.
|
|
30
|
+
end
|
|
31
|
+
builder.close_svg
|
|
32
|
+
result = builder.to_s
|
|
33
|
+
enforce_size_limit(result)
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def open_svg
|
|
40
|
+
viewbox, width, height = compute_viewbox
|
|
41
|
+
builder.open_svg(viewbox: viewbox, width: width, height: height)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def wrap_y_flip
|
|
45
|
+
builder.open_y_flip_group
|
|
46
|
+
yield
|
|
47
|
+
builder.close_group
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Determine viewBox from (1) explicit override, (2) header
|
|
51
|
+
# BoundingBox / HiResBoundingBox, (3) default A4 page size.
|
|
52
|
+
def compute_viewbox
|
|
53
|
+
return options_viewbox if options_viewbox
|
|
54
|
+
|
|
55
|
+
bbox = program.header.hires_bounding_box || program.header.bounding_box
|
|
56
|
+
if bbox
|
|
57
|
+
llx, lly, urx, ury = bbox
|
|
58
|
+
width = urx - llx
|
|
59
|
+
height = ury - lly
|
|
60
|
+
viewbox = "#{FormatNumber.call(llx)} #{FormatNumber.call(lly)} " \
|
|
61
|
+
"#{FormatNumber.call(width)} #{FormatNumber.call(height)}"
|
|
62
|
+
[viewbox, width, height]
|
|
63
|
+
else
|
|
64
|
+
width, height = options.page_size
|
|
65
|
+
["0 0 #{FormatNumber.call(width)} #{FormatNumber.call(height)}", width, height]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def options_viewbox
|
|
70
|
+
return nil unless options.viewbox_override
|
|
71
|
+
|
|
72
|
+
vb = options.viewbox_override
|
|
73
|
+
return vb if vb.is_a?(Array) && vb.length == 4
|
|
74
|
+
|
|
75
|
+
[vb, nil, nil]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def enforce_size_limit(result)
|
|
79
|
+
return unless result.bytesize > MAX_OUTPUT_BYTES
|
|
80
|
+
|
|
81
|
+
raise SizeLimitError,
|
|
82
|
+
"SVG output #{result.bytesize} bytes exceeds limit #{MAX_OUTPUT_BYTES}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Postsvg
|
|
4
|
+
# Model::Program -> PostScript source text. Walks the program's
|
|
5
|
+
# header (DSC comments) and body (Model records), emitting each as
|
|
6
|
+
# PS source. Used by the SVG -> PS direction and by tests.
|
|
7
|
+
#
|
|
8
|
+
# OCP: adding serialization for a new operator class means adding
|
|
9
|
+
# one +emit_<keyword>+ method (or falling through to the default
|
|
10
|
+
# keyword+operands emitter). No switch edits.
|
|
11
|
+
class Serializer
|
|
12
|
+
DEFAULT_LANGUAGE_LEVEL = 3
|
|
13
|
+
DEFAULT_CREATOR = "Postsvg #{::Postsvg::VERSION}".freeze
|
|
14
|
+
|
|
15
|
+
def self.call(program, eps: false, creator: DEFAULT_CREATOR, **_options)
|
|
16
|
+
new(program, eps: eps, creator: creator).call
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :program, :eps, :creator
|
|
20
|
+
|
|
21
|
+
def initialize(program, eps:, creator:)
|
|
22
|
+
raise ArgumentError, "program must be a Model::Program" unless program.is_a?(Model::Program)
|
|
23
|
+
|
|
24
|
+
@program = program
|
|
25
|
+
@eps = eps
|
|
26
|
+
@creator = creator
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def call
|
|
30
|
+
buffer = String.new(capacity: 4096)
|
|
31
|
+
emit_header(buffer)
|
|
32
|
+
emit_body(buffer)
|
|
33
|
+
buffer << "showpage\n"
|
|
34
|
+
buffer << "%%EOF\n"
|
|
35
|
+
buffer
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# All methods below this point are public. They are intentionally
|
|
39
|
+
# exposed (prefixed +emit_+) so dispatch can reach them via
|
|
40
|
+
# +method_defined?+ without +respond_to?+.
|
|
41
|
+
def emit_header(buffer)
|
|
42
|
+
buffer << "%!PS-Adobe-#{DEFAULT_LANGUAGE_LEVEL}.0"
|
|
43
|
+
buffer << " EPSF-3.0" if eps
|
|
44
|
+
buffer << "\n"
|
|
45
|
+
buffer << "%%Creator: #{creator}\n"
|
|
46
|
+
bbox = program.header.bounding_box
|
|
47
|
+
if bbox && !bbox.empty?
|
|
48
|
+
buffer << "%%BoundingBox: " << bbox.map { |v| FormatNumber.call(v) }.join(" ") << "\n"
|
|
49
|
+
end
|
|
50
|
+
if program.header.title
|
|
51
|
+
buffer << "%%Title: #{program.header.title}\n"
|
|
52
|
+
end
|
|
53
|
+
buffer << "%%LanguageLevel: #{DEFAULT_LANGUAGE_LEVEL}\n"
|
|
54
|
+
buffer << "%%EndComments\n"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def emit_body(buffer)
|
|
58
|
+
program.body.each do |statement|
|
|
59
|
+
emit_statement(statement, buffer)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def emit_statement(statement, buffer)
|
|
64
|
+
case statement
|
|
65
|
+
when Model::Literals::Number
|
|
66
|
+
buffer << FormatNumber.call(statement.value) << "\n"
|
|
67
|
+
when Model::Literals::Name
|
|
68
|
+
prefix = statement.literal? ? "/" : ""
|
|
69
|
+
buffer << prefix << statement.value << "\n"
|
|
70
|
+
when Model::Literals::StringLiteral
|
|
71
|
+
buffer << escape_string(statement.value) << "\n"
|
|
72
|
+
when Model::Literals::HexLiteral
|
|
73
|
+
buffer << "<" << statement.value << ">\n"
|
|
74
|
+
when Model::Literals::ArrayLiteral
|
|
75
|
+
buffer << "[ "
|
|
76
|
+
statement.elements.each { |e| emit_inline(e, buffer); buffer << " " }
|
|
77
|
+
buffer << "]\n"
|
|
78
|
+
when Model::Literals::Procedure
|
|
79
|
+
buffer << "{\n"
|
|
80
|
+
statement.body.each { |s| emit_statement(s, buffer) }
|
|
81
|
+
buffer << "}\n"
|
|
82
|
+
when Model::Literals::Dictionary
|
|
83
|
+
buffer << "<<\n"
|
|
84
|
+
statement.entries.each do |k, v|
|
|
85
|
+
buffer << "/#{k} "
|
|
86
|
+
emit_inline(v, buffer)
|
|
87
|
+
buffer << "\n"
|
|
88
|
+
end
|
|
89
|
+
buffer << ">>\n"
|
|
90
|
+
when Model::Operator
|
|
91
|
+
emit_operator(statement, buffer)
|
|
92
|
+
when Model::UnknownOperator
|
|
93
|
+
buffer << "% unhandled operator: #{statement.keyword}\n"
|
|
94
|
+
when Model::InvokeProcedure
|
|
95
|
+
buffer << statement.name << "\n"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def emit_inline(statement, buffer)
|
|
100
|
+
case statement
|
|
101
|
+
when Model::Literals::Number then buffer << FormatNumber.call(statement.value)
|
|
102
|
+
when Model::Literals::Name then buffer << (statement.literal? ? "/" : "") << statement.value
|
|
103
|
+
when Model::Literals::StringLiteral then buffer << escape_string(statement.value)
|
|
104
|
+
when Model::Literals::HexLiteral then buffer << "<" << statement.value << ">"
|
|
105
|
+
else buffer << statement.class.name.to_s
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Dispatch table: emit_<keyword>. Falls back to the generic
|
|
110
|
+
# operand+keyword form when no specific method exists.
|
|
111
|
+
def emit_operator(operator, buffer)
|
|
112
|
+
method = :"emit_#{operator.visit_name}"
|
|
113
|
+
if self.class.public_method_defined?(method)
|
|
114
|
+
public_send(method, operator, buffer)
|
|
115
|
+
else
|
|
116
|
+
emit_generic_operator(operator, buffer)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def emit_generic_operator(operator, buffer)
|
|
121
|
+
buffer << operator.keyword << "\n"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Path
|
|
125
|
+
def emit_moveto(op, buf)
|
|
126
|
+
buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} moveto\n"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def emit_rmoveto(op, buf)
|
|
130
|
+
buf << "#{FormatNumber.call(op.dx)} #{FormatNumber.call(op.dy)} rmoveto\n"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def emit_lineto(op, buf)
|
|
134
|
+
buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} lineto\n"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def emit_rlineto(op, buf)
|
|
138
|
+
buf << "#{FormatNumber.call(op.dx)} #{FormatNumber.call(op.dy)} rlineto\n"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def emit_curveto(op, buf)
|
|
142
|
+
buf << "#{FormatNumber.call(op.x1)} #{FormatNumber.call(op.y1)} " \
|
|
143
|
+
"#{FormatNumber.call(op.x2)} #{FormatNumber.call(op.y2)} " \
|
|
144
|
+
"#{FormatNumber.call(op.x3)} #{FormatNumber.call(op.y3)} curveto\n"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def emit_rcurveto(op, buf)
|
|
148
|
+
buf << "#{FormatNumber.call(op.dx1)} #{FormatNumber.call(op.dy1)} " \
|
|
149
|
+
"#{FormatNumber.call(op.dx2)} #{FormatNumber.call(op.dy2)} " \
|
|
150
|
+
"#{FormatNumber.call(op.dx3)} #{FormatNumber.call(op.dy3)} rcurveto\n"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def emit_arc(op, buf)
|
|
154
|
+
buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} " \
|
|
155
|
+
"#{FormatNumber.call(op.radius)} " \
|
|
156
|
+
"#{FormatNumber.call(op.angle1)} #{FormatNumber.call(op.angle2)} arc\n"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def emit_arcn(op, buf)
|
|
160
|
+
buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} " \
|
|
161
|
+
"#{FormatNumber.call(op.radius)} " \
|
|
162
|
+
"#{FormatNumber.call(op.angle1)} #{FormatNumber.call(op.angle2)} arcn\n"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def emit_closepath(_op, buf)
|
|
166
|
+
buf << "closepath\n"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def emit_newpath(_op, buf)
|
|
170
|
+
buf << "newpath\n"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Painting
|
|
174
|
+
def emit_stroke(_op, buf) = buf << "stroke\n"
|
|
175
|
+
def emit_fill(_op, buf) = buf << "fill\n"
|
|
176
|
+
def emit_eofill(_op, buf) = buf << "eofill\n"
|
|
177
|
+
def emit_clip(_op, buf) = buf << "clip\n"
|
|
178
|
+
def emit_eoclip(_op, buf) = buf << "eoclip\n"
|
|
179
|
+
|
|
180
|
+
# Color
|
|
181
|
+
def emit_setrgbcolor(op, buf)
|
|
182
|
+
buf << "#{FormatNumber.call(op.red)} #{FormatNumber.call(op.green)} #{FormatNumber.call(op.blue)} setrgbcolor\n"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def emit_setgray(op, buf)
|
|
186
|
+
buf << "#{FormatNumber.call(op.gray)} setgray\n"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def emit_setcmykcolor(op, buf)
|
|
190
|
+
buf << "#{FormatNumber.call(op.cyan)} #{FormatNumber.call(op.magenta)} " \
|
|
191
|
+
"#{FormatNumber.call(op.yellow)} #{FormatNumber.call(op.key)} setcmykcolor\n"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def emit_sethsbcolor(op, buf)
|
|
195
|
+
buf << "#{FormatNumber.call(op.hue)} #{FormatNumber.call(op.saturation)} " \
|
|
196
|
+
"#{FormatNumber.call(op.brightness)} sethsbcolor\n"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Graphics state
|
|
200
|
+
def emit_gsave(_op, buf) = buf << "gsave\n"
|
|
201
|
+
def emit_grestore(_op, buf) = buf << "grestore\n"
|
|
202
|
+
def emit_grestoreall(_op, buf) = buf << "grestoreall\n"
|
|
203
|
+
|
|
204
|
+
def emit_setlinewidth(op, buf)
|
|
205
|
+
buf << "#{FormatNumber.call(op.width)} setlinewidth\n"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def emit_setlinecap(op, buf)
|
|
209
|
+
buf << "#{op.cap_code} setlinecap\n"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def emit_setlinejoin(op, buf)
|
|
213
|
+
buf << "#{op.join_code} setlinejoin\n"
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def emit_setmiterlimit(op, buf)
|
|
217
|
+
buf << "#{FormatNumber.call(op.limit)} setmiterlimit\n"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def emit_setdash(op, buf)
|
|
221
|
+
pattern_str =
|
|
222
|
+
case op.pattern
|
|
223
|
+
when Array then "[#{op.pattern.map { |v| FormatNumber.call(v.to_f) }.join(' ')}]"
|
|
224
|
+
when Numeric then FormatNumber.call(op.pattern.to_f)
|
|
225
|
+
else "[]"
|
|
226
|
+
end
|
|
227
|
+
buf << "#{pattern_str} #{FormatNumber.call(op.offset)} setdash\n"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Transformations
|
|
231
|
+
def emit_translate(op, buf)
|
|
232
|
+
buf << "#{FormatNumber.call(op.tx)} #{FormatNumber.call(op.ty)} translate\n"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def emit_scale(op, buf)
|
|
236
|
+
buf << "#{FormatNumber.call(op.sx)} #{FormatNumber.call(op.sy)} scale\n"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def emit_rotate(op, buf)
|
|
240
|
+
buf << "#{FormatNumber.call(op.angle)} rotate\n"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def emit_concat(op, buf)
|
|
244
|
+
m = op.matrix
|
|
245
|
+
arr = m.is_a?(Array) ? m : [m.a, m.b, m.c, m.d, m.e, m.f]
|
|
246
|
+
buf << "[#{arr.map { |v| FormatNumber.call(v.to_f) }.join(' ')}] concat\n"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def escape_string(text)
|
|
250
|
+
escaped = text.to_s.gsub(/[\(\)\\]/) { |c| "\\#{c}" }
|
|
251
|
+
.gsub("\n", "\\n")
|
|
252
|
+
.gsub("\r", "\\r")
|
|
253
|
+
.gsub("\t", "\\t")
|
|
254
|
+
"(#{escaped})"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Font / text
|
|
258
|
+
def emit_findfont(op, buf)
|
|
259
|
+
name = font_name_value(op.name)
|
|
260
|
+
buf << "/#{name} findfont\n"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def emit_scalefont(op, buf)
|
|
264
|
+
buf << "#{FormatNumber.call(op.size)} scalefont\n"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def emit_setfont(_op, buf)
|
|
268
|
+
buf << "setfont\n"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def emit_show(op, buf)
|
|
272
|
+
buf << "#{escape_string(string_value(op.text))} show\n"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def emit_xyshow(_op, buf)
|
|
276
|
+
buf << "% xyshow: per-glyph advances not serialized\n"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def emit_stringwidth(_op, buf)
|
|
280
|
+
buf << "% stringwidth: no runtime result captured\n"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def emit_charpath(_op, buf)
|
|
284
|
+
buf << "% charpath: requires font metrics\n"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Container operators
|
|
288
|
+
def emit_length(_op, buf) = buf << "length\n"
|
|
289
|
+
def emit_get(_op, buf) = buf << "get\n"
|
|
290
|
+
def emit_put(_op, buf) = buf << "put\n"
|
|
291
|
+
def emit_getinterval(_op, buf) = buf << "getinterval\n"
|
|
292
|
+
def emit_putinterval(_op, buf) = buf << "putinterval\n"
|
|
293
|
+
def emit_forall(_op, buf) = buf << "forall\n"
|
|
294
|
+
def emit_astore(_op, buf) = buf << "astore\n"
|
|
295
|
+
def emit_search(_op, buf) = buf << "search\n"
|
|
296
|
+
def emit_anchorsearch(_op, buf) = buf << "anchorsearch\n"
|
|
297
|
+
def emit_token(_op, buf) = buf << "token\n"
|
|
298
|
+
def emit_string(_op, buf) = buf << "string\n"
|
|
299
|
+
def emit_cvs(_op, buf) = buf << "cvs\n"
|
|
300
|
+
|
|
301
|
+
# Dictionary operators (currentdict etc.)
|
|
302
|
+
def emit_currentdict(_op, buf) = buf << "currentdict\n"
|
|
303
|
+
def emit_countdictstack(_op, buf) = buf << "countdictstack\n"
|
|
304
|
+
def emit_dictstack(_op, buf) = buf << "dictstack\n"
|
|
305
|
+
def emit_maxlength(_op, buf) = buf << "maxlength\n"
|
|
306
|
+
|
|
307
|
+
def font_name_value(value)
|
|
308
|
+
case value
|
|
309
|
+
when Model::Literals::Name then value.value
|
|
310
|
+
when Model::Literals::StringLiteral then value.value
|
|
311
|
+
when String then value
|
|
312
|
+
else value.to_s
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def string_value(value)
|
|
317
|
+
case value
|
|
318
|
+
when Model::Literals::StringLiteral then value.value
|
|
319
|
+
when Model::Literals::HexLiteral then value.bytes
|
|
320
|
+
when String then value
|
|
321
|
+
else value.to_s
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|