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.
Files changed (163) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/COPYING +2 -0
  4. data/GPLv2 +339 -0
  5. data/GPLv3 +674 -0
  6. data/LICENSE +65 -0
  7. data/LICENSE-prawn-svg +21 -0
  8. data/README.md +148 -0
  9. data/data/fonts/Courier-Bold.afm +342 -0
  10. data/data/fonts/Courier-BoldOblique.afm +342 -0
  11. data/data/fonts/Courier-Oblique.afm +342 -0
  12. data/data/fonts/Courier.afm +342 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/lib/gamba/annotations.rb +21 -0
  25. data/lib/gamba/combine.rb +307 -0
  26. data/lib/gamba/document/bounding_box.rb +193 -0
  27. data/lib/gamba/document/column_box.rb +91 -0
  28. data/lib/gamba/document/internals.rb +264 -0
  29. data/lib/gamba/document/span.rb +25 -0
  30. data/lib/gamba/document.rb +268 -0
  31. data/lib/gamba/encoding.rb +13 -0
  32. data/lib/gamba/errors.rb +39 -0
  33. data/lib/gamba/font.rb +228 -0
  34. data/lib/gamba/fonts/afm.rb +171 -0
  35. data/lib/gamba/fonts/otf.rb +50 -0
  36. data/lib/gamba/fonts/ttf.rb +413 -0
  37. data/lib/gamba/graphic_state.rb +84 -0
  38. data/lib/gamba/graphics/color.rb +174 -0
  39. data/lib/gamba/graphics/patterns.rb +100 -0
  40. data/lib/gamba/graphics/style.rb +159 -0
  41. data/lib/gamba/graphics.rb +212 -0
  42. data/lib/gamba/grid.rb +216 -0
  43. data/lib/gamba/images/image.rb +271 -0
  44. data/lib/gamba/images/png_decoder.rb +65 -0
  45. data/lib/gamba/images.rb +115 -0
  46. data/lib/gamba/measurement_extensions.rb +40 -0
  47. data/lib/gamba/measurements.rb +62 -0
  48. data/lib/gamba/outline.rb +148 -0
  49. data/lib/gamba/page.rb +125 -0
  50. data/lib/gamba/page_geometry.rb +27 -0
  51. data/lib/gamba/pdf/decryption.rb +165 -0
  52. data/lib/gamba/pdf/outline_node.rb +53 -0
  53. data/lib/gamba/pdf/reader.rb +616 -0
  54. data/lib/gamba/pdf/reference.rb +31 -0
  55. data/lib/gamba/pdf/writer.rb +105 -0
  56. data/lib/gamba/pdf.rb +87 -0
  57. data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
  58. data/lib/gamba/pdf_a.rb +132 -0
  59. data/lib/gamba/repeater.rb +79 -0
  60. data/lib/gamba/security.rb +111 -0
  61. data/lib/gamba/stamp.rb +76 -0
  62. data/lib/gamba/table/cell/kinds.rb +246 -0
  63. data/lib/gamba/table/cell.rb +331 -0
  64. data/lib/gamba/table/cells.rb +208 -0
  65. data/lib/gamba/table.rb +298 -0
  66. data/lib/gamba/text/box.rb +16 -0
  67. data/lib/gamba/text/formatted/arranger.rb +217 -0
  68. data/lib/gamba/text/formatted/box.rb +312 -0
  69. data/lib/gamba/text/formatted/fragment.rb +205 -0
  70. data/lib/gamba/text/formatted/line_wrap.rb +205 -0
  71. data/lib/gamba/text/formatted/parser.rb +150 -0
  72. data/lib/gamba/text/formatted/wrap.rb +103 -0
  73. data/lib/gamba/text.rb +310 -0
  74. data/lib/gamba/transformation_stack.rb +43 -0
  75. data/lib/gamba/true_type/cff_subset.rb +157 -0
  76. data/lib/gamba/true_type/file.rb +372 -0
  77. data/lib/gamba/true_type/open_type_layout.rb +189 -0
  78. data/lib/gamba/true_type/simple_subsets.rb +108 -0
  79. data/lib/gamba/true_type/subset.rb +206 -0
  80. data/lib/gamba/version.rb +10 -0
  81. data/lib/gamba/view.rb +28 -0
  82. data/lib/gamba.rb +124 -0
  83. data/lib/pdf/core.rb +4 -0
  84. data/lib/prawn/measurement_extensions.rb +4 -0
  85. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  86. data/lib/prawn/svg/attributes/mask.rb +16 -0
  87. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  88. data/lib/prawn/svg/attributes/space.rb +10 -0
  89. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  90. data/lib/prawn/svg/attributes/transform.rb +10 -0
  91. data/lib/prawn/svg/attributes.rb +6 -0
  92. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  93. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  94. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  95. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  96. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  97. data/lib/prawn/svg/color.rb +240 -0
  98. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  99. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  100. data/lib/prawn/svg/css/font_parser.rb +46 -0
  101. data/lib/prawn/svg/css/selector_parser.rb +180 -0
  102. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  103. data/lib/prawn/svg/css/values_parser.rb +68 -0
  104. data/lib/prawn/svg/document.rb +169 -0
  105. data/lib/prawn/svg/elements/anchor.rb +10 -0
  106. data/lib/prawn/svg/elements/base.rb +315 -0
  107. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  108. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  109. data/lib/prawn/svg/elements/circle.rb +19 -0
  110. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  111. data/lib/prawn/svg/elements/container.rb +10 -0
  112. data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
  113. data/lib/prawn/svg/elements/ellipse.rb +20 -0
  114. data/lib/prawn/svg/elements/gradient.rb +183 -0
  115. data/lib/prawn/svg/elements/ignored.rb +5 -0
  116. data/lib/prawn/svg/elements/image.rb +178 -0
  117. data/lib/prawn/svg/elements/line.rb +27 -0
  118. data/lib/prawn/svg/elements/marker.rb +72 -0
  119. data/lib/prawn/svg/elements/mask.rb +65 -0
  120. data/lib/prawn/svg/elements/path.rb +294 -0
  121. data/lib/prawn/svg/elements/pattern.rb +150 -0
  122. data/lib/prawn/svg/elements/polygon.rb +25 -0
  123. data/lib/prawn/svg/elements/polyline.rb +23 -0
  124. data/lib/prawn/svg/elements/rect.rb +64 -0
  125. data/lib/prawn/svg/elements/root.rb +22 -0
  126. data/lib/prawn/svg/elements/switch.rb +92 -0
  127. data/lib/prawn/svg/elements/text.rb +99 -0
  128. data/lib/prawn/svg/elements/text_component.rb +352 -0
  129. data/lib/prawn/svg/elements/text_node.rb +308 -0
  130. data/lib/prawn/svg/elements/text_path.rb +153 -0
  131. data/lib/prawn/svg/elements/use.rb +142 -0
  132. data/lib/prawn/svg/elements/viewport.rb +29 -0
  133. data/lib/prawn/svg/elements.rb +46 -0
  134. data/lib/prawn/svg/extension.rb +25 -0
  135. data/lib/prawn/svg/font.rb +20 -0
  136. data/lib/prawn/svg/font_metrics.rb +144 -0
  137. data/lib/prawn/svg/font_registry.rb +315 -0
  138. data/lib/prawn/svg/funciri.rb +14 -0
  139. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  140. data/lib/prawn/svg/gradients.rb +53 -0
  141. data/lib/prawn/svg/interface.rb +72 -0
  142. data/lib/prawn/svg/length.rb +43 -0
  143. data/lib/prawn/svg/link_renderer.rb +40 -0
  144. data/lib/prawn/svg/loaders/data.rb +17 -0
  145. data/lib/prawn/svg/loaders/file.rb +111 -0
  146. data/lib/prawn/svg/loaders/web.rb +24 -0
  147. data/lib/prawn/svg/paint.rb +67 -0
  148. data/lib/prawn/svg/pathable.rb +153 -0
  149. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  150. data/lib/prawn/svg/pdfmatrix.rb +39 -0
  151. data/lib/prawn/svg/percentage.rb +24 -0
  152. data/lib/prawn/svg/properties.rb +329 -0
  153. data/lib/prawn/svg/renderer.rb +249 -0
  154. data/lib/prawn/svg/state.rb +24 -0
  155. data/lib/prawn/svg/transform_parser.rb +78 -0
  156. data/lib/prawn/svg/ttc.rb +35 -0
  157. data/lib/prawn/svg/ttf.rb +95 -0
  158. data/lib/prawn/svg/url_loader.rb +46 -0
  159. data/lib/prawn/svg/version.rb +5 -0
  160. data/lib/prawn/table.rb +4 -0
  161. data/lib/prawn-svg.rb +52 -0
  162. data/lib/prawn.rb +3 -0
  163. 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