Class: Postsvg::Serializer

Inherits:
Object
  • Object
show all
Defined in:
lib/postsvg/serializer.rb

Overview

Model::Program -> PostScript source text. Walks the program's header (DSC comments) and body (Model records), emitting each as PS source. Used by the SVG -> PS direction and by tests.

OCP: adding serialization for a new operator class means adding one emit_<keyword> method (or falling through to the default keyword+operands emitter). No switch edits.

Constant Summary collapse

DEFAULT_LANGUAGE_LEVEL =
3
DEFAULT_CREATOR =
"Postsvg #{::Postsvg::VERSION}".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(program, eps:, creator:) ⇒ Serializer

Returns a new instance of Serializer.

Raises:

  • (ArgumentError)


21
22
23
24
25
26
27
# File 'lib/postsvg/serializer.rb', line 21

def initialize(program, eps:, creator:)
  raise ArgumentError, "program must be a Model::Program" unless program.is_a?(Model::Program)

  @program = program
  @eps = eps
  @creator = creator
end

Instance Attribute Details

#creatorObject (readonly)

Returns the value of attribute creator.



19
20
21
# File 'lib/postsvg/serializer.rb', line 19

def creator
  @creator
end

#epsObject (readonly)

Returns the value of attribute eps.



19
20
21
# File 'lib/postsvg/serializer.rb', line 19

def eps
  @eps
end

#programObject (readonly)

Returns the value of attribute program.



19
20
21
# File 'lib/postsvg/serializer.rb', line 19

def program
  @program
end

Class Method Details

.call(program, eps: false, creator: DEFAULT_CREATOR, **_options) ⇒ Object



15
16
17
# File 'lib/postsvg/serializer.rb', line 15

def self.call(program, eps: false, creator: DEFAULT_CREATOR, **_options)
  new(program, eps: eps, creator: creator).call
end

Instance Method Details

#callObject



29
30
31
32
33
34
35
36
# File 'lib/postsvg/serializer.rb', line 29

def call
  buffer = String.new(capacity: 4096)
  emit_header(buffer)
  emit_body(buffer)
  buffer << "showpage\n"
  buffer << "%%EOF\n"
  buffer
end

#emit_anchorsearch(_op, buf) ⇒ Object



296
# File 'lib/postsvg/serializer.rb', line 296

def emit_anchorsearch(_op, buf) = buf << "anchorsearch\n"

#emit_arc(op, buf) ⇒ Object



153
154
155
156
157
# File 'lib/postsvg/serializer.rb', line 153

def emit_arc(op, buf)
  buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} " \
        "#{FormatNumber.call(op.radius)} " \
        "#{FormatNumber.call(op.angle1)} #{FormatNumber.call(op.angle2)} arc\n"
end

#emit_arcn(op, buf) ⇒ Object



159
160
161
162
163
# File 'lib/postsvg/serializer.rb', line 159

def emit_arcn(op, buf)
  buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} " \
        "#{FormatNumber.call(op.radius)} " \
        "#{FormatNumber.call(op.angle1)} #{FormatNumber.call(op.angle2)} arcn\n"
end

#emit_astore(_op, buf) ⇒ Object



294
# File 'lib/postsvg/serializer.rb', line 294

def emit_astore(_op, buf) = buf << "astore\n"

#emit_body(buffer) ⇒ Object



57
58
59
60
61
# File 'lib/postsvg/serializer.rb', line 57

def emit_body(buffer)
  program.body.each do |statement|
    emit_statement(statement, buffer)
  end
end

#emit_charpath(_op, buf) ⇒ Object



283
284
285
# File 'lib/postsvg/serializer.rb', line 283

def emit_charpath(_op, buf)
  buf << "% charpath: requires font metrics\n"
end

#emit_clip(_op, buf) ⇒ Object



177
# File 'lib/postsvg/serializer.rb', line 177

def emit_clip(_op, buf) = buf << "clip\n"

#emit_closepath(_op, buf) ⇒ Object



165
166
167
# File 'lib/postsvg/serializer.rb', line 165

def emit_closepath(_op, buf)
  buf << "closepath\n"
