prawn 1.0.0.rc2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/COPYING +2 -2
  4. data/Gemfile +8 -15
  5. data/LICENSE +1 -1
  6. data/Rakefile +25 -16
  7. data/data/images/16bit.alpha +0 -0
  8. data/data/images/16bit.color +0 -0
  9. data/data/images/dice.alpha +0 -0
  10. data/data/images/dice.color +0 -0
  11. data/data/images/indexed_color.dat +0 -0
  12. data/data/images/indexed_color.png +0 -0
  13. data/data/images/license.md +8 -0
  14. data/data/images/page_white_text.alpha +0 -0
  15. data/data/images/page_white_text.color +0 -0
  16. data/lib/prawn.rb +85 -23
  17. data/lib/prawn/document.rb +134 -116
  18. data/lib/prawn/document/bounding_box.rb +33 -4
  19. data/lib/prawn/document/column_box.rb +18 -6
  20. data/lib/prawn/document/graphics_state.rb +11 -74
  21. data/lib/prawn/document/internals.rb +24 -23
  22. data/lib/prawn/document/span.rb +12 -10
  23. data/lib/prawn/encoding.rb +8 -9
  24. data/lib/prawn/errors.rb +13 -32
  25. data/lib/prawn/font.rb +137 -105
  26. data/lib/prawn/font/afm.rb +76 -32
  27. data/lib/prawn/font/dfont.rb +4 -3
  28. data/lib/prawn/font/ttf.rb +33 -25
  29. data/lib/prawn/font_metric_cache.rb +47 -0
  30. data/lib/prawn/graphics.rb +177 -57
  31. data/lib/prawn/graphics/cap_style.rb +4 -3
  32. data/lib/prawn/graphics/color.rb +5 -4
  33. data/lib/prawn/graphics/dash.rb +53 -31
  34. data/lib/prawn/graphics/join_style.rb +9 -7
  35. data/lib/prawn/graphics/patterns.rb +4 -15
  36. data/lib/prawn/graphics/transformation.rb +10 -9
  37. data/lib/prawn/graphics/transparency.rb +3 -1
  38. data/lib/prawn/{layout/grid.rb → grid.rb} +72 -54
  39. data/lib/prawn/image_handler.rb +42 -0
  40. data/lib/prawn/images.rb +58 -54
  41. data/lib/prawn/images/image.rb +6 -22
  42. data/lib/prawn/images/jpg.rb +20 -14
  43. data/lib/prawn/images/png.rb +58 -121
  44. data/lib/prawn/layout.rb +12 -15
  45. data/lib/prawn/measurement_extensions.rb +10 -6
  46. data/lib/prawn/measurements.rb +27 -21
  47. data/lib/prawn/outline.rb +108 -147
  48. data/lib/prawn/repeater.rb +10 -8
  49. data/lib/prawn/security.rb +59 -40
  50. data/lib/prawn/security/arcfour.rb +52 -0
  51. data/lib/prawn/soft_mask.rb +4 -4
  52. data/lib/prawn/stamp.rb +5 -3
  53. data/lib/prawn/table.rb +83 -60
  54. data/lib/prawn/table/cell.rb +17 -21
  55. data/lib/prawn/table/cell/image.rb +2 -3
  56. data/lib/prawn/table/cell/in_table.rb +8 -2
  57. data/lib/prawn/table/cell/span_dummy.rb +5 -0
  58. data/lib/prawn/table/cell/subtable.rb +3 -2
  59. data/lib/prawn/table/cell/text.rb +14 -12
  60. data/lib/prawn/table/cells.rb +58 -14
  61. data/lib/prawn/table/column_width_calculator.rb +61 -0
  62. data/lib/prawn/text.rb +27 -26
  63. data/lib/prawn/text/box.rb +12 -6
  64. data/lib/prawn/text/formatted.rb +5 -4
  65. data/lib/prawn/text/formatted/arranger.rb +290 -0
  66. data/lib/prawn/text/formatted/box.rb +85 -57
  67. data/lib/prawn/text/formatted/fragment.rb +11 -11
  68. data/lib/prawn/text/formatted/line_wrap.rb +266 -0
  69. data/lib/prawn/text/formatted/parser.rb +11 -4
  70. data/lib/prawn/text/formatted/wrap.rb +156 -0
  71. data/lib/prawn/utilities.rb +5 -3
  72. data/manual/document_and_page_options/document_and_page_options.rb +2 -1
  73. data/manual/document_and_page_options/metadata.rb +3 -3
  74. data/manual/document_and_page_options/page_size.rb +2 -2
  75. data/manual/document_and_page_options/print_scaling.rb +20 -0
  76. data/manual/example_file.rb +2 -7
  77. data/manual/example_helper.rb +62 -81
  78. data/manual/graphics/common_lines.rb +2 -0
  79. data/manual/graphics/helper.rb +11 -4
  80. data/manual/graphics/stroke_dash.rb +19 -14
  81. data/manual/manual/cover.rb +16 -0
  82. data/manual/manual/manual.rb +1 -5
  83. data/manual/text/fallback_fonts.rb +4 -4
  84. data/manual/text/formatted_text.rb +5 -5
  85. data/manual/text/inline.rb +2 -4
  86. data/manual/text/registering_families.rb +12 -12
  87. data/manual/text/single_usage.rb +4 -4
  88. data/manual/text/text.rb +0 -2
  89. data/prawn.gemspec +21 -13
  90. data/spec/acceptance/png.rb +23 -0
  91. data/spec/annotations_spec.rb +16 -32
  92. data/spec/bounding_box_spec.rb +22 -5
  93. data/spec/cell_spec.rb +49 -5
  94. data/spec/column_box_spec.rb +32 -0
  95. data/spec/destinations_spec.rb +5 -5
  96. data/spec/document_spec.rb +112 -118
  97. data/spec/extensions/encoding_helpers.rb +5 -2
  98. data/spec/font_metric_cache_spec.rb +52 -0
  99. data/spec/font_spec.rb +121 -120
  100. data/spec/formatted_text_arranger_spec.rb +24 -24
  101. data/spec/formatted_text_box_spec.rb +31 -32
  102. data/spec/formatted_text_fragment_spec.rb +2 -2
  103. data/spec/graphics_spec.rb +63 -45
  104. data/spec/grid_spec.rb +24 -13
  105. data/spec/image_handler_spec.rb +54 -0
  106. data/spec/images_spec.rb +34 -21
  107. data/spec/inline_formatted_text_parser_spec.rb +69 -20
  108. data/spec/jpg_spec.rb +3 -3
  109. data/spec/line_wrap_spec.rb +25 -14
  110. data/spec/measurement_units_spec.rb +5 -5
  111. data/spec/outline_spec.rb +68 -64
  112. data/spec/png_spec.rb +15 -18
  113. data/spec/reference_spec.rb +2 -82
  114. data/spec/repeater_spec.rb +1 -1
  115. data/spec/security_spec.rb +41 -9
  116. data/spec/soft_mask_spec.rb +0 -40
  117. data/spec/span_spec.rb +6 -11
  118. data/spec/spec_helper.rb +20 -2
  119. data/spec/stamp_spec.rb +19 -20
  120. data/spec/stroke_styles_spec.rb +31 -13
  121. data/spec/table/span_dummy_spec.rb +17 -0
  122. data/spec/table_spec.rb +268 -43
  123. data/spec/text_at_spec.rb +13 -27
  124. data/spec/text_box_spec.rb +35 -30
  125. data/spec/text_spec.rb +56 -40
  126. data/spec/transparency_spec.rb +5 -5
  127. metadata +214 -217
  128. data/README.md +0 -98
  129. data/data/fonts/Action Man.dfont +0 -0
  130. data/data/fonts/Activa.ttf +0 -0
  131. data/data/fonts/Chalkboard.ttf +0 -0
  132. data/data/fonts/DejaVuSans.ttf +0 -0
  133. data/data/fonts/Dustismo_Roman.ttf +0 -0
  134. data/data/fonts/comicsans.ttf +0 -0
  135. data/data/fonts/gkai00mp.ttf +0 -0
  136. data/data/images/16bit.dat +0 -0
  137. data/data/images/barcode_issue.png +0 -0
  138. data/data/images/dice.dat +0 -0
  139. data/data/images/page_white_text.dat +0 -0
  140. data/data/images/rails.dat +0 -0
  141. data/data/images/rails.png +0 -0
  142. data/lib/prawn/compatibility.rb +0 -87
  143. data/lib/prawn/core.rb +0 -87
  144. data/lib/prawn/core/annotations.rb +0 -61
  145. data/lib/prawn/core/byte_string.rb +0 -9
  146. data/lib/prawn/core/destinations.rb +0 -90
  147. data/lib/prawn/core/document_state.rb +0 -79
  148. data/lib/prawn/core/literal_string.rb +0 -16
  149. data/lib/prawn/core/name_tree.rb +0 -177
  150. data/lib/prawn/core/object_store.rb +0 -320
  151. data/lib/prawn/core/page.rb +0 -212
  152. data/lib/prawn/core/pdf_object.rb +0 -125
  153. data/lib/prawn/core/reference.rb +0 -119
  154. data/lib/prawn/core/text.rb +0 -268
  155. data/lib/prawn/core/text/formatted/arranger.rb +0 -294
  156. data/lib/prawn/core/text/formatted/line_wrap.rb +0 -288
  157. data/lib/prawn/core/text/formatted/wrap.rb +0 -153
  158. data/lib/prawn/document/page_geometry.rb +0 -136
  159. data/lib/prawn/document/snapshot.rb +0 -89
  160. data/manual/manual/foreword.rb +0 -13
  161. data/manual/templates/full_template.rb +0 -23
  162. data/manual/templates/page_template.rb +0 -47
  163. data/manual/templates/templates.rb +0 -26
  164. data/manual/text/group.rb +0 -29
  165. data/spec/name_tree_spec.rb +0 -112
  166. data/spec/object_store_spec.rb +0 -170
  167. data/spec/pdf_object_spec.rb +0 -172
  168. data/spec/snapshot_spec.rb +0 -186
  169. data/spec/template_spec.rb +0 -351
