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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +23 -7
- data/exe/qr_reader +1 -1
- data/lib/qr_reader/formats/jpeg.rb +61 -2
- data/lib/qr_reader/formats/jpeg2000/codestream.rb +447 -0
- data/lib/qr_reader/formats/jpeg2000/container.rb +156 -0
- data/lib/qr_reader/formats/jpeg2000/dwt.rb +607 -0
- data/lib/qr_reader/formats/jpeg2000/tier1.rb +1084 -0
- data/lib/qr_reader/formats/jpeg2000/tile.rb +722 -0
- data/lib/qr_reader/formats/jpeg2000.rb +764 -0
- data/lib/qr_reader/formats/pdf/font_programs.rb +1205 -0
- data/lib/qr_reader/formats/pdf/font_tables.rb +224 -0
- data/lib/qr_reader/formats/pdf/fonts.rb +412 -0
- data/lib/qr_reader/formats/pdf/text.rb +151 -0
- data/lib/qr_reader/formats/pdf/vector.rb +799 -0
- data/lib/qr_reader/formats/pdf.rb +183 -44
- data/lib/qr_reader/formats.rb +13 -4
- data/lib/qr_reader/reader.rb +36 -10
- data/lib/qr_reader/version.rb +1 -1
- data/lib/qr_reader.rb +1 -1
- metadata +15 -4
|
@@ -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
|