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,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Formats
|
|
5
|
+
module PDF
|
|
6
|
+
# Text drawing for the vector pass (ISO 32000-1, 9.3 and 9.4): when
|
|
7
|
+
# paths are collected, ContentScanner positions every glyph and hands
|
|
8
|
+
# the module-like ones (barcode fonts) to the PathCollector; Type 3
|
|
9
|
+
# glyph procedures are run like forms.
|
|
10
|
+
class ContentScanner
|
|
11
|
+
TEXT_OPERATORS = %w[BT ET Tf Tm Td TD T* TL Tc Tw Tz Ts Tr Tj TJ ' "].to_h { |op| [op, true] }.freeze
|
|
12
|
+
# Type 3 glyph procedures run per page (each is a content stream).
|
|
13
|
+
MAX_TYPE3_GLYPHS = 20_000
|
|
14
|
+
# Elements of strings and arrays parsed from one page's content.
|
|
15
|
+
TEXT_ELEMENTS = 1_000_000
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# The String, hex String or Array operand at the scanner position,
|
|
20
|
+
# or nil (garbage is skipped).
|
|
21
|
+
def text_operand(s, data)
|
|
22
|
+
@text_parser ||= {}
|
|
23
|
+
parser = @text_parser[data.object_id] ||= Parser.new(data, @text_budget ||= [TEXT_ELEMENTS])
|
|
24
|
+
parser.pos = s.pos
|
|
25
|
+
value = parser.parse
|
|
26
|
+
s.pos = parser.pos
|
|
27
|
+
value.is_a?(String) || value.is_a?(Array) ? value : nil
|
|
28
|
+
rescue ParseError
|
|
29
|
+
s.pos += 1
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def text_operator(token, operands, ctm, resources, depth)
|
|
34
|
+
st = @paths.state
|
|
35
|
+
case token
|
|
36
|
+
when "BT" then @tm = @tlm = IDENTITY
|
|
37
|
+
when "Tf"
|
|
38
|
+
name, size = operands.last(2)
|
|
39
|
+
st.font = name.is_a?(String) && name.start_with?("/") ? font(resources, Parser.name_symbol(name)) : nil
|
|
40
|
+
st.font_size = size.to_f
|
|
41
|
+
when "Tm"
|
|
42
|
+
m = operands.last(6).map { |v| text_number(v) }
|
|
43
|
+
@tm = @tlm = m if m.size == 6 && m.none?(&:nil?)
|
|
44
|
+
when "Td", "TD"
|
|
45
|
+
tx, ty = operands.last(2).map { |v| text_number(v) }
|
|
46
|
+
if tx && ty
|
|
47
|
+
st.leading = -ty if token == "TD"
|
|
48
|
+
next_line(tx, ty)
|
|
49
|
+
end
|
|
50
|
+
when "T*" then next_line(0.0, -st.leading)
|
|
51
|
+
when "TL" then st.leading = text_number(operands.last) || st.leading
|
|
52
|
+
when "Tc" then st.char_spacing = text_number(operands.last) || st.char_spacing
|
|
53
|
+
when "Tw" then st.word_spacing = text_number(operands.last) || st.word_spacing
|
|
54
|
+
when "Tz" then st.scaling = text_number(operands.last) || st.scaling
|
|
55
|
+
when "Ts" then st.rise = text_number(operands.last) || st.rise
|
|
56
|
+
when "Tr" then st.render = text_number(operands.last)&.to_i || st.render
|
|
57
|
+
when "Tj" then show(operands.last, ctm, resources, depth)
|
|
58
|
+
when "'"
|
|
59
|
+
next_line(0.0, -st.leading)
|
|
60
|
+
show(operands.last, ctm, resources, depth)
|
|
61
|
+
when "\""
|
|
62
|
+
aw, ac, string = operands.last(3)
|
|
63
|
+
st.word_spacing = text_number(aw) || st.word_spacing
|
|
64
|
+
st.char_spacing = text_number(ac) || st.char_spacing
|
|
65
|
+
next_line(0.0, -st.leading)
|
|
66
|
+
show(string, ctm, resources, depth)
|
|
67
|
+
when "TJ"
|
|
68
|
+
items = operands.last
|
|
69
|
+
return unless items.is_a?(Array)
|
|
70
|
+
|
|
71
|
+
items.each do |item|
|
|
72
|
+
if item.is_a?(String)
|
|
73
|
+
show(item, ctm, resources, depth)
|
|
74
|
+
elsif (n = text_number(item))
|
|
75
|
+
advance(-n / 1000.0 * st.font_size * st.scaling / 100.0)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
rescue Error, *INTERNAL_ERRORS
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def text_number(value)
|
|
84
|
+
case value
|
|
85
|
+
when String then value.start_with?("/") ? nil : (v = value.to_f).finite? ? v : nil
|
|
86
|
+
else Util.number(value)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def next_line(tx, ty)
|
|
91
|
+
@tlm = multiply([1.0, 0.0, 0.0, 1.0, tx, ty], @tlm || IDENTITY)
|
|
92
|
+
@tm = @tlm
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Moves the text matrix +tx+ along the line.
|
|
96
|
+
def advance(tx)
|
|
97
|
+
m = @tm || IDENTITY
|
|
98
|
+
@tm = [m[0], m[1], m[2], m[3], tx * m[0] + m[4], tx * m[1] + m[5]]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def font(resources, name)
|
|
102
|
+
fonts = @doc.dict_value(@doc.dict_value(resources)&.[](:Font)) or return nil
|
|
103
|
+
ref = fonts[name] or return nil
|
|
104
|
+
cache = @doc.fonts
|
|
105
|
+
key = ref.is_a?(Ref) ? ref : @doc.resolve(ref).object_id
|
|
106
|
+
cache.key?(key) ? cache[key] : (cache[key] = Font.load(@doc, ref))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def show(string, ctm, resources, depth)
|
|
110
|
+
st = @paths.state
|
|
111
|
+
font = st.font
|
|
112
|
+
return unless string.is_a?(String) && font
|
|
113
|
+
|
|
114
|
+
size = st.font_size
|
|
115
|
+
h = st.scaling / 100.0
|
|
116
|
+
visible = ![3, 7].include?(st.render)
|
|
117
|
+
font.each_code(string) do |code, w0, space|
|
|
118
|
+
if visible
|
|
119
|
+
trm = multiply(multiply([size * h, 0.0, 0.0, size, 0.0, st.rise], @tm || IDENTITY), ctm)
|
|
120
|
+
if font.type3?
|
|
121
|
+
type3_glyph(font, code, trm, resources, depth)
|
|
122
|
+
elsif (polys = font.glyph(code))
|
|
123
|
+
@paths.add_glyph(polys, trm)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
advance((w0 * size + st.char_spacing + (space ? st.word_spacing : 0.0)) * h)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Runs a Type 3 glyph procedure (its images are not collected).
|
|
131
|
+
def type3_glyph(font, code, trm, resources, depth)
|
|
132
|
+
return if depth >= MAX_FORM_DEPTH || (@type3_runs = (@type3_runs || 0) + 1) > MAX_TYPE3_GLYPHS
|
|
133
|
+
|
|
134
|
+
content = font.procedure(code) or return
|
|
135
|
+
tm = @tm
|
|
136
|
+
tlm = @tlm
|
|
137
|
+
@paths.save
|
|
138
|
+
@in_glyph = (@in_glyph || 0) + 1
|
|
139
|
+
scan(content, font.resources || resources, multiply(font.matrix, trm), depth + 1)
|
|
140
|
+
ensure
|
|
141
|
+
if content
|
|
142
|
+
@in_glyph -= 1
|
|
143
|
+
@paths.restore
|
|
144
|
+
@tm = tm
|
|
145
|
+
@tlm = tlm
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|