@@ -11,14 +11,16 @@
11
11
  module Prawn
12
12
 
13
13
  class Document
14
-
15
14
  # A list of all repeaters in the document.
16
15
  # See Document#repeat for details
17
16
  #
17
+ # @private
18
18
  def repeaters
19
19
  @repeaters ||= []
20
20
  end
21
21
 
22
+ # @group Experimental API
23
+
22
24
  # Provides a way to execute a block of code repeatedly based on a
23
25
  # page_filter. Since Stamp is used under the hood, this method is very space
24
26
  # efficient.
@@ -29,10 +31,10 @@ module Prawn
29
31
  # :even -- repeats on even pages
30
32
  # some_array -- repeats on every page listed in the array
31
33
  # some_range -- repeats on every page included in the range
32
- # some_lambda -- yields page number and repeats for true return values
34
+ # some_lambda -- yields page number and repeats for true return values
33
35
  #
34
- # Also accepts an optional second argument for dynamic content which executes the code
35
- # in the context of the filtered pages without using a Stamp.
36
+ # Also accepts an optional second argument for dynamic content which executes the code
37
+ # in the context of the filtered pages without using a Stamp.
36
38
  #
37
39
  # Example:
38
40
  #
@@ -49,8 +51,8 @@ module Prawn
49
51
  # repeat :even do