end

#emit_concat(op, buf) ⇒ Object



243
244
245
246
247
# File 'lib/postsvg/serializer.rb', line 243

def emit_concat(op, buf)
  m = op.matrix
  arr = m.is_a?(Array) ? m : [m.a, m.b, m.c, m.d, m.e, m.f]
  buf << "[#{arr.map { |v| FormatNumber.call(v.to_f) }.join(' ')}] concat\n"
end

#emit_countdictstack(_op, buf) ⇒ Object



303
# File 'lib/postsvg/serializer.rb', line 303

def emit_countdictstack(_op, buf) = buf << "countdictstack\n"

#emit_currentdict(_op, buf) ⇒ Object

Dictionary operators (currentdict etc.)



302
# File 'lib/postsvg/serializer.rb', line 302

def emit_currentdict(_op, buf) = buf << "currentdict\n"

#emit_curveto(op, buf) ⇒ Object



141
142
143
144
145
# File 'lib/postsvg/serializer.rb', line 141

def emit_curveto(op, buf)
  buf << "#{FormatNumber.call(op.x1)} #{FormatNumber.call(op.y1)} " \
        "#{FormatNumber.call(op.x2)} #{FormatNumber.call(op.y2)} " \
        "#{FormatNumber.call(op.x3)} #{FormatNumber.call(op.y3)} curveto\n"
end

#emit_cvs(_op, buf) ⇒ Object



299
# File 'lib/postsvg/serializer.rb', line 299

def emit_cvs(_op, buf) = buf << "cvs\n"

#emit_dictstack(_op, buf) ⇒ Object



304
# File 'lib/postsvg/serializer.rb', line 304

def emit_dictstack(_op, buf) = buf << "dictstack\n"

#emit_eoclip(_op, buf) ⇒ Object



178
# File 'lib/postsvg/serializer.rb', line 178

def emit_eoclip(_op, buf) = buf << "eoclip\n"

#emit_eofill(_op, buf) ⇒ Object



176
# File 'lib/postsvg/serializer.rb', line 176

def emit_eofill(_op, buf) = buf << "eofill\n"

#emit_fill(_op, buf) ⇒ Object



175
# File 'lib/postsvg/serializer.rb', line 175

def emit_fill(_op, buf) = buf << "fill\n"

#emit_findfont(op, buf) ⇒ Object

Font / text



258
259
260
261
# File 'lib/postsvg/serializer.rb', line 258

def emit_findfont(op, buf)
  name = font_name_value(op.name)
  buf << "/#{name} findfont\n"
end

#emit_forall(_op, buf) ⇒ Object



293
# File 'lib/postsvg/serializer.rb', line 293

def emit_forall(_op, buf) = buf << "forall\n"

#emit_generic_operator(operator, buffer) ⇒ Object



120
121
122
# File 'lib/postsvg/serializer.rb', line 120

def emit_generic_operator(operator, buffer)
  buffer << operator.keyword << "\n"
end

#emit_get(_op, buf) ⇒ Object



289
# File 'lib/postsvg/serializer.rb', line 289

def emit_get(_op, buf) = buf << "get\n"

#emit_getinterval(_op, buf) ⇒ Object



291
# File 'lib/postsvg/serializer.rb', line 291

def emit_getinterval(_op, buf) = buf << "getinterval\n"

#emit_grestore(_op, buf) ⇒ Object



201
# File 'lib/postsvg/serializer.rb', line 201

def emit_grestore(_op, buf) = buf << "grestore\n"

#emit_grestoreall(_op, buf) ⇒ Object



202
# File 'lib/postsvg/serializer.rb', line 202

def emit_grestoreall(_op, buf) = buf << "grestoreall\n"

#emit_gsave(_op, buf) ⇒ Object

Graphics state



200
# File 'lib/postsvg/serializer.rb', line 200

def emit_gsave(_op, buf) = buf << "gsave\n"

#emit_header(buffer) ⇒ Object

All methods below this point are public. They are intentionally exposed (prefixed emit_) so dispatch can reach them via method_defined? without respond_to?.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/postsvg/serializer.rb', line 41

