qr_reader 0.0.1 → 0.0.2

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.
@@ -0,0 +1,1205 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QrReader
4
+ module Formats
5
+ module PDF
6
+ # Glyph outlines of embedded font programs (TrueType, CFF, Type 1),
7
+ # enough to draw text set in a barcode font: each glyph becomes closed
8
+ # polygons (flat [x0, y0, x1, y1, ...] Arrays in glyph units, curves
9
+ # flattened). Hinting is ignored. Corrupt programs raise ImageError
10
+ # (via INTERNAL_ERRORS at the caller) or yield no outline.
11
+ module FontPrograms
12
+ # Line segments per curve.
13
+ CURVE_STEPS = 4
14
+ # Interpreter limits per glyph (subroutine nesting, operators run,
15
+ # points made) and composite glyph nesting.
16
+ MAX_CALL_DEPTH = 10
17
+ MAX_OPERATORS = 50_000
18
+ MAX_POINTS = 20_000
19
+ MAX_COMPONENT_DEPTH = 8
20
+
21
+ # Collects contours (flat point Arrays) from moveto/lineto/curveto.
22
+ class Pen
23
+ attr_reader :polys
24
+
25
+ def initialize
26
+ @polys = []
27
+ @current = nil
28
+ @points = 0
29
+ @x = @y = 0.0
30
+ end
31
+
32
+ attr_reader :x, :y
33
+
34
+ # Moves the current point without starting a contour.
35
+ def set_point(x, y)
36
+ @x = x.to_f
37
+ @y = y.to_f
38
+ end
39
+
40
+ def move_to(x, y)
41
+ close
42
+ @x = x.to_f
43
+ @y = y.to_f
44
+ @current = [@x, @y]
45
+ end
46
+
47
+ def line_to(x, y)
48
+ move_to(@x, @y) unless @current
49
+ @x = x.to_f
50
+ @y = y.to_f
51
+ add(@x, @y)
52
+ end
53
+
54
+ def curve_to(x1, y1, x2, y2, x3, y3)
55
+ move_to(@x, @y) unless @current
56
+ x0 = @x
57
+ y0 = @y
58
+ i = 1
59
+ while i <= CURVE_STEPS
60
+ t = i.to_f / CURVE_STEPS
61
+ u = 1 - t
62
+ a = u * u * u
63
+ b = 3 * u * u * t
64
+ c = 3 * u * t * t
65
+ d = t * t * t
66
+ add(a * x0 + b * x1 + c * x2 + d * x3, a * y0 + b * y1 + c * y2 + d * y3)
67
+ i += 1
68
+ end
69
+ @x = x3.to_f
70
+ @y = y3.to_f
71
+ end
72
+
73
+ def quad_to(x1, y1, x2, y2)
74
+ x0 = @x
75
+ y0 = @y
76
+ curve_to(x0 + 2.0 / 3 * (x1 - x0), y0 + 2.0 / 3 * (y1 - y0),
77
+ x2 + 2.0 / 3 * (x1 - x2), y2 + 2.0 / 3 * (y1 - y2), x2, y2)
78
+ end
79
+
80
+ def close
81
+ @polys << @current if @current && @current.size >= 6
82
+ @current = nil
83
+ end
84
+
85
+ private
86
+
87
+ def add(x, y)
88
+ raise ImageError, "font glyph has too many points" if (@points += 1) > MAX_POINTS
89
+
90
+ @current << x << y
91
+ end
92
+ end
93
+
94
+ # Big-endian readers over a binary String.
95
+ module Bytes
96
+ module_function
97
+
98
+ def u8(d, o) = d.getbyte(o) || raise(ImageError, "font data ends early")
99
+
100
+ def u16(d, o) = (u8(d, o) << 8) | u8(d, o + 1)
101
+
102
+ def s16(d, o)
103
+ v = u16(d, o)
104
+ v >= 0x8000 ? v - 0x10000 : v
105
+ end
106
+
107
+ def u32(d, o) = (u16(d, o) << 16) | u16(d, o + 2)
108
+
109
+ def s32(d, o)
110
+ v = u32(d, o)
111
+ v >= 0x80000000 ? v - 0x100000000 : v
112
+ end
113
+ end
114
+
115
+ # A TrueType (or OpenType) font: glyph outlines from 'glyf', advance
116
+ # widths from 'hmtx', character maps from 'cmap'. An OpenType font
117
+ # with CFF outlines answers #cff instead.
118
+ class TrueType
119
+ include Bytes
120
+
121
+ attr_reader :units_per_em, :cff
122
+
123
+ def initialize(data)
124
+ @data = data
125
+ @tables = {}
126
+ count = u16(data, 4)
127
+ raise ImageError, "TrueType font without tables" if count.zero? || count > 512
128
+
129
+ count.times do |i|
130
+ rec = 12 + i * 16
131
+ tag = data.byteslice(rec, 4)
132
+ offset = u32(data, rec + 8)
133
+ length = u32(data, rec + 12)
134
+ @tables[tag] = [offset, length] if offset + length <= data.bytesize
135
+ end
136
+ head = table("head") or raise ImageError, "TrueType font without a head table"
137
+ @units_per_em = u16(data, head + 18)
138
+ @units_per_em = 1000 if @units_per_em < 16
139
+ @long_loca = s16(data, head + 50) == 1
140
+ @num_glyphs = (maxp = table("maxp")) ? u16(data, maxp + 4) : 0
141
+ @cff = (offset = table("CFF ")) ? CFF.new(data.byteslice(offset, @tables["CFF "][1])) : nil
142
+ @cmaps = {}
143
+ read_cmaps
144
+ end
145
+
146
+ # Glyph index for character +code+ in the (platform, encoding)
147
+ # cmap, or nil.
148
+ def lookup(platform, encoding, code)
149
+ cmap = @cmaps[[platform, encoding]] or return nil
150
+ gid = cmap_lookup(cmap, code)
151
+ gid&.positive? ? gid : nil
152
+ end
153
+
154
+ def cmap?(platform, encoding)
155
+ @cmaps.key?([platform, encoding])
156
+ end
157
+
158
+ def glyph_count
159
+ @num_glyphs
160
+ end
161
+
162
+ # Contours of glyph +gid+ in font units ([] for none).
163
+ def outline(gid)
164
+ return @cff.outline(gid) if @cff
165
+
166
+ pen = Pen.new
167
+ draw(gid, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], pen, 0)
168
+ pen.close
169
+ pen.polys
170
+ end
171
+
172
+ # Advance width of +gid+ in font units, or nil.
173
+ def advance(gid)
174
+ hhea = table("hhea") or return nil
175
+ hmtx = table("hmtx") or return nil
176
+ metrics = u16(@data, hhea + 34)
177
+ return nil if metrics.zero?
178
+
179
+ u16(@data, hmtx + 4 * (gid < metrics ? gid : metrics - 1))
180
+ end
181
+
182
+ private
183
+
184
+ def u16(d, o) = Bytes.u16(d, o)
185
+ def s16(d, o) = Bytes.s16(d, o)
186
+ def u32(d, o) = Bytes.u32(d, o)
187
+
188
+ def table(tag)
189
+ @tables[tag]&.first
190
+ end
191
+
192
+ def read_cmaps
193
+ cmap = table("cmap") or return
194
+ count = u16(@data, cmap + 2)
195
+ [count, 64].min.times do |i|
196
+ rec = cmap + 4 + i * 8
197
+ @cmaps[[u16(@data, rec), u16(@data, rec + 2)]] ||= cmap + u32(@data, rec + 4)
198
+ end
199
+ end
200
+
201
+ def cmap_lookup(at, code)
202
+ d = @data
203
+ case u16(d, at)
204
+ when 0
205
+ code < 256 ? d.getbyte(at + 6 + code) : nil
206
+ when 4
207
+ segs = u16(d, at + 6) / 2
208
+ ends = at + 14
209
+ starts = ends + 2 * segs + 2
210
+ deltas = starts + 2 * segs
211
+ ranges = deltas + 2 * segs
212
+ i = 0
213
+ while i < segs
214
+ if code <= u16(d, ends + 2 * i)
215
+ start = u16(d, starts + 2 * i)
216
+ return nil if code < start
217
+
218
+ range = u16(d, ranges + 2 * i)
219
+ delta = u16(d, deltas + 2 * i)
220
+ return (code + delta) & 0xFFFF if range.zero?
221
+
222
+ gid = u16(d, ranges + 2 * i + range + 2 * (code - start))
223
+ return gid.zero? ? nil : (gid + delta) & 0xFFFF
224
+ end
225
+ i += 1
226
+ end
227
+ nil
228
+ when 6
229
+ first = u16(d, at + 6)
230
+ count = u16(d, at + 8)
231
+ code >= first && code < first + count ? u16(d, at + 10 + 2 * (code - first)) : nil
232
+ when 12
233
+ groups = u32(d, at + 12)
234
+ [groups, 100_000].min.times do |i|
235
+ g = at + 16 + 12 * i
236
+ start = u32(d, g)
237
+ stop = u32(d, g + 4)
238
+ return u32(d, g + 8) + code - start if code.between?(start, stop)
239
+ end
240
+ nil
241
+ end
242
+ end
243
+
244
+ def glyph_range(gid)
245
+ loca = table("loca") or return nil
246
+ glyf = table("glyf") or return nil
247
+ return nil if gid.negative? || gid >= @num_glyphs
248
+
249
+ if @long_loca
250
+ start = u32(@data, loca + 4 * gid)
251
+ stop = u32(@data, loca + 4 * gid + 4)
252
+ else
253
+ start = 2 * u16(@data, loca + 2 * gid)
254
+ stop = 2 * u16(@data, loca + 2 * gid + 2)
255
+ end
256
+ stop > start ? [glyf + start, glyf + stop] : nil
257
+ end
258
+
259
+ # Draws glyph +gid+ transformed by +m+ ([a b c d e f]) into +pen+.
260
+ def draw(gid, m, pen, depth)
261
+ range = glyph_range(gid) or return
262
+ at = range.first
263
+ contours = s16(@data, at)
264
+ if contours.negative?
265
+ composite(at + 10, m, pen, depth)
266
+ else
267
+ simple(at, contours, m, pen)
268
+ end
269
+ end
270
+
271
+ def simple(at, contours, m, pen)
272
+ d = @data
273
+ ends = Array.new(contours) { |i| u16(d, at + 10 + 2 * i) }
274
+ count = contours.zero? ? 0 : ends.last + 1
275
+ raise ImageError, "TrueType glyph has too many points" if count > MAX_POINTS
276
+
277
+ pos = at + 10 + 2 * contours
278
+ pos += 2 + u16(d, pos) # instructions
279
+ flags = []
280
+ while flags.size < count
281
+ f = Bytes.u8(d, pos)
282
+ pos += 1
283
+ flags << f
284
+ next unless f & 8 != 0
285
+
286
+ repeat = Bytes.u8(d, pos)
287
+ pos += 1
288
+ repeat.times { flags << f }
289
+ end
290
+ xs, pos = coordinates(flags, count, pos, 2, 16)
291
+ ys, = coordinates(flags, count, pos, 4, 32)
292
+ a, b, c, dd, e, f = m
293
+ first = 0
294
+ ends.each do |last|
295
+ points = (first..last).map do |i|
296
+ x = xs[i]
297
+ y = ys[i]
298
+ [a * x + c * y + e, b * x + dd * y + f, flags[i] & 1 != 0]
299
+ end
300
+ contour(points, pen)
301
+ first = last + 1
302
+ end
303
+ end
304
+
305
+ def coordinates(flags, count, pos, short_bit, same_bit)
306
+ values = Array.new(count)
307
+ v = 0
308
+ i = 0
309
+ while i < count
310
+ f = flags[i]
311
+ if f & short_bit != 0
312
+ delta = Bytes.u8(@data, pos)
313
+ pos += 1
314
+ v += f & same_bit != 0 ? delta : -delta
315
+ elsif f & same_bit == 0
316
+ v += Bytes.s16(@data, pos)
317
+ pos += 2
318
+ end
319
+ values[i] = v
320
+ i += 1
321
+ end
322
+ [values, pos]
323
+ end
324
+
325
+ # One quadratic contour (on/off-curve points) into +pen+.
326
+ def contour(points, pen)
327
+ return if points.size < 2
328
+
329
+ start = points.index { |p| p[2] }
330
+ if start
331
+ points = points.rotate(start)
332
+ sx, sy = points[0]
333
+ else # all off-curve: start between the first two
334
+ sx = (points[0][0] + points[1][0]) / 2
335
+ sy = (points[0][1] + points[1][1]) / 2
336
+ points = [[sx, sy, true]] + points
337
+ end
338
+ pen.move_to(sx, sy)
339
+ control = nil
340
+ (points.drop(1) + [[sx, sy, true]]).each do |x, y, on|
341
+ if on
342
+ control ? pen.quad_to(control[0], control[1], x, y) : pen.line_to(x, y)
343
+ control = nil
344
+ elsif control
345
+ mx = (control[0] + x) / 2
346
+ my = (control[1] + y) / 2
347
+ pen.quad_to(control[0], control[1], mx, my)
348
+ control = [x, y]
349
+ else
350
+ control = [x, y]
351
+ end
352
+ end
353
+ pen.close
354
+ end
355
+
356
+ def composite(pos, m, pen, depth)
357
+ raise ImageError, "TrueType composite glyphs nested too deeply" if depth > MAX_COMPONENT_DEPTH
358
+
359
+ d = @data
360
+ loop do
361
+ flags = u16(d, pos)
362
+ gid = u16(d, pos + 2)
363
+ pos += 4
364
+ if flags & 1 != 0
365
+ dx = s16(d, pos)
366
+ dy = s16(d, pos + 2)
367
+ pos += 4
368
+ else
369
+ dx = Bytes.u8(d, pos)
370
+ dy = Bytes.u8(d, pos + 1)
371
+ dx -= 256 if dx >= 128
372
+ dy -= 256 if dy >= 128
373
+ pos += 2
374
+ end
375
+ dx = dy = 0 if flags & 2 == 0 # point matching: not supported
376
+ a = dd = 1.0
377
+ b = c = 0.0
378
+ if flags & 8 != 0
379
+ a = dd = s16(d, pos) / 16_384.0
380
+ pos += 2
381
+ elsif flags & 0x40 != 0
382
+ a = s16(d, pos) / 16_384.0
383
+ dd = s16(d, pos + 2) / 16_384.0
384
+ pos += 4
385
+ elsif flags & 0x80 != 0
386
+ a = s16(d, pos) / 16_384.0
387
+ b = s16(d, pos + 2) / 16_384.0
388
+ c = s16(d, pos + 4) / 16_384.0
389
+ dd = s16(d, pos + 6) / 16_384.0
390
+ pos += 8
391
+ end
392
+ ma, mb, mc, md, me, mf = m
393
+ child = [a * ma + b * mc, a * mb + b * md, c * ma + dd * mc, c * mb + dd * md,
394
+ dx * ma + dy * mc + me, dx * mb + dy * md + mf]
395
+ draw(gid, child, pen, depth + 1)
396
+ break if flags & 0x20 == 0
397
+ end
398
+ end
399
+ end
400
+
401
+ # A CFF font (bare, as in /FontFile3 /Type1C or /CIDFontType0C, or
402
+ # the 'CFF ' table of an OpenType font) with Type 2 charstrings.
403
+ class CFF
404
+ include Bytes
405
+
406
+ attr_reader :font_matrix
407
+
408
+ def initialize(data)
409
+ @data = data
410
+ pos = Bytes.u8(data, 2)
411
+ _names, pos = index(pos)
412
+ tops, pos = index(pos)
413
+ @strings, pos = index(pos)
414
+ @global_subrs, = index(pos)
415
+ top = dict(tops.first || raise(ImageError, "CFF font without a Top DICT"))
416
+ @font_matrix = top[1207] || [0.001, 0.0, 0.0, 0.001, 0.0, 0.0]
417
+ @charstrings, = index(int(top[17]) || raise(ImageError, "CFF font without CharStrings"))
418
+ @cid = top.key?(1230)
419
+ if @cid
420
+ fds, = index(int(top[1236]) || raise(ImageError, "CID CFF font without FDArray"))
421
+ @privates = fds.map { |fd| private_dict(dict(fd)) }
422
+ @fd_select = fd_select(int(top[1237]))
423
+ else
424
+ @privates = [private_dict(top)]
425
+ end
426
+ @charset = charset(int(top[15]) || 0)
427
+ @encoding = encoding(int(top[16]) || 0)
428
+ end
429
+
430
+ def cid?
431
+ @cid
432
+ end
433
+
434
+ def glyph_count
435
+ @charstrings.size
436
+ end
437
+
438
+ # Glyph index by glyph name (non-CID fonts) or CID (CID fonts).
439
+ def gid_for_name(name)
440
+ @names ||= @charset.each_with_index.to_h { |sid, gid| [string(sid), gid] }
441
+ @names[name]
442
+ end
443
+
444
+ def gid_for_cid(cid)
445
+ @cids ||= @charset.each_with_index.to_h { |c, gid| [c, gid] }
446
+ @cids[cid]
447
+ end
448
+
449
+ # The font's built-in encoding: code -> gid.
450
+ def gid_for_code(code)
451
+ @encoding[code]
452
+ end
453
+
454
+ def outline(gid)
455
+ data = @charstrings[gid] or return []
456
+ fd = @fd_select ? @fd_select[gid] || 0 : 0
457
+ priv = @privates[fd] || @privates.first
458
+ Type2.new(data, @global_subrs, priv[:subrs]).run
459
+ end
460
+
461
+ def advance(gid)
462
+ data = @charstrings[gid] or return nil
463
+ fd = @fd_select ? @fd_select[gid] || 0 : 0
464
+ priv = @privates[fd] || @privates.first
465
+ interpreter = Type2.new(data, @global_subrs, priv[:subrs])
466
+ interpreter.run
467
+ w = interpreter.width
468
+ w ? priv[:nominal] + w : priv[:default]
469
+ end
470
+
471
+ private
472
+
473
+ def string(sid)
474
+ return FontTables::CFF_STANDARD_STRINGS[sid] if sid < 391
475
+
476
+ @strings[sid - 391]
477
+ end
478
+
479
+ def int(value)
480
+ value.is_a?(Array) ? value.last&.to_i : value&.to_i
481
+ end
482
+
483
+ # [Array of item Strings, position after the INDEX].
484
+ def index(pos)
485
+ count = u16(@data, pos)
486
+ return [[], pos + 2] if count.zero?
487
+
488
+ size = Bytes.u8(@data, pos + 2)
489
+ raise ImageError, "invalid CFF INDEX" unless size.between?(1, 4)
490
+
491
+ offsets = Array.new(count + 1) { |i| offset(pos + 3 + i * size, size) }
492
+ base = pos + 2 + (count + 1) * size
493
+ items = Array.new(count) do |i|
494
+ start = offsets[i]
495
+ stop = offsets[i + 1]
496
+ raise ImageError, "invalid CFF INDEX" if start < 1 || stop < start
497
+
498
+ @data.byteslice(base + start, stop - start) || ""
499
+ end
500
+ [items, base + offsets.last]
501
+ end
502
+
503
+ def offset(pos, size)
504
+ v = 0
505
+ size.times { |i| v = (v << 8) | Bytes.u8(@data, pos + i) }
506
+ v
507
+ end
508
+
509
+ # DICT data -> {operator => operands (Array) or single operand}.
510
+ def dict(data)
511
+ out = {}
512
+ operands = []
513
+ pos = 0
514
+ while pos < data.bytesize
515
+ b = data.getbyte(pos)
516
+ if b <= 21
517
+ op = b
518
+ pos += 1
519
+ if b == 12
520
+ op = 1200 + data.getbyte(pos).to_i
521
+ pos += 1
522
+ end
523
+ out[op] = operands.size == 1 ? operands[0] : operands
524
+ operands = []
525
+ elsif b == 28
526
+ operands << Bytes.s16(data, pos + 1)
527
+ pos += 3
528
+ elsif b == 29
529
+ operands << Bytes.s32(data, pos + 1)
530
+ pos += 5
531
+ elsif b == 30
532
+ value, pos = real(data, pos + 1)
533
+ operands << value
534
+ elsif b >= 32 && b <= 246
535
+ operands << b - 139
536
+ pos += 1
537
+ elsif b >= 247 && b <= 250
538
+ operands << (b - 247) * 256 + data.getbyte(pos + 1).to_i + 108
539
+ pos += 2
540
+ elsif b >= 251 && b <= 254
541
+ operands << -(b - 251) * 256 - data.getbyte(pos + 1).to_i - 108
542
+ pos += 2
543
+ else
544
+ pos += 1
545
+ end
546
+ end
547
+ out
548
+ end
549
+
550
+ def real(data, pos)
551
+ text = +""
552
+ loop do
553
+ b = data.getbyte(pos) or break
554
+ pos += 1
555
+ done = false
556
+ [b >> 4, b & 15].each do |n|
557
+ case n
558
+ when 0..9 then text << n.to_s
559
+ when 0xA then text << "."
560
+ when 0xB then text << "E"
561
+ when 0xC then text << "E-"
562
+ when 0xE then text << "-"
563
+ when 0xF then done = true
564
+ end
565
+ break if done
566
+ end
567
+ break if done
568
+ end
569
+ [text.to_f, pos]
570
+ end
571
+
572
+ def private_dict(top)
573
+ size, offset = top[18]
574
+ priv = size && offset ? dict(@data.byteslice(offset, size) || "") : {}
575
+ subrs = []
576
+ if (rel = int(priv[19]))
577
+ subrs, = index(offset + rel)
578
+ end
579
+ { subrs: subrs, default: (priv[20].is_a?(Numeric) ? priv[20] : 0),
580
+ nominal: (priv[21].is_a?(Numeric) ? priv[21] : 0) }
581
+ end
582
+
583
+ # gid -> SID (or CID).
584
+ def charset(offset)
585
+ count = @charstrings.size
586
+ return Array.new(count) { |i| i } if offset <= 2 # ISOAdobe (and the expert sets, approximately)
587
+
588
+ out = [0]
589
+ format = Bytes.u8(@data, offset)
590
+ pos = offset + 1
591
+ while out.size < count
592
+ case format
593
+ when 0
594
+ out << u16(@data, pos)
595
+ pos += 2
596
+ when 1, 2
597
+ first = u16(@data, pos)
598
+ left = format == 1 ? Bytes.u8(@data, pos + 2) : u16(@data, pos + 2)
599
+ pos += format == 1 ? 3 : 4
600
+ (left + 1).times { |i| out << first + i if out.size < count }
601
+ else raise ImageError, "invalid CFF charset format #{format}"
602
+ end
603
+ end
604
+ out
605
+ end
606
+
607
+ # code -> gid.
608
+ def encoding(offset)
609
+ out = {}
610
+ if offset <= 1
611
+ return out if offset == 1 || @cid
612
+
613
+ # Standard encoding through the charset names.
614
+ FontTables::STANDARD_ENCODING.each_with_index do |name, code|
615
+ gid = name && gid_for_name(name)
616
+ out[code] = gid if gid
617
+ end
618
+ return out
619
+ end
620
+ format = Bytes.u8(@data, offset)
621
+ case format & 0x7F
622
+ when 0
623
+ n = Bytes.u8(@data, offset + 1)
624
+ n.times { |i| out[Bytes.u8(@data, offset + 2 + i)] = i + 1 }
625
+ when 1
626
+ n = Bytes.u8(@data, offset + 1)
627
+ gid = 1
628
+ n.times do |i|
629
+ first = Bytes.u8(@data, offset + 2 + 2 * i)
630
+ left = Bytes.u8(@data, offset + 3 + 2 * i)
631
+ (left + 1).times do |j|
632
+ out[first + j] = gid
633
+ gid += 1
634
+ end
635
+ end
636
+ end
637
+ out
638
+ end
639
+
640
+ def fd_select(offset)
641
+ return nil unless offset
642
+
643
+ count = @charstrings.size
644
+ case Bytes.u8(@data, offset)
645
+ when 0
646
+ Array.new(count) { |i| Bytes.u8(@data, offset + 1 + i) }
647
+ when 3
648
+ ranges = u16(@data, offset + 1)
649
+ out = Array.new(count, 0)
650
+ ranges.times do |i|
651
+ r = offset + 3 + 3 * i
652
+ first = u16(@data, r)
653
+ fd = Bytes.u8(@data, r + 2)
654
+ stop = i + 1 < ranges ? u16(@data, r + 3) : u16(@data, offset + 3 + 3 * ranges)
655
+ (first...[stop, count].min).each { |g| out[g] = fd }
656
+ end
657
+ out
658
+ end
659
+ end
660
+
661
+ def u16(d, o) = Bytes.u16(d, o)
662
+ end
663
+
664
+ # Type 2 charstring interpreter (Adobe TN 5177) producing contours.
665
+ class Type2
666
+ attr_reader :width
667
+
668
+ def initialize(data, global_subrs, local_subrs)
669
+ @data = data
670
+ @gsubrs = global_subrs
671
+ @lsubrs = local_subrs
672
+ @pen = Pen.new
673
+ @stack = []
674
+ @stems = 0
675
+ @width = nil
676
+ @first = true
677
+ @ops = 0
678
+ @transient = {}
679
+ end
680
+
681
+ def run
682
+ exec(@data, 0)
683
+ @pen.close
684
+ @pen.polys
685
+ end
686
+
687
+ private
688
+
689
+ def bias(subrs)
690
+ n = subrs.size
691
+ n < 1240 ? 107 : (n < 33_900 ? 1131 : 32_768)
692
+ end
693
+
694
+ # Takes the width argument off the stack at the first
695
+ # stack-clearing operator when there is one more than +even+ says.
696
+ def take_width(extra)
697
+ return unless @first
698
+
699
+ @first = false
700
+ @width = @stack.shift if extra
701
+ end
702
+
703
+ def exec(code, depth)
704
+ raise ImageError, "charstring subroutines nested too deeply" if depth > MAX_CALL_DEPTH
705
+
706
+ pen = @pen
707
+ s = @stack
708
+ pos = 0
709
+ while pos < code.bytesize
710
+ raise ImageError, "charstring runs too long" if (@ops += 1) > MAX_OPERATORS
711
+
712
+ b = code.getbyte(pos)
713
+ pos += 1
714
+ if b >= 32 || b == 28
715
+ if b == 28
716
+ s << Bytes.s16(code, pos)
717
+ pos += 2
718
+ elsif b <= 246
719
+ s << b - 139
720
+ elsif b <= 250
721
+ s << (b - 247) * 256 + code.getbyte(pos).to_i + 108
722
+ pos += 1
723
+ elsif b <= 254
724
+ s << -(b - 251) * 256 - code.getbyte(pos).to_i - 108
725
+ pos += 1
726
+ else
727
+ s << Bytes.s32(code, pos) / 65_536.0
728
+ pos += 4
729
+ end
730
+ next
731
+ end
732
+ case b
733
+ when 1, 3, 18, 23 # hstem vstem hstemhm vstemhm
734
+ take_width(s.size.odd?)
735
+ @stems += s.size / 2
736
+ s.clear
737
+ when 19, 20 # hintmask cntrmask
738
+ take_width(s.size.odd?)
739
+ @stems += s.size / 2
740
+ s.clear
741
+ pos += (@stems + 7) / 8
742
+ when 21 # rmoveto
743
+ take_width(s.size > 2)
744
+ pen.move_to(pen.x + s[-2].to_f, pen.y + s[-1].to_f) if s.size >= 2
745
+ s.clear
746
+ when 22 # hmoveto
747
+ take_width(s.size > 1)
748
+ pen.move_to(pen.x + s[-1].to_f, pen.y) unless s.empty?
749
+ s.clear
750
+ when 4 # vmoveto
751
+ take_width(s.size > 1)
752
+ pen.move_to(pen.x, pen.y + s[-1].to_f) unless s.empty?
753
+ s.clear
754
+ when 5 # rlineto
755
+ (s.size / 2).times { |i| pen.line_to(pen.x + s[2 * i], pen.y + s[2 * i + 1]) }
756
+ s.clear
757
+ when 6, 7 # hlineto vlineto
758
+ horizontal = b == 6
759
+ s.each do |v|
760
+ horizontal ? pen.line_to(pen.x + v, pen.y) : pen.line_to(pen.x, pen.y + v)
761
+ horizontal = !horizontal
762
+ end
763
+ s.clear
764
+ when 8 # rrcurveto
765
+ (s.size / 6).times { |i| rcurve(*s[6 * i, 6]) }
766
+ s.clear
767
+ when 24 # rcurveline
768
+ n = (s.size - 2) / 6
769
+ n.times { |i| rcurve(*s[6 * i, 6]) }
770
+ pen.line_to(pen.x + s[6 * n], pen.y + s[6 * n + 1]) if s.size >= 6 * n + 2
771
+ s.clear
772
+ when 25 # rlinecurve
773
+ n = (s.size - 6) / 2
774
+ n.times { |i| pen.line_to(pen.x + s[2 * i], pen.y + s[2 * i + 1]) }
775
+ rcurve(*s[2 * n, 6]) if s.size >= 2 * n + 6
776
+ s.clear
777
+ when 26 # vvcurveto
778
+ dx1 = s.size.odd? ? s.shift : 0
779
+ (s.size / 4).times do |i|
780
+ rcurve(i.zero? ? dx1 : 0, s[4 * i], s[4 * i + 1], s[4 * i + 2], 0, s[4 * i + 3])
781
+ end
782
+ s.clear
783
+ when 27 # hhcurveto
784
+ dy1 = s.size.odd? ? s.shift : 0
785
+ (s.size / 4).times do |i|
786
+ rcurve(s[4 * i], i.zero? ? dy1 : 0, s[4 * i + 1], s[4 * i + 2], s[4 * i + 3], 0)
787
+ end
788
+ s.clear
789
+ when 30, 31 # vhcurveto hvcurveto
790
+ alternating(s, b == 31)
791
+ s.clear
792
+ when 10, 29 # callsubr callgsubr
793
+ subrs = b == 10 ? @lsubrs : @gsubrs
794
+ n = s.pop.to_i + bias(subrs)
795
+ sub = subrs[n] or raise ImageError, "charstring calls a missing subroutine"
796
+ return :endchar if exec(sub, depth + 1) == :endchar
797
+ when 11 # return
798
+ return nil
799
+ when 14 # endchar
800
+ take_width(s.size == 1 || s.size == 5)
801
+ pen.close
802
+ return :endchar
803
+ when 12
804
+ op = code.getbyte(pos).to_i
805
+ pos += 1
806
+ escape(op, s)
807
+ else
808
+ s.clear
809
+ end
810
+ end
811
+ nil
812
+ end
813
+
814
+ def rcurve(dx1, dy1, dx2, dy2, dx3, dy3)
815
+ pen = @pen
816
+ x1 = pen.x + dx1
817
+ y1 = pen.y + dy1
818
+ x2 = x1 + dx2
819
+ y2 = y1 + dy2
820
+ pen.curve_to(x1, y1, x2, y2, x2 + dx3, y2 + dy3)
821
+ end
822
+
823
+ # hvcurveto (horizontal first) / vhcurveto.
824
+ def alternating(s, horizontal)
825
+ i = 0
826
+ while s.size - i >= 4
827
+ last = s.size - i == 5
828
+ if horizontal
829
+ rcurve(s[i], 0, s[i + 1], s[i + 2], last ? s[i + 4] : 0, s[i + 3])
830
+ else
831
+ rcurve(0, s[i], s[i + 1], s[i + 2], s[i + 3], last ? s[i + 4] : 0)
832
+ end
833
+ horizontal = !horizontal
834
+ i += last ? 5 : 4
835
+ end
836
+ end
837
+
838
+ def escape(op, s)
839
+ case op
840
+ when 35 # flex
841
+ rcurve(*s[0, 6]) if s.size >= 12
842
+ rcurve(*s[6, 6]) if s.size >= 12
843
+ when 34 # hflex
844
+ if s.size >= 7
845
+ dx1, dx2, dy2, dx3, dx4, dx5, dx6 = s
846
+ rcurve(dx1, 0, dx2, dy2, dx3, 0)
847
+ rcurve(dx4, 0, dx5, -dy2, dx6, 0)
848
+ end
849
+ when 36 # hflex1
850
+ if s.size >= 9
851
+ dx1, dy1, dx2, dy2, dx3, dx4, dx5, dy5, dx6 = s
852
+ rcurve(dx1, dy1, dx2, dy2, dx3, 0)
853
+ rcurve(dx4, 0, dx5, dy5, dx6, -(dy1 + dy2 + dy5))
854
+ end
855
+ when 37 # flex1
856
+ if s.size >= 11
857
+ d = s[0, 10]
858
+ dx = d[0] + d[2] + d[4] + d[6] + d[8]
859
+ dy = d[1] + d[3] + d[5] + d[7] + d[9]
860
+ rcurve(*d[0, 6])
861
+ if dx.abs > dy.abs
862
+ rcurve(d[6], d[7], d[8], d[9], s[10], -dy)
863
+ else
864
+ rcurve(d[6], d[7], d[8], d[9], -dx, s[10])
865
+ end
866
+ end
867
+ when 3 then binary(s) { |a, b| a != 0 && b != 0 ? 1 : 0 }
868
+ when 4 then binary(s) { |a, b| a != 0 || b != 0 ? 1 : 0 }
869
+ when 5 then s << (s.pop.to_f.zero? ? 1 : 0)
870
+ when 9 then s << s.pop.to_f.abs
871
+ when 10 then binary(s) { |a, b| a + b }
872
+ when 11 then binary(s) { |a, b| a - b }
873
+ when 12 then binary(s) { |a, b| b.zero? ? 0 : a.to_f / b }
874
+ when 14 then s << -s.pop.to_f
875
+ when 15 then binary(s) { |a, b| a == b ? 1 : 0 }
876
+ when 18 then s.pop
877
+ when 20 then (v, i = s.pop(2)) && @transient[i.to_i] = v
878
+ when 21 then s << (@transient[s.pop.to_i] || 0)
879
+ when 22
880
+ a, b, c, d = s.pop(4)
881
+ s << (c.to_f <= d.to_f ? a : b)
882
+ when 23 then s << 0.5
883
+ when 24 then binary(s) { |a, b| a * b }
884
+ when 26 then s << Math.sqrt(s.pop.to_f.abs)
885
+ when 27 then s << s.last if s.last
886
+ when 28
887
+ a, b = s.pop(2)
888
+ s.push(b, a) if b
889
+ when 29
890
+ i = s.pop.to_i
891
+ s << (i.negative? ? s.last : s[-1 - i]) unless s.empty?
892
+ when 30
893
+ n, j = s.pop(2)
894
+ n = n.to_i
895
+ if n.positive? && n <= s.size
896
+ part = s.pop(n).rotate(-j.to_i)
897
+ s.concat(part)
898
+ end
899
+ else
900
+ s.clear
901
+ return
902
+ end
903
+ s.clear if [34, 35, 36, 37].include?(op)
904
+ end
905
+
906
+ def binary(s)
907
+ b = s.pop.to_f
908
+ a = s.pop.to_f
909
+ s << yield(a, b)
910
+ end
911
+ end
912
+
913
+ # A Type 1 font program (/FontFile): the clear-text part with the
914
+ # encoding and font matrix, and the eexec-encrypted part with the
915
+ # charstrings and subroutines.
916
+ class Type1
917
+ attr_reader :font_matrix, :encoding
918
+
919
+ def initialize(data, clear_length = nil)
920
+ eexec = data.index("eexec") or raise ImageError, "Type 1 font without eexec section"
921
+ clear = data.byteslice(0, clear_length&.between?(1, data.bytesize) ? clear_length : eexec)
922
+ @font_matrix = matrix(clear) || [0.001, 0.0, 0.0, 0.001, 0.0, 0.0]
923
+ @encoding = parse_encoding(clear)
924
+ start = eexec + 5
925
+ start += 1 while start < data.bytesize && " \t\r\n".include?(data.byteslice(start, 1))
926
+ private = decrypt(binary(data.byteslice(start, data.bytesize - start)), 55_665).byteslice(4..) || ""
927
+ len_iv = private[%r{/lenIV\s+(-?\d+)}, 1]&.to_i || 4
928
+ @subrs = subrs(private, len_iv)
929
+ @charstrings = charstrings(private, len_iv)
930
+ end
931
+
932
+ def glyph?(name)
933
+ @charstrings.key?(name)
934
+ end
935
+
936
+ def outline(name)
937
+ code = @charstrings[name] or return []
938
+ Type1Charstring.new(self, @subrs).run(code)
939
+ end
940
+
941
+ def advance(name)
942
+ code = @charstrings[name] or return nil
943
+ interpreter = Type1Charstring.new(self, @subrs)
944
+ interpreter.run(code)
945
+ interpreter.width
946
+ end
947
+
948
+ # The charstring of glyph +name+ (for seac).
949
+ def charstring(name)
950
+ @charstrings[name]
951
+ end
952
+
953
+ private
954
+
955
+ def matrix(clear)
956
+ m = clear[%r</FontMatrix\s*[\[{]([^\]}]*)[\]}]>, 1] or return nil
957
+ values = m.split.map(&:to_f)
958
+ values.size == 6 ? values : nil
959
+ end
960
+
961
+ def parse_encoding(clear)
962
+ return :standard if clear.match?(%r{/Encoding\s+StandardEncoding\s+def})
963
+
964
+ out = {}
965
+ clear.scan(%r{dup\s+(\d+)\s*/([^\s/\[\]{}()<>]+)\s+put}) { |code, name| out[code.to_i] = name }
966
+ out
967
+ end
968
+
969
+ def binary(data)
970
+ head = data.byteslice(0, 4) || ""
971
+ return data unless head.match?(/\A\h{4}\z/)
972
+
973
+ [data.gsub(/[^0-9A-Fa-f]/, "")].pack("H*")
974
+ end
975
+
976
+ def decrypt(data, r)
977
+ out = String.new(capacity: data.bytesize, encoding: Encoding::BINARY)
978
+ data.each_byte do |c|
979
+ out << (c ^ (r >> 8))
980
+ r = ((c + r) * 52_845 + 22_719) & 0xFFFF
981
+ end
982
+ out
983
+ end
984
+
985
+ def charstring_bytes(bytes, len_iv)
986
+ return bytes if len_iv.negative?
987
+
988
+ decrypt(bytes, 4330).byteslice(len_iv..) || ""
989
+ end
990
+
991
+ def subrs(private, len_iv)
992
+ out = []
993
+ private.scan(/dup\s+(\d+)\s+(\d+)\s*(?:RD|-\|)\s/) do
994
+ m = Regexp.last_match
995
+ index = m[1].to_i
996
+ length = m[2].to_i
997
+ next if index > 65_535
998
+
999
+ out[index] = charstring_bytes(private.byteslice(m.end(0), length) || "", len_iv)
1000
+ end
1001
+ out
1002
+ end
1003
+
1004
+ def charstrings(private, len_iv)
1005
+ out = {}
1006
+ at = private.index("/CharStrings") or return out
1007
+ private.byteslice(at, private.bytesize - at)
1008
+ .scan(%r{/([^\s/\[\]{}()<>]+)\s+(\d+)\s*(?:RD|-\|)\s}) do
1009
+ m = Regexp.last_match
1010
+ start = at + m.end(0)
1011
+ out[m[1]] = charstring_bytes(private.byteslice(start, m[2].to_i) || "", len_iv)
1012
+ end
1013
+ out
1014
+ end
1015
+ end
1016
+
1017
+ # Type 1 charstring interpreter (Adobe Type 1 Font Format, 6),
1018
+ # including flex, hint replacement and seac.
1019
+ class Type1Charstring
1020
+ attr_reader :width
1021
+
1022
+ def initialize(font, subrs, pen = Pen.new, origin = [0.0, 0.0])
1023
+ @font = font
1024
+ @subrs = subrs
1025
+ @pen = pen
1026
+ @stack = []
1027
+ @ps = []
1028
+ @flex = nil
1029
+ @ops = 0
1030
+ @origin = origin
1031
+ @width = nil
1032
+ end
1033
+
1034
+ def run(code)
1035
+ exec(code, 0)
1036
+ @pen.close
1037
+ @pen.polys
1038
+ end
1039
+
1040
+ private
1041
+
1042
+ def exec(code, depth)
1043
+ raise ImageError, "charstring subroutines nested too deeply" if depth > MAX_CALL_DEPTH
1044
+
1045
+ pen = @pen
1046
+ s = @stack
1047
+ pos = 0
1048
+ while pos < code.bytesize
1049
+ raise ImageError, "charstring runs too long" if (@ops += 1) > MAX_OPERATORS
1050
+
1051
+ b = code.getbyte(pos)
1052
+ pos += 1
1053
+ if b >= 32
1054
+ if b <= 246
1055
+ s << b - 139
1056
+ elsif b <= 250
1057
+ s << (b - 247) * 256 + code.getbyte(pos).to_i + 108
1058
+ pos += 1
1059
+ elsif b <= 254
1060
+ s << -(b - 251) * 256 - code.getbyte(pos).to_i - 108
1061
+ pos += 1
1062
+ else
1063
+ s << Bytes.s32(code, pos)
1064
+ pos += 4
1065
+ end
1066
+ next
1067
+ end
1068
+ case b
1069
+ when 13 # hsbw: sbx wx
1070
+ sbx, wx = s.pop(2)
1071
+ @width = wx
1072
+ pen.move_to(@origin[0] + sbx.to_f, @origin[1])
1073
+ @started = false
1074
+ s.clear
1075
+ when 21 then move(s[-2].to_f, s[-1].to_f) && s.clear
1076
+ when 22 then move(s[-1].to_f, 0) && s.clear
1077
+ when 4 then move(0, s[-1].to_f) && s.clear
1078
+ when 5
1079
+ pen.line_to(pen.x + s[-2], pen.y + s[-1]) if s.size >= 2
1080
+ s.clear
1081
+ when 6
1082
+ pen.line_to(pen.x + s[-1], pen.y) unless s.empty?
1083
+ s.clear
1084
+ when 7
1085
+ pen.line_to(pen.x, pen.y + s[-1]) unless s.empty?
1086
+ s.clear
1087
+ when 8
1088
+ if s.size >= 6
1089
+ dx1, dy1, dx2, dy2, dx3, dy3 = s.last(6)
1090
+ x1 = pen.x + dx1
1091
+ y1 = pen.y + dy1
1092
+ x2 = x1 + dx2
1093
+ y2 = y1 + dy2
1094
+ pen.curve_to(x1, y1, x2, y2, x2 + dx3, y2 + dy3)
1095
+ end
1096
+ s.clear
1097
+ when 30 # vhcurveto dy1 dx2 dy2 dx3
1098
+ if s.size >= 4
1099
+ dy1, dx2, dy2, dx3 = s.last(4)
1100
+ x1 = pen.x
1101
+ y1 = pen.y + dy1
1102
+ pen.curve_to(x1, y1, x1 + dx2, y1 + dy2, x1 + dx2 + dx3, y1 + dy2)
1103
+ end
1104
+ s.clear
1105
+ when 31 # hvcurveto dx1 dx2 dy2 dy3
1106
+ if s.size >= 4
1107
+ dx1, dx2, dy2, dy3 = s.last(4)
1108
+ x1 = pen.x + dx1
1109
+ y1 = pen.y
1110
+ pen.curve_to(x1, y1, x1 + dx2, y1 + dy2, x1 + dx2, y1 + dy2 + dy3)
1111
+ end
1112
+ s.clear
1113
+ when 9 # closepath
1114
+ pen.close
1115
+ s.clear
1116
+ when 10 # callsubr
1117
+ n = s.pop.to_i
1118
+ sub = @subrs[n] or raise ImageError, "charstring calls a missing subroutine"
1119
+ return :endchar if exec(sub, depth + 1) == :endchar
1120
+ when 11 then return nil
1121
+ when 14
1122
+ pen.close
1123
+ return :endchar
1124
+ when 1, 3 then s.clear # hstem vstem
1125
+ when 12
1126
+ op = code.getbyte(pos).to_i
1127
+ pos += 1
1128
+ return :endchar if escape(op, s, depth) == :endchar
1129
+ else
1130
+ s.clear
1131
+ end
1132
+ end
1133
+ nil
1134
+ end
1135
+
1136
+ def move(dx, dy)
1137
+ pen = @pen
1138
+ if @flex
1139
+ @flex << [pen.x + dx, pen.y + dy]
1140
+ pen.set_point(pen.x + dx, pen.y + dy)
1141
+ else
1142
+ pen.move_to(pen.x + dx, pen.y + dy)
1143
+ end
1144
+ true
1145
+ end
1146
+
1147
+ def escape(op, s, depth)
1148
+ pen = @pen
1149
+ case op
1150
+ when 7 # sbw: sbx sby wx wy
1151
+ sbx, sby, wx, = s.pop(4)
1152
+ @width = wx
1153
+ pen.move_to(@origin[0] + sbx.to_f, @origin[1] + sby.to_f)
1154
+ s.clear
1155
+ when 6 # seac: asb adx ady bchar achar
1156
+ asb, adx, ady, bchar, achar = s.pop(5)
1157
+ s.clear
1158
+ base = FontTables::STANDARD_ENCODING[bchar.to_i]
1159
+ accent = FontTables::STANDARD_ENCODING[achar.to_i]
1160
+ [[base, 0.0, 0.0], [accent, adx.to_f - asb.to_f, ady.to_f]].each do |name, dx, dy|
1161
+ code = name && @font.charstring(name) or next
1162
+ Type1Charstring.new(@font, @subrs, pen, [@origin[0] + dx, @origin[1] + dy]).send(:exec, code, depth + 1)
1163
+ pen.close
1164
+ end
1165
+ return :endchar
1166
+ when 12 # div
1167
+ a, b = s.pop(2)
1168
+ s << (b.to_f.zero? ? 0 : a.to_f / b)
1169
+ when 16 # callothersubr: args... n othersubr#
1170
+ number = s.pop.to_i
1171
+ n = s.pop.to_i
1172
+ args = n.positive? ? s.pop(n) : []
1173
+ case number
1174
+ when 1 # start flex
1175
+ @flex = []
1176
+ @flex_start = [pen.x, pen.y]
1177
+ when 2 then nil # flex point (recorded by rmoveto)
1178
+ when 0 # end flex: 7 points (reference + 6 curve points)
1179
+ points = @flex || []
1180
+ @flex = nil
1181
+ if points.size >= 7 && @flex_start
1182
+ pen.set_point(*@flex_start)
1183
+ pen.curve_to(*points[1], *points[2], *points[3])
1184
+ pen.curve_to(*points[4], *points[5], *points[6])
1185
+ end
1186
+ # "pop pop setcurrentpoint" follows: x first, then y.
1187
+ @ps = [pen.x, pen.y]
1188
+ return nil
1189
+ end
1190
+ @ps = args.reverse
1191
+ when 17 then s << (@ps.shift || 0) # pop
1192
+ when 33 # setcurrentpoint
1193
+ x, y = s.pop(2)
1194
+ pen.set_point(x, y) if x && y
1195
+ s.clear
1196
+ else # dotsection vstem3 hstem3
1197
+ s.clear
1198
+ end
1199
+ nil
1200
+ end
1201
+ end
1202
+ end
1203
+ end
1204
+ end
1205
+ end