50
52
  # draw_text "EVEN", :at => [0,0]
51
53
  # end
52
- #
53
- # repeat [1,2] do
54
+ #
55
+ # repeat [1,2] do
54
56
  # draw_text "[1,2]", :at => [100,0]
55
57
  # end
56
58
  #
@@ -62,11 +64,11 @@ module Prawn
62
64
  # draw_text "Every third", :at => [250, 20]
63
65
  # end
64
66
  #
65
- # 10.times do
67
+ # 10.times do
66
68
  # start_new_page
67
69
  # draw_text "A wonderful page", :at => [400,400]
68
70
  # end
69
- #
71
+ #
70
72
  # repeat(:all, :dynamic => true) do
71
73
  # text page_number, :at => [500, 0]
72
74
  # end
@@ -7,30 +7,33 @@
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
9
  require 'digest/md5'
10
- require 'rc4'
11
- require 'prawn/core/byte_string'
10
+
11
+ require 'pdf/core/byte_string'
12
+
13
+ require 'prawn/security/arcfour'
12
14
 
13
15
  module Prawn
14
16
  class Document
15
-
17
+
16
18
  # Implements PDF encryption (password protection and permissions) as
17
19
  # specified in the PDF Reference, version 1.3, section 3.5 "Encryption".
18
20
  module Security
19
- include Prawn::Core
20
-
21
+
22
+ # @group Experimental API
23
+
21
24
  # Encrypts the document, to protect confidential data or control
22
25
  # modifications to the document. The encryption algorithm used is
23
26
  # detailed in the PDF Reference 1.3, section 3.5 "Encryption", and it is
24
27
  # implemented by all major PDF readers.
25
28
  #
26
29
  # +options+ can contain the following:
27
- #
28
- # <tt>:user_password</tt>:: Password required to open the document. If
30
+ #
31
+ # <tt>:user_password</tt>:: Password required to open the document. If
29
32
  # this is omitted or empty, no password will be
30
33
  # required. The document will still be
31
34
  # encrypted, but anyone can read it.
32
35
  #
33
- # <tt>:owner_password</tt>:: Password required to make modifications to
36
+ # <tt>:owner_password</tt>:: Password required to make modifications to
34
37
  # the document or change or override its
35
38
  # permissions. If this is set to
36
39
  # <tt>:random</tt>, a random password will be
@@ -52,7 +55,7 @@ module Prawn
52
55
  #
53
56
  # <tt>:copy_contents</tt>:: Copy text and graphics from document.
54
57
  #
55
- # <tt>:modify_annotations</tt>:: Add or modify text annotations and
58
+ # <tt>:modify_annotations</tt>:: Add or modify text annotations and
56
59
  # interactive form fields.
57
60
  #
58
61
  # == Examples
@@ -66,8 +69,8 @@ module Prawn
66
69
  # both the user and the owner:
67
70
  #
68
71
  # encrypt_document :user_password => 'foo', :owner_password => 'bar'
69
- #
70
- # Set no passwords, grant all permissions (This is useful because the
72
+ #
73
+ # Set no passwords, grant all permissions (This is useful because the
71
74
  # default in some readers, if no permissions are specified, is "deny"):
72
75
  #
73
76
  # encrypt_document
@@ -77,10 +80,10 @@ module Prawn
77
80
  # * The encryption used is weak; the key is password-derived and is
78
81
  # limited to 40 bits, due to US export controls in effect at the time
79
82
  # the PDF standard was written.
80
- #
83
+ #
81
84
  # * There is nothing technologically requiring PDF readers to respect the
82
85
  # permissions embedded in a document. Many PDF readers do not.
83
- #
86
+ #
84
87
  # * In short, you have <b>no security at all</b> against a moderately
85
88
  # motivated person. Don't use this for anything super-serious. This is
86
89
  # not a limitation of Prawn, but is rather a built-in limitation of the
@@ -104,7 +107,7 @@ module Prawn
104
107
  state.encrypt = true
105
108
  state.encryption_key = user_encryption_key
106
109
  end