def emit_header(buffer)
  buffer << "%!PS-Adobe-#{DEFAULT_LANGUAGE_LEVEL}.0"
  buffer << " EPSF-3.0" if eps
  buffer << "\n"
  buffer << "%%Creator: #{creator}\n"
  bbox = program.header.bounding_box
  if bbox && !bbox.empty?
    buffer << "%%BoundingBox: " << bbox.map { |v| FormatNumber.call(v) }.join(" ") << "\n"
  end
  if program.header.title
    buffer << "%%Title: #{program.header.title}\n"
  end
  buffer << "%%LanguageLevel: #{DEFAULT_LANGUAGE_LEVEL}\n"
  buffer << "%%EndComments\n"
end

#emit_inline(statement, buffer) ⇒ Object



99
100
101
102
103
104
105
106
107
# File 'lib/postsvg/serializer.rb', line 99

def emit_inline(statement, buffer)
  case statement
  when Model::Literals::Number then buffer << FormatNumber.call(statement.value)
  when Model::Literals::Name then buffer << (statement.literal? ? "/" : "") << statement.value
  when Model::Literals::StringLiteral then buffer << escape_string(statement.value)
  when Model::Literals::HexLiteral then buffer << "<" << statement.value << ">"
  else buffer << statement.class.name.to_s
  end
end

#emit_length(_op, buf) ⇒ Object

Container operators



288
# File 'lib/postsvg/serializer.rb', line 288

def emit_length(_op, buf) = buf << "length\n"

#emit_lineto(op, buf) ⇒ Object



133
134
135
# File 'lib/postsvg/serializer.rb', line 133

def emit_lineto(op, buf)
  buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} lineto\n"
end

#emit_maxlength(_op, buf) ⇒ Object



305
# File 'lib/postsvg/serializer.rb', line 305

def emit_maxlength(_op, buf) = buf << "maxlength\n"

#emit_moveto(op, buf) ⇒ Object

Path



125
126
127
# File 'lib/postsvg/serializer.rb', line 125

def emit_moveto(op, buf)
  buf << "#{FormatNumber.call(op.x)} #{FormatNumber.call(op.y)} moveto\n"
end

#emit_newpath(_op, buf) ⇒ Object



169
170
171
# File 'lib/postsvg/serializer.rb', line 169

def emit_newpath(_op, buf)
  buf << "newpath\n"
end

#emit_operator(operator, buffer) ⇒ Object

Dispatch table: emit_. Falls back to the generic operand+keyword form when no specific method exists.



111
112
113
114
115
116
117
118
# File 'lib/postsvg/serializer.rb', line 111

def emit_operator(operator, buffer)
  method = :"emit_#{operator.visit_name}"
  if self.class.public_method_defined?(method)
    public_send(method, operator, buffer)
  else
    emit_generic_operator(operator, buffer)
  end
end

#emit_put(_op, buf) ⇒ Object



290
# File 'lib/postsvg/serializer.rb', line 290

def emit_put(_op, buf) = buf << "put\n"

#emit_putinterval(_op, buf) ⇒ Object



292
# File 'lib/postsvg/serializer.rb', line 292

def emit_putinterval(_op, buf) = buf << "putinterval\n"

#emit_rcurveto(op, buf) ⇒ Object



147
148
149
150
151
# File 'lib/postsvg/serializer.rb', line 147

def emit_rcurveto(op, buf)
  buf << "#{FormatNumber.call(op.dx1)} #{FormatNumber.call(op.dy1)} " \
        "#{FormatNumber.call(op.dx2)} #{FormatNumber.call(op.dy2)} " \
        "#{FormatNumber.call(op.dx3)} #{FormatNumber.call(op.dy3)} rcurveto\n"
end

#emit_rlineto(op, buf) ⇒ Object



137
138
139
# File 'lib/postsvg/serializer.rb', line 137

def emit_rlineto(op, buf)
  buf << "#{FormatNumber.call(op.dx)} #{FormatNumber.call(op.dy)} rlineto\n"
end

#emit_rmoveto(op, buf) ⇒ Object



