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,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Formats
|
|
5
|
+
module JPEG2000
|
|
6
|
+
# A JP2 palette (pclr box): +entries+ is an Array of rows, one Integer
|
|
7
|
+
# per column; +depths+ / +signed+ describe each column.
|
|
8
|
+
Palette = Struct.new(:entries, :depths, :signed)
|
|
9
|
+
|
|
10
|
+
# Channel definition (cdef): channel index, type (0 colour, 1
|
|
11
|
+
# opacity, 2 premultiplied opacity, 65535 unspecified) and the colour
|
|
12
|
+
# it belongs to (0 whole image, 1..n colour n, 65535 none).
|
|
13
|
+
ChannelDef = Struct.new(:channel, :type, :assoc)
|
|
14
|
+
|
|
15
|
+
# What the JP2 boxes say about a codestream.
|
|
16
|
+
Header = Struct.new(:codestream, :enum_cs, :icc_space, :palette, :cmap, :cdef)
|
|
17
|
+
|
|
18
|
+
# JP2 / JPX file format boxes (ITU-T T.800 Annex I): finds the
|
|
19
|
+
# codestream and the colour information. Raw codestreams (starting
|
|
20
|
+
# with SOC SIZ) are accepted too.
|
|
21
|
+
module Container
|
|
22
|
+
SIGNATURE = "\x00\x00\x00\x0CjP \r\n\x87\n".b.freeze
|
|
23
|
+
SOC_SIZ = "\xFF\x4F\xFF\x51".b.freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Parses +bytes+ (a binary String) into a Header.
|
|
28
|
+
def parse(bytes)
|
|
29
|
+
return Header.new(bytes) if bytes.start_with?(SOC_SIZ)
|
|
30
|
+
raise ImageError, "not a JPEG 2000 file" unless bytes.start_with?(SIGNATURE)
|
|
31
|
+
|
|
32
|
+
header = Header.new
|
|
33
|
+
each_box(bytes, 0, bytes.bytesize, true) do |type, start, stop|
|
|
34
|
+
case type
|
|
35
|
+
when "jp2h" then parse_jp2h(bytes, start, stop, header)
|
|
36
|
+
when "jp2c"
|
|
37
|
+
header.codestream = bytes.byteslice(start, stop - start)
|
|
38
|
+
break
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
raise ImageError, "JPEG 2000 file contains no codestream" unless header.codestream
|
|
42
|
+
|
|
43
|
+
header
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Yields [type, content start, content end] of the boxes in
|
|
47
|
+
# bytes[pos...stop]. A box running past the end is clipped (a
|
|
48
|
+
# truncated file) when +lenient+.
|
|
49
|
+
def each_box(bytes, pos, stop, lenient)
|
|
50
|
+
while pos + 8 <= stop
|
|
51
|
+
length = bytes.byteslice(pos, 4).unpack1("N")
|
|
52
|
+
type = bytes.byteslice(pos + 4, 4)
|
|
53
|
+
header = 8
|
|
54
|
+
if length == 1
|
|
55
|
+
raise ImageError, "truncated JPEG 2000 box" if pos + 16 > stop
|
|
56
|
+
|
|
57
|
+
length = bytes.byteslice(pos + 8, 8).unpack1("Q>")
|
|
58
|
+
header = 16
|
|
59
|
+
elsif length.zero?
|
|
60
|
+
length = stop - pos
|
|
61
|
+
end
|
|
62
|
+
raise ImageError, "invalid JPEG 2000 box length" if length < header
|
|
63
|
+
|
|
64
|
+
box_end = pos + length
|
|
65
|
+
if box_end > stop
|
|
66
|
+
raise ImageError, "truncated JPEG 2000 box" unless lenient
|
|
67
|
+
|
|
68
|
+
box_end = stop
|
|
69
|
+
end
|
|
70
|
+
yield type, pos + header, box_end
|
|
71
|
+
pos = box_end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_jp2h(bytes, pos, stop, header)
|
|
76
|
+
each_box(bytes, pos, stop, true) do |type, start, box_end|
|
|
77
|
+
case type
|
|
78
|
+
when "colr" then parse_colr(bytes, start, box_end, header)
|
|
79
|
+
when "pclr" then header.palette = parse_pclr(bytes, start, box_end)
|
|
80
|
+
when "cmap" then header.cmap = parse_cmap(bytes, start, box_end)
|
|
81
|
+
when "cdef" then header.cdef = parse_cdef(bytes, start, box_end)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Only the first colr box counts (I.5.3.3).
|
|
87
|
+
def parse_colr(bytes, pos, stop, header)
|
|
88
|
+
return if header.enum_cs || header.icc_space || stop - pos < 3
|
|
89
|
+
|
|
90
|
+
method = bytes.getbyte(pos)
|
|
91
|
+
if method == 1
|
|
92
|
+
header.enum_cs = bytes.byteslice(pos + 3, 4).unpack1("N") if stop - pos >= 7
|
|
93
|
+
elsif method == 2 || method == 3
|
|
94
|
+
# The ICC profile header's colour space signature (offset 16).
|
|
95
|
+
header.icc_space = bytes.byteslice(pos + 3 + 16, 4) if stop - pos >= 3 + 20
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def parse_pclr(bytes, pos, stop)
|
|
100
|
+
raise ImageError, "invalid JPEG 2000 palette" if stop - pos < 3
|
|
101
|
+
|
|
102
|
+
count = bytes.byteslice(pos, 2).unpack1("n")
|
|
103
|
+
columns = bytes.getbyte(pos + 2)
|
|
104
|
+
raise ImageError, "invalid JPEG 2000 palette" if count.zero? || count > 1024 || columns.zero?
|
|
105
|
+
|
|
106
|
+
pos += 3
|
|
107
|
+
raise ImageError, "truncated JPEG 2000 palette" if pos + columns > stop
|
|
108
|
+
|
|
109
|
+
depths = Array.new(columns) { |i| (bytes.getbyte(pos + i) & 0x7F) + 1 }
|
|
110
|
+
signed = Array.new(columns) { |i| bytes.getbyte(pos + i) >= 0x80 }
|
|
111
|
+
raise UnsupportedImageError, "JPEG 2000 palette entries over 32 bits" if depths.max > 32
|
|
112
|
+
|
|
113
|
+
pos += columns
|
|
114
|
+
sizes = depths.map { |d| (d + 7) >> 3 }
|
|
115
|
+
raise ImageError, "truncated JPEG 2000 palette" if pos + count * sizes.sum > stop
|
|
116
|
+
|
|
117
|
+
entries = Array.new(count) do
|
|
118
|
+
sizes.map do |size|
|
|
119
|
+
v = 0
|
|
120
|
+
size.times do
|
|
121
|
+
v = (v << 8) | bytes.getbyte(pos)
|
|
122
|
+
pos += 1
|
|
123
|
+
end
|
|
124
|
+
v
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
Palette.new(entries, depths, signed)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_cmap(bytes, pos, stop)
|
|
131
|
+
list = []
|
|
132
|
+
while pos + 4 <= stop
|
|
133
|
+
list << [bytes.byteslice(pos, 2).unpack1("n"), bytes.getbyte(pos + 2), bytes.getbyte(pos + 3)]
|
|
134
|
+
pos += 4
|
|
135
|
+
end
|
|
136
|
+
list
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parse_cdef(bytes, pos, stop)
|
|
140
|
+
return nil if stop - pos < 2
|
|
141
|
+
|
|
142
|
+
n = bytes.byteslice(pos, 2).unpack1("n")
|
|
143
|
+
pos += 2
|
|
144
|
+
list = []
|
|
145
|
+
n.times do
|
|
146
|
+
break if pos + 6 > stop
|
|
147
|
+
|
|
148
|
+
list << ChannelDef.new(*bytes.byteslice(pos, 6).unpack("n3"))
|
|
149
|
+
pos += 6
|
|
150
|
+
end
|
|
151
|
+
list
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|