107
-
110
+
108
111
  # Encrypts the given string under the given key, also requiring the
109
112
  # object ID and generation number of the reference.
110
113
  # See Algorithm 3.1.
@@ -116,7 +119,7 @@ module Prawn
116
119
 
117
120
  # Compute the RC4 key from the extended key and perform the encryption
118
121
  rc4_key = Digest::MD5.digest(extended_key)[0, 10]
119
- RC4.new(rc4_key).encrypt(str)
122
+ Arcfour.new(rc4_key).encrypt(str)
120
123
  end
121
124
 
122
125
  private
@@ -126,8 +129,8 @@ module Prawn
126
129
  { :Filter => :Standard, # default PDF security handler
127
130
  :V => 1, # "Algorithm 3.1", PDF reference 1.3
128
131
  :R => 2, # Revision 2 of the algorithm
129
- :O => ByteString.new(owner_password_hash),
130
- :U => ByteString.new(user_password_hash),
132
+ :O => PDF::Core::ByteString.new(owner_password_hash),
133
+ :U => PDF::Core::ByteString.new(user_password_hash),
131
134
  :P => permissions_value }
132
135
  end
133
136
 
@@ -136,7 +139,7 @@ module Prawn
136
139
  :modify_contents => 4,
137
140
  :copy_contents => 5,
138
141
  :modify_annotations => 6 }
139
-
142
+
140
143
  FullPermissions = 0b1111_1111_1111_1111_1111_1111_1111_1111
141
144
 
142
145
  def permissions=(perms={})
@@ -162,10 +165,10 @@ module Prawn
162
165
  @permissions || FullPermissions
163
166
  end
164
167
 
165
- PasswordPadding =
168
+ PasswordPadding =
166
169
  "28BF4E5E4E758A4164004E56FFFA01082E2E00B6D0683E802F0CA9FE6453697A".
167
170
  scan(/../).map{|x| x.to_i(16)}.pack("c*")
168
-
171
+
169
172
  # Pads or truncates a password to 32 bytes as per Alg 3.2.
170
173
  def pad_password(password)
171
174
  password = password[0, 32]
@@ -186,50 +189,53 @@ module Prawn
186
189
  def owner_password_hash
187
190
  @owner_password_hash ||= begin
188
191
  key = Digest::MD5.digest(pad_password(@owner_password))[0, 5]
189
- RC4.new(key).encrypt(pad_password(@user_password))
192
+ Arcfour.new(key).encrypt(pad_password(@user_password))
190
193
  end
191
194
  end
192
195
 
193
196
  # The U (user) value in the encryption dictionary. Algorithm 3.4.
194
197
  def user_password_hash
195
- RC4.new(user_encryption_key).encrypt(PasswordPadding)
198
+ Arcfour.new(user_encryption_key).encrypt(PasswordPadding)
196
199
  end
197
200
 
198
201
  end
199
202
 
200
203
  end
204
+ end
201
205
 
202
- module Core #:nodoc:
206
+ # @private
207
+ module PDF
208
+ module Core
203
209
  module_function
204
210
 
205
211
  # Like PdfObject, but returns an encrypted result if required.
206
212
  # For direct objects, requires the object identifier and generation number
207
213
  # from the indirect object referencing obj.
208
- def EncryptedPdfObject(obj, key, id, gen, in_content_stream=false)
214
+ #
215
+ # @private
216
+ def EncryptedPdfObject(obj, key, id, gen, in_content_stream=false)
209
217
  case obj
210
218
  when Array
211
219
  "[" << obj.map { |e|
212
220
  EncryptedPdfObject(e, key, id, gen, in_content_stream)
213
221
  }.join(' ') << "]"
214
222
  when LiteralString
215
- # FIXME: encrypted?
216
- obj = obj.gsub(/[\\\n\(\)]/) { |m| "\\#{m}" }
223
+ obj = ByteString.new(Prawn::Document::Security.encrypt_string(obj, key, id, gen)).gsub(/[\\\n\(\)]/) { |m| "\\#{m}" }
217
224
  "(#{obj})"
218
225
  when Time
219
- # FIXME: encrypted?
220
226
  obj = obj.strftime("D:%Y%m%d%H%M%S%z").chop.chop + "'00'"
221
- obj = obj.gsub(/[\\\n\(\)]/) { |m| "\\#{m}" }
227
+ obj = ByteString.new(Prawn::Document::Security.encrypt_string(obj, key, id, gen)).gsub(/[\\\n\(\)]/) { |m| "\\#{m}" }
222
228
  "(#{obj})"
223
229
  when String
224
230
  PdfObject(
225
231
  ByteString.new(
226
- Document::Security.encrypt_string(obj, key, id, gen)),
232
+ Prawn::Document::Security.encrypt_string(obj, key, id, gen)),
227
233
  in_content_stream)
228
- when Hash
234
+ when ::Hash
229
235
  output = "<< "
230
236
  obj.each do |k,v|
231
237
  unless String === k || Symbol === k
232
- raise Prawn::Errors::FailedObjectConversion,
238
+ raise PDF::Core::Errors::FailedObjectConversion,
233
239
  "A PDF Dictionary must be keyed by names"
234
240
  end
