gamba 2.0.0
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 +7 -0
- data/CHANGELOG.md +80 -0
- data/COPYING +2 -0
- data/GPLv2 +339 -0
- data/GPLv3 +674 -0
- data/LICENSE +65 -0
- data/LICENSE-prawn-svg +21 -0
- data/README.md +148 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/gamba/annotations.rb +21 -0
- data/lib/gamba/combine.rb +307 -0
- data/lib/gamba/document/bounding_box.rb +193 -0
- data/lib/gamba/document/column_box.rb +91 -0
- data/lib/gamba/document/internals.rb +264 -0
- data/lib/gamba/document/span.rb +25 -0
- data/lib/gamba/document.rb +268 -0
- data/lib/gamba/encoding.rb +13 -0
- data/lib/gamba/errors.rb +39 -0
- data/lib/gamba/font.rb +228 -0
- data/lib/gamba/fonts/afm.rb +171 -0
- data/lib/gamba/fonts/otf.rb +50 -0
- data/lib/gamba/fonts/ttf.rb +413 -0
- data/lib/gamba/graphic_state.rb +84 -0
- data/lib/gamba/graphics/color.rb +174 -0
- data/lib/gamba/graphics/patterns.rb +100 -0
- data/lib/gamba/graphics/style.rb +159 -0
- data/lib/gamba/graphics.rb +212 -0
- data/lib/gamba/grid.rb +216 -0
- data/lib/gamba/images/image.rb +271 -0
- data/lib/gamba/images/png_decoder.rb +65 -0
- data/lib/gamba/images.rb +115 -0
- data/lib/gamba/measurement_extensions.rb +40 -0
- data/lib/gamba/measurements.rb +62 -0
- data/lib/gamba/outline.rb +148 -0
- data/lib/gamba/page.rb +125 -0
- data/lib/gamba/page_geometry.rb +27 -0
- data/lib/gamba/pdf/decryption.rb +165 -0
- data/lib/gamba/pdf/outline_node.rb +53 -0
- data/lib/gamba/pdf/reader.rb +616 -0
- data/lib/gamba/pdf/reference.rb +31 -0
- data/lib/gamba/pdf/writer.rb +105 -0
- data/lib/gamba/pdf.rb +87 -0
- data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
- data/lib/gamba/pdf_a.rb +132 -0
- data/lib/gamba/repeater.rb +79 -0
- data/lib/gamba/security.rb +111 -0
- data/lib/gamba/stamp.rb +76 -0
- data/lib/gamba/table/cell/kinds.rb +246 -0
- data/lib/gamba/table/cell.rb +331 -0
- data/lib/gamba/table/cells.rb +208 -0
- data/lib/gamba/table.rb +298 -0
- data/lib/gamba/text/box.rb +16 -0
- data/lib/gamba/text/formatted/arranger.rb +217 -0
- data/lib/gamba/text/formatted/box.rb +312 -0
- data/lib/gamba/text/formatted/fragment.rb +205 -0
- data/lib/gamba/text/formatted/line_wrap.rb +205 -0
- data/lib/gamba/text/formatted/parser.rb +150 -0
- data/lib/gamba/text/formatted/wrap.rb +103 -0
- data/lib/gamba/text.rb +310 -0
- data/lib/gamba/transformation_stack.rb +43 -0
- data/lib/gamba/true_type/cff_subset.rb +157 -0
- data/lib/gamba/true_type/file.rb +372 -0
- data/lib/gamba/true_type/open_type_layout.rb +189 -0
- data/lib/gamba/true_type/simple_subsets.rb +108 -0
- data/lib/gamba/true_type/subset.rb +206 -0
- data/lib/gamba/version.rb +10 -0
- data/lib/gamba/view.rb +28 -0
- data/lib/gamba.rb +124 -0
- data/lib/pdf/core.rb +4 -0
- data/lib/prawn/measurement_extensions.rb +4 -0
- data/lib/prawn/svg/attributes/clip_path.rb +46 -0
- data/lib/prawn/svg/attributes/mask.rb +16 -0
- data/lib/prawn/svg/attributes/opacity.rb +30 -0
- data/lib/prawn/svg/attributes/space.rb +10 -0
- data/lib/prawn/svg/attributes/stroke.rb +50 -0
- data/lib/prawn/svg/attributes/transform.rb +10 -0
- data/lib/prawn/svg/attributes.rb +6 -0
- data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
- data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
- data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
- data/lib/prawn/svg/calculators/path_length.rb +168 -0
- data/lib/prawn/svg/calculators/pixels.rb +57 -0
- data/lib/prawn/svg/color.rb +240 -0
- data/lib/prawn/svg/css/font_face_parser.rb +72 -0
- data/lib/prawn/svg/css/font_family_parser.rb +39 -0
- data/lib/prawn/svg/css/font_parser.rb +46 -0
- data/lib/prawn/svg/css/selector_parser.rb +180 -0
- data/lib/prawn/svg/css/stylesheets.rb +233 -0
- data/lib/prawn/svg/css/values_parser.rb +68 -0
- data/lib/prawn/svg/document.rb +169 -0
- data/lib/prawn/svg/elements/anchor.rb +10 -0
- data/lib/prawn/svg/elements/base.rb +315 -0
- data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
- data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
- data/lib/prawn/svg/elements/circle.rb +19 -0
- data/lib/prawn/svg/elements/clip_path.rb +26 -0
- data/lib/prawn/svg/elements/container.rb +10 -0
- data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
- data/lib/prawn/svg/elements/ellipse.rb +20 -0
- data/lib/prawn/svg/elements/gradient.rb +183 -0
- data/lib/prawn/svg/elements/ignored.rb +5 -0
- data/lib/prawn/svg/elements/image.rb +178 -0
- data/lib/prawn/svg/elements/line.rb +27 -0
- data/lib/prawn/svg/elements/marker.rb +72 -0
- data/lib/prawn/svg/elements/mask.rb +65 -0
- data/lib/prawn/svg/elements/path.rb +294 -0
- data/lib/prawn/svg/elements/pattern.rb +150 -0
- data/lib/prawn/svg/elements/polygon.rb +25 -0
- data/lib/prawn/svg/elements/polyline.rb +23 -0
- data/lib/prawn/svg/elements/rect.rb +64 -0
- data/lib/prawn/svg/elements/root.rb +22 -0
- data/lib/prawn/svg/elements/switch.rb +92 -0
- data/lib/prawn/svg/elements/text.rb +99 -0
- data/lib/prawn/svg/elements/text_component.rb +352 -0
- data/lib/prawn/svg/elements/text_node.rb +308 -0
- data/lib/prawn/svg/elements/text_path.rb +153 -0
- data/lib/prawn/svg/elements/use.rb +142 -0
- data/lib/prawn/svg/elements/viewport.rb +29 -0
- data/lib/prawn/svg/elements.rb +46 -0
- data/lib/prawn/svg/extension.rb +25 -0
- data/lib/prawn/svg/font.rb +20 -0
- data/lib/prawn/svg/font_metrics.rb +144 -0
- data/lib/prawn/svg/font_registry.rb +315 -0
- data/lib/prawn/svg/funciri.rb +14 -0
- data/lib/prawn/svg/gradient_renderer.rb +332 -0
- data/lib/prawn/svg/gradients.rb +53 -0
- data/lib/prawn/svg/interface.rb +72 -0
- data/lib/prawn/svg/length.rb +43 -0
- data/lib/prawn/svg/link_renderer.rb +40 -0
- data/lib/prawn/svg/loaders/data.rb +17 -0
- data/lib/prawn/svg/loaders/file.rb +111 -0
- data/lib/prawn/svg/loaders/web.rb +24 -0
- data/lib/prawn/svg/paint.rb +67 -0
- data/lib/prawn/svg/pathable.rb +153 -0
- data/lib/prawn/svg/pattern_renderer.rb +112 -0
- data/lib/prawn/svg/pdfmatrix.rb +39 -0
- data/lib/prawn/svg/percentage.rb +24 -0
- data/lib/prawn/svg/properties.rb +329 -0
- data/lib/prawn/svg/renderer.rb +249 -0
- data/lib/prawn/svg/state.rb +24 -0
- data/lib/prawn/svg/transform_parser.rb +78 -0
- data/lib/prawn/svg/ttc.rb +35 -0
- data/lib/prawn/svg/ttf.rb +95 -0
- data/lib/prawn/svg/url_loader.rb +46 -0
- data/lib/prawn/svg/version.rb +5 -0
- data/lib/prawn/table.rb +4 -0
- data/lib/prawn-svg.rb +52 -0
- data/lib/prawn.rb +3 -0
- metadata +263 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "openssl"
|
|
5
|
+
|
|
6
|
+
module PDF
|
|
7
|
+
module Core
|
|
8
|
+
# The standard security handler opened with the empty user password, as viewers open a file that has only an
|
|
9
|
+
# owner password: RC4 (revisions 2 to 4), AES-128 (4) and AES-256 (5, 6). ISO 32000-1 §7.6, ISO 32000-2 §7.6.4.
|
|
10
|
+
class Decryption
|
|
11
|
+
PADDING = Prawn::Document::Security::PASSWORD_PADDING
|
|
12
|
+
IDENTITY = Name.new(value: "Identity")
|
|
13
|
+
AES_128 = Name.new(value: "AESV2")
|
|
14
|
+
AES_256 = Name.new(value: "AESV3")
|
|
15
|
+
RC4 = Name.new(value: "V2")
|
|
16
|
+
STANDARD = Name.new(value: "Standard")
|
|
17
|
+
TYPE_XREF = Name.new(value: "XRef")
|
|
18
|
+
TYPE_METADATA = Name.new(value: "Metadata")
|
|
19
|
+
NONE = Name.new(value: "None")
|
|
20
|
+
|
|
21
|
+
def initialize(dictionary, file_identifier, dictionary_number)
|
|
22
|
+
raise Prawn::Errors::EncryptedPDF, "Unsupported security handler #{dictionary[:Filter].inspect}" unless dictionary[:Filter] == STANDARD
|
|
23
|
+
|
|
24
|
+
@dictionary_number = dictionary_number
|
|
25
|
+
@revision = Reader.integer(dictionary[:R])
|
|
26
|
+
@encrypt_metadata = dictionary[:EncryptMetadata] != false
|
|
27
|
+
@stream_method, @string_method = methods_for(dictionary)
|
|
28
|
+
@key = (@revision >= 5) ? aes_256_key(dictionary) : rc4_era_key(dictionary, file_identifier)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def decrypt(value, number, generation)
|
|
32
|
+
return value if number == @dictionary_number
|
|
33
|
+
|
|
34
|
+
case value
|
|
35
|
+
when Reader::Stream
|
|
36
|
+
return value if value.dictionary[:Type] == TYPE_XREF
|
|
37
|
+
data = (value.dictionary[:Type] == TYPE_METADATA && !@encrypt_metadata) ? value.data : decrypt_bytes(value.data, @stream_method, number, generation)
|
|
38
|
+
Reader::Stream.new(dictionary: decrypt_strings(value.dictionary, number, generation), data:)
|
|
39
|
+
else decrypt_strings(value, number, generation)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def methods_for(dictionary)
|
|
46
|
+
case Reader.integer(dictionary[:V])
|
|
47
|
+
when 1, 2 then [:rc4, :rc4]
|
|
48
|
+
when 4, 5
|
|
49
|
+
filters = dictionary[:CF].is_a?(Hash) ? dictionary[:CF] : {}
|
|
50
|
+
[dictionary[:StmF], dictionary[:StrF]].map do |name|
|
|
51
|
+
next :identity if name.nil? || name == IDENTITY
|
|
52
|
+
|
|
53
|
+
filter = filters[name.value.to_sym]
|
|
54
|
+
method = filter[:CFM] if filter.is_a?(Hash)
|
|
55
|
+
case method
|
|
56
|
+
when RC4 then :rc4
|
|
57
|
+
when AES_128 then :aes_128
|
|
58
|
+
when AES_256 then :aes_256
|
|
59
|
+
when nil, NONE then :identity
|
|
60
|
+
else raise Prawn::Errors::EncryptedPDF, "Unsupported crypt filter #{method.inspect}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
else raise Prawn::Errors::EncryptedPDF, "Unsupported encryption version #{dictionary[:V].inspect}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def decrypt_strings(value, number, generation)
|
|
68
|
+
case value
|
|
69
|
+
when ByteString then ByteString.new(decrypt_bytes(value, @string_method, number, generation))
|
|
70
|
+
when Array then value.map { decrypt_strings(_1, number, generation) }
|
|
71
|
+
when Hash then value.transform_values { decrypt_strings(_1, number, generation) }
|
|
72
|
+
else value
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def decrypt_bytes(bytes, method, number, generation)
|
|
77
|
+
case method
|
|
78
|
+
when :identity then bytes
|
|
79
|
+
when :rc4 then Prawn::Document::Arcfour.new(object_key(number, generation, false)).encrypt(bytes)
|
|
80
|
+
when :aes_128 then aes_cbc("aes-128-cbc", object_key(number, generation, true), bytes)
|
|
81
|
+
when :aes_256 then aes_cbc("aes-256-cbc", @key, bytes)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def object_key(number, generation, aes)
|
|
86
|
+
return @key if @revision >= 5
|
|
87
|
+
|
|
88
|
+
Prawn::Document::Security.object_key(@key, number, generation, aes ? "sAlT" : nil)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The first 16 bytes are the initialization vector. The PKCS#7 padding is stripped by hand, as OpenSSL checks
|
|
92
|
+
# it, so that a wrong one keeps the bytes as decrypted instead of failing.
|
|
93
|
+
def aes_cbc(cipher_name, key, bytes)
|
|
94
|
+
return "".b if bytes.bytesize < 32
|
|
95
|
+
|
|
96
|
+
cipher = OpenSSL::Cipher.new(cipher_name).decrypt
|
|
97
|
+
cipher.key = key
|
|
98
|
+
cipher.iv = bytes.byteslice(0, 16)
|
|
99
|
+
cipher.padding = 0
|
|
100
|
+
plain = cipher.update(bytes.byteslice(16, (bytes.bytesize - 16) / 16 * 16)) + cipher.final
|
|
101
|
+
padding = plain.getbyte(-1)
|
|
102
|
+
(padding.between?(1, 16) && plain.end_with?(padding.chr * padding)) ? plain.byteslice(0, plain.bytesize - padding) : plain
|
|
103
|
+
rescue ArgumentError, OpenSSL::Cipher::CipherError => error
|
|
104
|
+
raise Prawn::Errors::MalformedPDF, "Cannot decrypt: #{error.message}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Algorithms 2, 4 and 5 with the empty password.
|
|
108
|
+
def rc4_era_key(dictionary, file_identifier)
|
|
109
|
+
length = (@revision == 2) ? 5 : Reader.integer(dictionary[:Length], 40).clamp(40, 128) / 8
|
|
110
|
+
length = 16 if @stream_method == :aes_128 || @string_method == :aes_128 || (@revision == 4 && dictionary[:Length].nil?)
|
|
111
|
+
seed = PADDING + dictionary[:O].to_s.b.byteslice(0, 32) + [Reader.integer(dictionary[:P])].pack("l<") + file_identifier
|
|
112
|
+
seed << "\xFF\xFF\xFF\xFF".b if @revision >= 4 && !@encrypt_metadata
|
|
113
|
+
key = Digest::MD5.digest(seed).byteslice(0, length)
|
|
114
|
+
50.times { key = Digest::MD5.digest(key).byteslice(0, length) } if @revision >= 3
|
|
115
|
+
raise Prawn::Errors::EncryptedPDF, "This PDF needs a password to open" unless user_password_valid?(key, dictionary[:U].to_s.b, file_identifier)
|
|
116
|
+
|
|
117
|
+
key
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def user_password_valid?(key, user_entry, file_identifier)
|
|
121
|
+
return Prawn::Document::Arcfour.new(key).encrypt(PADDING) == user_entry if @revision == 2
|
|
122
|
+
|
|
123
|
+
hash = Prawn::Document::Arcfour.new(key).encrypt(Digest::MD5.digest(PADDING + file_identifier))
|
|
124
|
+
1.upto(19) { |round| hash = Prawn::Document::Arcfour.new(key.bytes.map { _1 ^ round }.pack("C*")).encrypt(hash) }
|
|
125
|
+
hash == user_entry.byteslice(0, 16)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Algorithms 2.A and 2.B: the user entry validates the empty password, then its key salt unwraps /UE.
|
|
129
|
+
def aes_256_key(dictionary)
|
|
130
|
+
user_entry = dictionary[:U].to_s.b
|
|
131
|
+
raise Prawn::Errors::MalformedPDF, "Bad /U or /UE in the encryption dictionary" if user_entry.bytesize < 48 || dictionary[:UE].to_s.bytesize < 32
|
|
132
|
+
raise Prawn::Errors::EncryptedPDF, "This PDF needs a password to open" unless hash_2b(user_entry.byteslice(32, 8)) == user_entry.byteslice(0, 32)
|
|
133
|
+
|
|
134
|
+
cipher = OpenSSL::Cipher.new("aes-256-cbc").decrypt
|
|
135
|
+
cipher.key = hash_2b(user_entry.byteslice(40, 8))
|
|
136
|
+
cipher.iv = "\x00".b * 16
|
|
137
|
+
cipher.padding = 0
|
|
138
|
+
cipher.update(dictionary[:UE].to_s.b.byteslice(0, 32)) + cipher.final
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def hash_2b(salt)
|
|
142
|
+
digest = Digest::SHA256.digest(salt)
|
|
143
|
+
return digest if @revision == 5
|
|
144
|
+
|
|
145
|
+
round = 0
|
|
146
|
+
loop do
|
|
147
|
+
block = digest * 64
|
|
148
|
+
cipher = OpenSSL::Cipher.new("aes-128-cbc").encrypt
|
|
149
|
+
cipher.key = digest.byteslice(0, 16)
|
|
150
|
+
cipher.iv = digest.byteslice(16, 16)
|
|
151
|
+
cipher.padding = 0
|
|
152
|
+
encrypted = cipher.update(block) + cipher.final
|
|
153
|
+
digest = case encrypted.byteslice(0, 16).bytes.sum % 3
|
|
154
|
+
when 0 then Digest::SHA256.digest(encrypted)
|
|
155
|
+
when 1 then Digest::SHA384.digest(encrypted)
|
|
156
|
+
else Digest::SHA512.digest(encrypted)
|
|
157
|
+
end
|
|
158
|
+
round += 1
|
|
159
|
+
break if round >= 64 && encrypted.getbyte(-1) <= round - 32
|
|
160
|
+
end
|
|
161
|
+
digest.byteslice(0, 32)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PDF
|
|
4
|
+
module Core
|
|
5
|
+
class OutlineRoot
|
|
6
|
+
include Serializable
|
|
7
|
+
|
|
8
|
+
attr_accessor :count, :first, :last
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@count = 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_hash
|
|
15
|
+
{Type: :Outlines, Count: count, First: first, Last: last}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class OutlineItem
|
|
20
|
+
include Serializable
|
|
21
|
+
|
|
22
|
+
attr_accessor :count, :first, :last, :next, :prev, :parent, :title, :dest, :closed
|
|
23
|
+
|
|
24
|
+
def initialize(title, parent, options)
|
|
25
|
+
@closed = options[:closed]
|
|
26
|
+
@title = title
|
|
27
|
+
@parent = parent
|
|
28
|
+
@count = 0
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_hash
|
|
32
|
+
{Title: title, Parent: parent, Count: closed ? -count : count, First: first, Last: last, Next: @next, Prev: prev, Dest: dest}.compact
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A name tree kept as a single sorted leaf: valid at any size (ISO 32000-1 §7.9.6).
|
|
37
|
+
class NameTree
|
|
38
|
+
include Serializable
|
|
39
|
+
|
|
40
|
+
def initialize
|
|
41
|
+
@value_by_name = {}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add(name, value)
|
|
45
|
+
@value_by_name[name] = value
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_hash
|
|
49
|
+
{Names: @value_by_name.sort_by { |name, _| name.to_s }.flat_map { |name, value| [LiteralString.new(name.to_s), value] }}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|