129
130
131
# File 'lib/postsvg/serializer.rb', line 129

def emit_rmoveto(op, buf)
  buf << "#{FormatNumber.call(op.dx)} #{FormatNumber.call(op.dy)} rmoveto\n"
end

#emit_rotate(op, buf) ⇒ Object



239
240
241
# File 'lib/postsvg/serializer.rb', line 239

def emit_rotate(op, buf)
  buf << "#{FormatNumber.call(op.angle)} rotate\n"
end

#emit_scale(op, buf) ⇒ Object



235
236
237
# File 'lib/postsvg/serializer.rb', line 235

def emit_scale(op, buf)
  buf << "#{FormatNumber.call(op.sx)} #{FormatNumber.call(op.sy)} scale\n"
end

#emit_scalefont(op, buf) ⇒ Object



263
264
265
# File 'lib/postsvg/serializer.rb', line 263

def emit_scalefont(op, buf)
  buf << "#{FormatNumber.call(op.size)} scalefont\n"
end

#emit_search(_op, buf) ⇒ Object



295
# File 'lib/postsvg/serializer.rb', line 295

def emit_search(_op, buf) = buf << "search\n"

#emit_setcmykcolor(op, buf) ⇒ Object



189
190
191
192
# File 'lib/postsvg/serializer.rb', line 189

def emit_setcmykcolor(op, buf)
  buf << "#{FormatNumber.call(op.cyan)} #{FormatNumber.call(op.magenta)} " \
        "#{FormatNumber.call(op.yellow)} #{FormatNumber.call(op.key)} setcmykcolor\n"
end

#emit_setdash(op, buf) ⇒ Object



220
221
222
223
224
225
226
227
228
# File 'lib/postsvg/serializer.rb', line 220

def emit_setdash(op, buf)
  pattern_str =
    case op.pattern
    when Array then "[#{op.pattern.map { |v| FormatNumber.call(v.to_f) }.join(' ')}]"
    when Numeric then FormatNumber.call(op.pattern.to_f)
    else "[]"
    end
  buf << "#{pattern_str} #{FormatNumber.call(op.offset)} setdash\n"
end

#emit_setfont(_op, buf) ⇒ Object



267
268
269
# File 'lib/postsvg/serializer.rb', line 267

def emit_setfont(_op, buf)
  buf << "setfont\n"
end

#emit_setgray(op, buf) ⇒ Object



185
186
187
# File 'lib/postsvg/serializer.rb', line 185

def emit_setgray(op, buf)
  buf << "#{FormatNumber.call(op.gray)} setgray\n"
end

#emit_sethsbcolor(op, buf) ⇒ Object



194
195
196
197
# File 'lib/postsvg/serializer.rb', line 194

def emit_sethsbcolor(op, buf)
  buf << "#{FormatNumber.call(op.hue)} #{FormatNumber.call(op.saturation)} " \
        "#{FormatNumber.call(op.brightness)} sethsbcolor\n"
end

#emit_setlinecap(op, buf) ⇒ Object



208
209
210
# File 'lib/postsvg/serializer.rb', line 208

def emit_setlinecap(op, buf)
  buf << "#{op.cap_code} setlinecap\n"
end

#emit_setlinejoin(op, buf) ⇒ Object



212
213
214
# File 'lib/postsvg/serializer.rb', line 212

def emit_setlinejoin(op, buf)
  buf << "#{op.join_code} setlinejoin\n"
end

#emit_setlinewidth(op, buf) ⇒ Object



204
205
206
# File 'lib/postsvg/serializer.rb', line 204

def emit_setlinewidth(op, buf)
  buf << "#{FormatNumber.call(op.width)} setlinewidth\n"
end

#emit_setmiterlimit(op, buf) ⇒ Object



216
217
218
# File 'lib/postsvg/serializer.rb', line 216

def emit_setmiterlimit(op, buf)
  buf << "#{FormatNumber.call(op.limit)} setmiterlimit\n"
end

#emit_setrgbcolor(op, buf) ⇒ Object

Color



181
182
183
# File 'lib/postsvg/serializer.rb', line 181