235
241
  output << PdfObject(k.to_sym, in_content_stream) << " " <<
@@ -239,31 +245,44 @@ module Prawn
239
245
  when NameTree::Value
240
246
  PdfObject(obj.name) + " " +
241
247
  EncryptedPdfObject(obj.value, key, id, gen, in_content_stream)
242
- when Prawn::OutlineRoot, Prawn::OutlineItem
248
+ when PDF::Core::OutlineRoot, PDF::Core::OutlineItem
243
249
  EncryptedPdfObject(obj.to_hash, key, id, gen, in_content_stream)
244
250
  else # delegate back to PdfObject
245
251
  PdfObject(obj, in_content_stream)
246
252
  end
247
253
  end
248
254
 
255
+
256
+ # @private
257
+ class Stream
258
+ def encrypted_object(key, id, gen)
259
+ if filtered_stream
260
+ "stream\n#{Prawn::Document::Security.encrypt_string filtered_stream, key, id, gen}\nendstream\n"
261
+ else
262
+ ''
263
+ end
264
+ end
265
+ end
266
+
267
+ # @private
249
268
  class Reference
250
269
 
251
270
  # Returns the object definition for the object this references, keyed from
252
271
  # +key+.
253
272
  def encrypted_object(key)
254
273
  @on_encode.call(self) if @on_encode
255
- output = "#{@identifier} #{gen} obj\n" <<
256
- Prawn::Core::EncryptedPdfObject(data, key, @identifier, gen) << "\n"
257
- if @stream
258
- output << "stream\n" <<
259
- Document::Security.encrypt_string(@stream, key, @identifier, gen) <<
260
- "\nendstream\n"
274
+
275
+ output = "#{@identifier} #{gen} obj\n"
276
+ unless @stream.empty?
277
+ output << PDF::Core::EncryptedPdfObject(data.merge(@stream.data), key, @identifier, gen) << "\n" <<
278
+ @stream.encrypted_object(key, @identifier, gen)
279
+ else
280
+ output << PDF::Core::EncryptedPdfObject(data, key, @identifier, gen) << "\n"
261
281
  end
282
+
262
283
  output << "endobj\n"
263
284
  end
264
285
 
265
286
  end
266
287
  end
267
-
268
288
  end
269
-
@@ -0,0 +1,52 @@
1
+ # Implementation of the "ARCFOUR" algorithm ("alleged RC4 (tm)"). Implemented
2
+ # as described at:
3
+ # http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt
4
+ #
5
+ # "RC4" is a trademark of RSA Data Security, Inc.
6
+ #
7
+ # Copyright August 2009, Brad Ediger. All Rights Reserved.
8
+ #
9
+ # This is free software. Please see the LICENSE and COPYING files for details.
10
+
11
+ # @private
12
+ class Arcfour
13
+ def initialize(key)
14
+ # Convert string key to Array of integers
15
+ key = key.unpack('c*') if key.is_a?(String)
16
+
17
+ # 1. Allocate an 256 element array of 8 bit bytes to be used as an S-box
18
+ # 2. Initialize the S-box. Fill each entry first with it's index
19
+ @sbox = (0..255).to_a
20
+
21
+ # 3. Fill another array of the same size (256) with the key, repeating
22
+ # bytes as necessary.
23
+ s2 = []
24
+ while s2.length < 256
25
+ s2 += key
26
+ end
27
+ s2 = s2[0, 256]
28
+
29
+ # 4. Set j to zero and initialize the S-box
30
+ j = 0
31
+ (0..255).each do |i|
32
+ j = (j + @sbox[i] + s2[i]) % 256
33
+ @sbox[i], @sbox[j] = @sbox[j], @sbox[i]
34
+ end
35
+
36
+ @i = @j = 0
37
+ end
38
+
39
+ def encrypt(string)
40
+ string.unpack('c*').map{|byte| byte ^ key_byte}.pack('c*')
41
+ end
42
+
43
+ private
44
+
45
+ # Produces the next byte of key material in the stream (3.2 Stream Generation)
46
+ def key_byte
47
+ @i = (@i + 1) % 256
48
+ @j = (@j + @sbox[@i]) % 256
49
+ @sbox[@i], @sbox[@j] = @sbox[@j], @sbox[@i]
50
+ @sbox[(@sbox[@i] + @sbox[@j]) % 256]
51
+ end
52
+ end
@@ -10,7 +10,7 @@
10
10
  module Prawn
11
11
 
12
12
  # The Prawn::SoftMask module is used to create arbitrary transparency in
13
- # document. Using a soft mask allows creaing more visually rich documents.
13
+ # document. Using a soft mask allows creating more visually rich documents.
14
14
  #
15
15
  # You must group soft mask and graphics it's applied to under
16
16
  # save_graphics_state because soft mask is a part of graphic state in PDF.
@@ -26,6 +26,8 @@ module Prawn
26
26
  # end
27
27
  #
28
28
  module SoftMask
29
+ # @group Stable API
30
+
29
31
  def soft_mask(&block)
30
32
  min_version(1.4)
31
33
 
@@ -66,13 +68,11 @@ module Prawn
66
68
 
67
69
  registry_key = {
68
70
  :bbox => state.page.dimensions,
69
- :mask => group.stream,
71
+ :mask => [group.stream.filters.normalized, group.stream.filtered_stream],
70
72
  :page => state.page_count,
71
73
  }.hash
72
74
 
73
75
  if soft_mask_registry[registry_key]
74
- [g_state, mask, group, group_attrs].each { |ref| ref.live = false }
75
-
76
76
  add_content "/#{soft_mask_registry[registry_key]} gs"
77
77
  else
78
78
  masks = page.resources[:ExtGState] ||= {}
@@ -28,6 +28,8 @@ module Prawn
28
28
  #
29
29
  module Stamp
30
30
 
31
+ # @group Stable API
32
+
31
33
  # Renders the stamp named <tt>name</tt> to the page
32
34
  # raises <tt>Prawn::Errors::InvalidName</tt> if name.empty?
33
35
  # raises <tt>Prawn::Errors::UndefinedObjectName</tt> if no stamp
@@ -80,7 +82,7 @@ module Prawn
80
82
 
81
83
  state.page.stamp_stream(dictionary, &block)
82
84
  end
83
-
85
+
84
86
  private
85
87
 
86
88
  def stamp_dictionary_registry
@@ -121,12 +123,12 @@ module Prawn
121
123
  :stamp_dictionary => dictionary }
122
124
  dictionary
123
125
  end
124
-
126
+
125
127
  def freeze_stamp_graphics
126
128
  update_colors
127
129
  write_line_width
128
130
  write_stroke_cap_style
129
- write_stroke_join_style
131
+ write_stroke_join_style
130
132
  write_stroke_dash
131
133
  end
132
134
 
@@ -6,18 +6,21 @@
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
- require 'prawn/table/cells'
10
- require 'prawn/table/cell'
11
- require 'prawn/table/cell/in_table'
12
- require 'prawn/table/cell/text'
13
- require 'prawn/table/cell/subtable'
14
- require 'prawn/table/cell/image'
15
- require 'prawn/table/cell/span_dummy'
9
+ require_relative 'table/column_width_calculator'
10
+ require_relative 'table/cell'
11
+ require_relative 'table/cells'
12
+ require_relative 'table/cell/in_table'
13
+ require_relative 'table/cell/text'
14
+ require_relative 'table/cell/subtable'
15
+ require_relative 'table/cell/image'
16
+ require_relative 'table/cell/span_dummy'
16
17
 
17
18
  module Prawn
18
19
 
19
20
  class Document
20
-
21
+
22
+ # @group Experimental API
23
+
21
24
  # Set up and draw a table on this document. A block can be given, which will
22
25
  # be run after cell setup but before layout and drawing.
23
26
  #
@@ -51,7 +54,7 @@ module Prawn
51
54
  # Produces a text cell. This is the most common usage.
52
55
  # Prawn::Table::Cell::
53
56
  # If you have already built a Cell or have a custom subclass of Cell you
54
- # want to use in a table, you can pass through Cell objects.
57
+ # want to use in a table, you can pass through Cell objects.
55
58
  # Prawn::Table::
56
59
  # Creates a subtable (a table within a cell). You can use
57
60
  # Prawn::Document#make_table to create a table for use as a subtable
@@ -73,12 +76,12 @@ module Prawn
73
76
  # A hash of style options to style all cells. See the documentation on
74
77
  # Prawn::Table::Cell for all cell style options.
75
78
  # +header+::
76
- # If set to +true+, the first row will be repeated on every page. The
77
- # header must be included as the first row of your data. Row numbering
78
- # (for styling and other row-specific options) always indexes based on
79
- # your data array. Whether or not you have a header, row(n) always refers
80
- # to the nth element (starting from 0) of the +data+ array.
81
- # +column_widths+::
79
+ # If set to +true+, the first row will be repeated on every page. If set
80
+ # to an Integer, the first +x+ rows will be repeated on every page. Row
81
+ # numbering (for styling and other row-specific options) always indexes
82
+ # based on your data array. Whether or not you have a header, row(n) always
83
+ # refers to the nth element (starting from 0) of the +data+ array.
84
+ # +column_widths+::
82
85
  # Sets widths for individual columns. Manually setting widths can give
83
86
  # better results than letting Prawn guess at them, as Prawn's algorithm
84
87
  # for defaulting widths is currently pretty boneheaded. If you experience
@@ -101,7 +104,7 @@ module Prawn
101
104
  # pdf.table(data) do |table|
102
105
  # table.rows(1..3).width = 72
103
106
  # end
104
- #
107
+ #
105
108
  # As with Prawn::Document#initialize, if the block has no arguments, it will
106
109
  # be evaluated in the context of the object itself. The above code could be
107
110
  # rewritten as:
@@ -110,7 +113,7 @@ module Prawn
110
113
  # rows(1..3).width = 72
111
114
  # end
112
115
  #
113
- class Table
116
+ class Table
114
117
 
115
118
  # Set up a table on the given document. Arguments:
116
119
  #
@@ -137,7 +140,7 @@ module Prawn
137
140
  set_column_widths
138
141
  set_row_heights
139
142
  position_cells
140
- end
143
+ end
141
144
 
142
145
  # Number of rows in the table.
143
146
  #