def emit_setrgbcolor(op, buf)
  buf << "#{FormatNumber.call(op.red)} #{FormatNumber.call(op.green)} #{FormatNumber.call(op.blue)} setrgbcolor\n"
end

#emit_show(op, buf) ⇒ Object



271
272
273
# File 'lib/postsvg/serializer.rb', line 271

def emit_show(op, buf)
  buf << "#{escape_string(string_value(op.text))} show\n"
end

#emit_statement(statement, buffer) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/postsvg/serializer.rb', line 63

def emit_statement(statement, buffer)
  case statement
  when Model::Literals::Number
    buffer << FormatNumber.call(statement.value) << "\n"
  when Model::Literals::Name
    prefix = statement.literal? ? "/" : ""
    buffer << prefix << statement.value << "\n"
  when Model::Literals::StringLiteral
    buffer << escape_string(statement.value) << "\n"
  when Model::Literals::HexLiteral
    buffer << "<" << statement.value << ">\n"
  when Model::Literals::ArrayLiteral
    buffer << "[ "
    statement.elements.each { |e| emit_inline(e, buffer); buffer << " " }
    buffer << "]\n"
  when Model::Literals::Procedure
    buffer << "{\n"
    statement.body.each { |s| emit_statement(s, buffer) }
    buffer << "}\n"
  when Model::Literals::Dictionary
    buffer << "<<\n"
    statement.entries.each do |k, v|
      buffer << "/#{k} "
      emit_inline(v, buffer)
      buffer << "\n"
    end
    buffer << ">>\n"
  when Model::Operator
    emit_operator(statement, buffer)
  when Model::UnknownOperator
    buffer << "% unhandled operator: #{statement.keyword}\n"
  when Model::InvokeProcedure
    buffer << statement.name << "\n"
  end
end

#emit_string(_op, buf) ⇒ Object



298
# File 'lib/postsvg/serializer.rb', line 298

def emit_string(_op, buf) = buf << "string\n"

#emit_stringwidth(_op, buf) ⇒ Object



279
280
281
# File 'lib/postsvg/serializer.rb', line 279

def emit_stringwidth(_op, buf)
  buf << "% stringwidth: no runtime result captured\n"
end

#emit_stroke(_op, buf) ⇒ Object

Painting



174
# File 'lib/postsvg/serializer.rb', line 174

def emit_stroke(_op, buf) = buf << "stroke\n"

#emit_token(_op, buf) ⇒ Object



297
# File 'lib/postsvg/serializer.rb', line 297

def emit_token(_op, buf) = buf << "token\n"

#emit_translate(op, buf) ⇒ Object

Transformations



231
232
233
# File 'lib/postsvg/serializer.rb', line 231

def emit_translate(op, buf)
  buf << "#{FormatNumber.call(op.tx)} #{FormatNumber.call(op.ty)} translate\n"
end

#emit_xyshow(_op, buf) ⇒ Object



275
276
277
# File 'lib/postsvg/serializer.rb', line 275

def emit_xyshow(_op, buf)
  buf << "% xyshow: per-glyph advances not serialized\n"
end

#escape_string(text) ⇒ Object



249
250
251
252
253
254
255
# File 'lib/postsvg/serializer.rb', line 249

def escape_string(text)
  escaped = text.to_s.gsub(/[\(\)\\]/) { |c| "\\#{c}" }
                      .gsub("\n", "\\n")
                      .gsub("\r", "\\r")
                      .gsub("\t", "\\t")
  "(#{escaped})"
end

#font_name_value(value) ⇒ Object



307
308
309
310
311
312
313
314
# File 'lib/postsvg/serializer.rb', line 307

def font_name_value(value)
  case value
  when Model::Literals::Name then value.value
  when Model::Literals::StringLiteral then value.value
  when String then value
  else value.to_s
  end
end

#string_value(value) ⇒ Object



316
317
318
319
320
321
322
323
# File 'lib/postsvg/serializer.rb', line 316

def string_value(value)
  case value
  when Model::Literals::StringLiteral then value.value
  when Model::Literals::HexLiteral then value.bytes
  when String then value
  else value.to_s
  end
end