@@ -165,7 +168,7 @@ module Prawn
165
168
  # The block is passed a Cells object containing all cells to be rendered on
166
169
  # that page. You can change styling of the cells in this block, but keep in
167
170
  # mind that the cells have already been positioned and sized.
168
- #
171
+ #
169
172
  def before_rendering_page(&block)
170
173
  @before_rendering_page = block
171
174
  end
@@ -179,9 +182,9 @@ module Prawn
179
182
  # Sets column widths for the table. The argument can be one of the following
180
183
  # types:
181
184
  #
182
- # +Array+::
185
+ # +Array+::
183
186
  # <tt>[w0, w1, w2, ...]</tt> (specify a width for each column)
184
- # +Hash+::
187
+ # +Hash+::
185
188
  # <tt>{0 => w0, 1 => w1, ...}</tt> (keys are column names, values are
186
189
  # widths)
187
190
  # +Numeric+::
@@ -207,8 +210,9 @@ module Prawn
207
210
  end
208
211
 
209
212
  # If +true+, designates the first row as a header row to be repeated on
210
- # every page. Does not change row numbering -- row numbers always index into
211
- # the data array provided, with no modification.
213
+ # every page. If an integer, designates the number of rows to be treated
214
+ # as a header Does not change row numbering -- row numbers always index
215
+ # into the data array provided, with no modification.
212
216
  #
213
217
  attr_writer :header
214
218
 
@@ -277,7 +281,13 @@ module Prawn
277
281
  # If there isn't enough room left on the page to fit the first data row
278
282
  # (excluding the header), start the table on the next page.
279
283
  needed_height = row(0).height
280
- needed_height += row(1).height if @header
284
+ if @header
285
+ if @header.is_a? Integer
286
+ needed_height += row(1..@header).height
287
+ else
288
+ needed_height += row(1).height
289
+ end
290
+ end
281
291
  if needed_height > @pdf.y - ref_bounds.absolute_bottom
282
292
  @pdf.bounds.move_past_bottom
283
293
  offset = @pdf.y
@@ -289,7 +299,13 @@ module Prawn
289
299
  # modified in before_rendering_page callbacks.
290
300
  if @header
291
301
  @header_row = Cells.new
292
- row(0).each { |cell| @header_row[cell.row, cell.column] = cell.dup }
302
+ if @header.is_a? Integer
303
+ @header.times do |r|
304
+ row(r).each { |cell| @header_row[cell.row, cell.column] = cell.dup }
305
+ end
306
+ else
307
+ row(0).each { |cell| @header_row[cell.row, cell.column] = cell.dup }
308
+ end
293
309
  end
294
310
 
295
311
  # Track cells to be drawn on this page. They will all be drawn when this
@@ -300,8 +316,8 @@ module Prawn
300
316
  if cell.height > (cell.y + offset) - ref_bounds.absolute_bottom &&
301
317
  cell.row > started_new_page_at_row
302
318
  # Ink all cells on the current page
303
- if @before_rendering_page
304
- c = Cells.new(cells_this_page.map { |c, _| c })
319
+ if defined?(@before_rendering_page) && @before_rendering_page
320
+ c = Cells.new(cells_this_page.map { |ci, _| ci })
305
321
  @before_rendering_page.call(c)
306
322
  end
307
323
  Cell.draw_cells(cells_this_page)
@@ -309,31 +325,49 @@ module Prawn
309
325
 
310
326
  # start a new page or column
311
327
  @pdf.bounds.move_past_bottom
328
+ x_offset = @pdf.bounds.left_side - @pdf.bounds.absolute_left
312
329
  if cell.row > 0 && @header
313
- header_height = add_header(cells_this_page, @pdf.cursor, cell.row-1)
330
+ if @header.is_a? Integer
331
+ header_height = 0
332
+ y_coord = @pdf.cursor
333
+ @header.times do |h|
334
+ additional_header_height = add_header(cells_this_page, x_offset, y_coord-header_height, cell.row-1, h)
335
+ header_height += additional_header_height
336
+ end
337
+ else
338
+ header_height = add_header(cells_this_page, x_offset, @pdf.cursor, cell.row-1)
339
+ end
314
340
  else
315
341
  header_height = 0
316
342
  end
317
343
  offset = @pdf.y - cell.y - header_height
318
344
  started_new_page_at_row = cell.row
319
345
  end
320
-
346
+
321
347
  # Don't modify cell.x / cell.y here, as we want to reuse the original
322
348
  # values when re-inking the table. #draw should be able to be called
323
349
  # multiple times.
324
350
  x, y = cell.x, cell.y
325
- y += offset
351
+ y += offset
326
352
 
327
- # Translate coordinates to the bounds we are in, since drawing is
353
+ # Translate coordinates to the bounds we are in, since drawing is
328
354
  # relative to the cursor, not ref_bounds.
329
355
  x += @pdf.bounds.left_side - @pdf.bounds.absolute_left
330
356
  y -= @pdf.bounds.absolute_bottom
331
357
 
332
358
  # Set background color, if any.
333
- if @row_colors && (!@header || cell.row > 0)
359
+ if defined?(@row_colors) && @row_colors && (!@header || cell.row > 0)
334
360
  # Ensure coloring restarts on every page (to make sure the header
335
361
  # and first row of a page are not colored the same way).
336
- index = cell.row - [started_new_page_at_row, @header ? 1 : 0].max
362
+ if @header.is_a? Integer
363
+ rows = @header
364
+ elsif @header
365
+ rows = 1
366
+ else
367
+ rows = 0
368
+ end
369
+ index = cell.row - [started_new_page_at_row, rows].max
370
+
337
371
  cell.background_color ||= @row_colors[index % @row_colors.length]
338
372
  end
339
373
 
@@ -341,8 +375,8 @@ module Prawn
341
375
  last_y = y
342
376
  end
343
377
  # Draw the last page of cells
344
- if @before_rendering_page
345
- c = Cells.new(cells_this_page.map { |c, _| c })
378
+ if defined?(@before_rendering_page) && @before_rendering_page
379
+ c = Cells.new(cells_this_page.map { |ci, _| ci })
346
380
  @before_rendering_page.call(c)
347
381
  end
348
382
  Cell.draw_cells(cells_this_page)
@@ -426,7 +460,7 @@ module Prawn
426
460
  assert_proper_table_data(data)
427
461
 
428
462
  cells = Cells.new
429
-
463
+
430
464
  row_number = 0
431
465
  data.each do |row_cells|
432
466
  column_number = 0
@@ -449,7 +483,7 @@ module Prawn
449
483
  next if i == 0 && j == 0
450
484
 
451
485
  # It is an error to specify spans that overlap; catch this here
452
- if bad_cell = cells[row_number + i, column_number + j]
486
+ if cells[row_number + i, column_number + j]
453
487
  raise Prawn::Errors::InvalidTableSpan,
454
488
  "Spans overlap at row #{row_number + i}, " +
455
489
  "column #{column_number + j}."
@@ -481,19 +515,22 @@ module Prawn
481
515
  cells
482
516
  end
483
517
 
484
- # Add the header row to the given array of cells at the given y-position.
518
+ # Add the header row(s) to the given array of cells at the given y-position.
485
519
  # Number the row with the given +row+ index, so that the header appears (in
486
520
  # any Cells built for this page) immediately prior to the first data row on
487
521
  # this page.
488
522
  #
489
523
  # Return the height of the header.
490
524
  #
491
- def add_header(page_of_cells, y, row)
492
- @header_row.each do |cell|
525
+ def add_header(page_of_cells, x_offset, y, row, row_of_header=nil)
526
+ rows_to_operate_on = @header_row
527
+ rows_to_operate_on = @header_row.rows(row_of_header) if row_of_header
528
+ rows_to_operate_on.each do |cell|
493
529
  cell.row = row
494
- page_of_cells << [cell, [cell.x, y]]
530
+ cell.dummy_cells.each {|c| c.row = row }
531
+ page_of_cells << [cell, [cell.x + x_offset, y]]
495
532
  end
496
- @header_row.height
533
+ rows_to_operate_on.height
497
534
  end
498
535
 
499
536
  # Raises an error if the data provided cannot be converted into a valid
@@ -515,21 +552,7 @@ module Prawn
515
552
  # Returns an array of each column's natural (unconstrained) width.
516
553
  #
517
554
  def natural_column_widths
518
- @natural_column_widths ||=
519
- begin
520
- widths_by_column = Hash.new(0)
521
- cells.each do |cell|
522
- next if cell.is_a?(Cell::SpanDummy)
523
-
524
- # Split the width of colspanned cells evenly by columns
525
- width_per_column = cell.width.to_f / cell.colspan
526
- cell.colspan.times do |i|
527
- widths_by_column[cell.column + i] =
528
- [widths_by_column[cell.column + i], width_per_column].max
529
- end
530
- end
531
- widths_by_column.sort_by { |col, _| col }.map { |_, w| w }
532
- end
555
+ @natural_column_widths ||= ColumnWidthCalculator.new(cells).natural_widths
533
556
  end
534
557
 
535
558
  # Returns the "natural" (unconstrained) width of the table. This may be
@@ -547,7 +570,7 @@ module Prawn
547
570
  # values that will be used to ink the table.
548
571
  #
549
572
  def set_column_widths
550
- column_widths.each_with_index do |w, col_num|
573
+ column_widths.each_with_index do |w, col_num|
551
574
  column(col_num).width = w
552
575
  end
553
576
  end
@@ -564,7 +587,7 @@ module Prawn
564
587
  #
565
588
  def position_cells
566
589
  # Calculate x- and y-positions as running sums of widths / heights.
567
- x_positions = column_widths.inject([0]) { |ary, x|
590
+ x_positions = column_widths.inject([0]) { |ary, x|
568
591
  ary << (ary.last + x); ary }[0..-2]
569
592
  x_positions.each_with_index { |x, i| column(i).x = x }
570
593
 
@@ -579,7 +602,7 @@ module Prawn
579
602
  # :position option, and yields.
580
603
  #
581
604
  def with_position
582
- x = case @position || :left
605
+ x = case defined?(@position) && @position || :left
583
606
  when :left then return yield
584
607
  when :center then (@pdf.bounds.width - width) / 2.0
585
608
  when :right then @pdf.bounds.width - width