prawn 1.0.0.rc1 → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. data/Gemfile +18 -0
  2. data/README.md +5 -3
  3. data/Rakefile +8 -14
  4. data/data/pdfs/nested_pages.pdf +13 -13
  5. data/lib/prawn.rb +3 -1
  6. data/lib/prawn/compatibility.rb +46 -10
  7. data/lib/prawn/core.rb +3 -1
  8. data/lib/prawn/core/document_state.rb +2 -1
  9. data/lib/prawn/core/object_store.rb +61 -5
  10. data/lib/prawn/core/page.rb +3 -6
  11. data/lib/prawn/core/pdf_object.rb +21 -4
  12. data/lib/prawn/core/reference.rb +6 -2
  13. data/lib/prawn/core/text.rb +4 -4
  14. data/lib/prawn/core/text/formatted/line_wrap.rb +23 -8
  15. data/lib/prawn/document.rb +21 -15
  16. data/lib/prawn/document/bounding_box.rb +3 -3
  17. data/lib/prawn/document/column_box.rb +22 -4
  18. data/lib/prawn/document/snapshot.rb +1 -1
  19. data/lib/prawn/encoding.rb +1 -1
  20. data/lib/prawn/errors.rb +4 -0
  21. data/lib/prawn/font.rb +1 -1
  22. data/lib/prawn/font/afm.rb +30 -72
  23. data/lib/prawn/font/ttf.rb +6 -33
  24. data/lib/prawn/graphics.rb +148 -23
  25. data/lib/prawn/graphics/color.rb +8 -1
  26. data/lib/prawn/graphics/patterns.rb +137 -0
  27. data/lib/prawn/images.rb +25 -19
  28. data/lib/prawn/images/jpg.rb +4 -4
  29. data/lib/prawn/images/png.rb +18 -12
  30. data/lib/prawn/security.rb +6 -4
  31. data/lib/prawn/soft_mask.rb +94 -0
  32. data/lib/prawn/table.rb +136 -31
  33. data/lib/prawn/table/cell.rb +260 -29
  34. data/lib/prawn/table/cell/span_dummy.rb +88 -0
  35. data/lib/prawn/table/cell/text.rb +36 -14
  36. data/lib/prawn/table/cells.rb +91 -41
  37. data/lib/prawn/text.rb +3 -2
  38. data/lib/prawn/text/formatted/box.rb +14 -5
  39. data/lib/prawn/text/formatted/fragment.rb +33 -22
  40. data/lib/prawn/text/formatted/parser.rb +5 -2
  41. data/lib/prawn/utilities.rb +44 -0
  42. data/manual/basic_concepts/adding_pages.rb +27 -0
  43. data/manual/basic_concepts/basic_concepts.rb +34 -0
  44. data/manual/basic_concepts/creation.rb +39 -0
  45. data/manual/basic_concepts/cursor.rb +33 -0
  46. data/manual/basic_concepts/measurement.rb +25 -0
  47. data/manual/basic_concepts/origin.rb +38 -0
  48. data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
  49. data/manual/bounding_box/bounding_box.rb +39 -0
  50. data/manual/bounding_box/bounds.rb +49 -0
  51. data/manual/bounding_box/canvas.rb +24 -0
  52. data/manual/bounding_box/creation.rb +23 -0
  53. data/manual/bounding_box/indentation.rb +46 -0
  54. data/manual/bounding_box/nesting.rb +45 -0
  55. data/manual/bounding_box/russian_boxes.rb +40 -0
  56. data/manual/bounding_box/stretchy.rb +31 -0
  57. data/manual/document_and_page_options/background.rb +27 -0
  58. data/manual/document_and_page_options/document_and_page_options.rb +31 -0
  59. data/manual/document_and_page_options/metadata.rb +23 -0
  60. data/manual/document_and_page_options/page_margins.rb +38 -0
  61. data/manual/document_and_page_options/page_size.rb +34 -0
  62. data/manual/example_file.rb +116 -0
  63. data/manual/example_helper.rb +430 -0
  64. data/manual/example_package.rb +53 -0
  65. data/manual/example_section.rb +46 -0
  66. data/manual/graphics/circle_and_ellipse.rb +22 -0
  67. data/manual/graphics/color.rb +24 -0
  68. data/manual/graphics/common_lines.rb +28 -0
  69. data/manual/graphics/fill_and_stroke.rb +42 -0
  70. data/manual/graphics/fill_rules.rb +37 -0
  71. data/manual/graphics/gradients.rb +37 -0
  72. data/manual/graphics/graphics.rb +58 -0
  73. data/manual/graphics/helper.rb +17 -0
  74. data/manual/graphics/line_width.rb +35 -0
  75. data/manual/graphics/lines_and_curves.rb +41 -0
  76. data/manual/graphics/polygon.rb +29 -0
  77. data/manual/graphics/rectangle.rb +21 -0
  78. data/manual/graphics/rotate.rb +28 -0
  79. data/manual/graphics/scale.rb +41 -0
  80. data/manual/graphics/soft_masks.rb +46 -0
  81. data/manual/graphics/stroke_cap.rb +31 -0
  82. data/manual/graphics/stroke_dash.rb +43 -0
  83. data/manual/graphics/stroke_join.rb +30 -0
  84. data/manual/graphics/translate.rb +29 -0
  85. data/manual/graphics/transparency.rb +35 -0
  86. data/manual/images/absolute_position.rb +23 -0
  87. data/manual/images/fit.rb +21 -0
  88. data/manual/images/horizontal.rb +25 -0
  89. data/manual/images/images.rb +40 -0
  90. data/manual/images/plain_image.rb +18 -0
  91. data/manual/images/scale.rb +22 -0
  92. data/manual/images/vertical.rb +28 -0
  93. data/manual/images/width_and_height.rb +25 -0
  94. data/manual/layout/boxes.rb +27 -0
  95. data/manual/layout/content.rb +25 -0
  96. data/manual/layout/layout.rb +28 -0
  97. data/manual/layout/simple_grid.rb +23 -0
  98. data/manual/manual/cover.rb +26 -0
  99. data/manual/manual/foreword.rb +13 -0
  100. data/manual/manual/how_to_read_this_manual.rb +41 -0
  101. data/manual/manual/manual.rb +36 -0
  102. data/manual/outline/add_subsection_to.rb +61 -0
  103. data/manual/outline/insert_section_after.rb +47 -0
  104. data/manual/outline/outline.rb +32 -0
  105. data/manual/outline/sections_and_pages.rb +67 -0
  106. data/manual/repeatable_content/page_numbering.rb +54 -0
  107. data/manual/repeatable_content/repeatable_content.rb +31 -0
  108. data/manual/repeatable_content/repeater.rb +55 -0
  109. data/manual/repeatable_content/stamp.rb +41 -0
  110. data/manual/security/encryption.rb +31 -0
  111. data/manual/security/permissions.rb +38 -0
  112. data/manual/security/security.rb +28 -0
  113. data/manual/syntax_highlight.rb +52 -0
  114. data/manual/table/basic_block.rb +53 -0
  115. data/manual/table/before_rendering_page.rb +26 -0
  116. data/manual/table/cell_border_lines.rb +24 -0
  117. data/manual/table/cell_borders_and_bg.rb +31 -0
  118. data/manual/table/cell_dimensions.rb +30 -0
  119. data/manual/table/cell_text.rb +38 -0
  120. data/manual/table/column_widths.rb +30 -0
  121. data/manual/table/content_and_subtables.rb +39 -0
  122. data/manual/table/creation.rb +27 -0
  123. data/manual/table/filtering.rb +36 -0
  124. data/manual/table/flow_and_header.rb +17 -0
  125. data/manual/table/image_cells.rb +33 -0
  126. data/manual/table/position.rb +29 -0
  127. data/manual/table/row_colors.rb +20 -0
  128. data/manual/table/span.rb +30 -0
  129. data/manual/table/style.rb +22 -0
  130. data/manual/table/table.rb +52 -0
  131. data/manual/table/width.rb +27 -0
  132. data/manual/templates/full_template.rb +23 -0
  133. data/manual/templates/page_template.rb +47 -0
  134. data/manual/templates/templates.rb +26 -0
  135. data/manual/text/alignment.rb +44 -0
  136. data/manual/text/color.rb +24 -0
  137. data/manual/text/column_box.rb +32 -0
  138. data/manual/text/fallback_fonts.rb +37 -0
  139. data/manual/text/font.rb +41 -0
  140. data/manual/text/font_size.rb +45 -0
  141. data/manual/text/font_style.rb +23 -0
  142. data/manual/text/formatted_callbacks.rb +60 -0
  143. data/manual/text/formatted_text.rb +50 -0
  144. data/manual/text/free_flowing_text.rb +51 -0
  145. data/manual/text/group.rb +29 -0
  146. data/manual/text/inline.rb +43 -0
  147. data/manual/text/kerning_and_character_spacing.rb +39 -0
  148. data/manual/text/leading.rb +25 -0
  149. data/manual/text/line_wrapping.rb +41 -0
  150. data/manual/text/paragraph_indentation.rb +26 -0
  151. data/manual/text/positioned_text.rb +38 -0
  152. data/manual/text/registering_families.rb +48 -0
  153. data/manual/text/rendering_and_color.rb +37 -0
  154. data/manual/text/right_to_left_text.rb +43 -0
  155. data/manual/text/rotation.rb +43 -0
  156. data/manual/text/single_usage.rb +37 -0
  157. data/manual/text/text.rb +75 -0
  158. data/manual/text/text_box_excess.rb +32 -0
  159. data/manual/text/text_box_extensions.rb +45 -0
  160. data/manual/text/text_box_overflow.rb +44 -0
  161. data/manual/text/utf8.rb +28 -0
  162. data/manual/text/win_ansi_charset.rb +59 -0
  163. data/prawn.gemspec +10 -7
  164. data/spec/bounding_box_spec.rb +107 -17
  165. data/spec/cell_spec.rb +66 -40
  166. data/spec/column_box_spec.rb +33 -0
  167. data/spec/document_spec.rb +45 -24
  168. data/spec/extensions/encoding_helpers.rb +6 -0
  169. data/spec/extensions/mocha.rb +1 -0
  170. data/spec/font_spec.rb +71 -53
  171. data/spec/formatted_text_arranger_spec.rb +19 -19
  172. data/spec/formatted_text_box_spec.rb +16 -16
  173. data/spec/formatted_text_fragment_spec.rb +6 -6
  174. data/spec/graphics_spec.rb +96 -31
  175. data/spec/grid_spec.rb +2 -2
  176. data/spec/images_spec.rb +18 -10
  177. data/spec/jpg_spec.rb +1 -1
  178. data/spec/line_wrap_spec.rb +14 -14
  179. data/spec/measurement_units_spec.rb +2 -2
  180. data/spec/name_tree_spec.rb +6 -6
  181. data/spec/object_store_spec.rb +17 -17
  182. data/spec/outline_spec.rb +35 -17
  183. data/spec/pdf_object_spec.rb +3 -1
  184. data/spec/png_spec.rb +22 -19
  185. data/spec/reference_spec.rb +24 -1
  186. data/spec/repeater_spec.rb +9 -9
  187. data/spec/security_spec.rb +3 -3
  188. data/spec/snapshot_spec.rb +3 -3
  189. data/spec/soft_mask_spec.rb +117 -0
  190. data/spec/span_spec.rb +4 -4
  191. data/spec/spec_helper.rb +12 -6
  192. data/spec/stamp_spec.rb +12 -12
  193. data/spec/stroke_styles_spec.rb +5 -5
  194. data/spec/table_spec.rb +458 -88
  195. data/spec/template_spec.rb +108 -54
  196. data/spec/text_at_spec.rb +17 -17
  197. data/spec/text_box_spec.rb +76 -45
  198. data/spec/text_rendering_mode_spec.rb +5 -5
  199. data/spec/text_spacing_spec.rb +4 -4
  200. data/spec/text_spec.rb +44 -40
  201. metadata +419 -250
  202. data/lib/prawn/graphics/gradient.rb +0 -84
  203. data/lib/prawn/security/arcfour.rb +0 -51
@@ -34,7 +34,9 @@ module Prawn
34
34
 
35
35
  def <<(data)
36
36
  raise 'Cannot add data to a stream that is compressed' if @compressed
37
- (@stream ||= "") << data
37
+ (@stream ||= "") << data
38
+ @data[:Length] = @stream.length
39
+ @stream
38
40
  end
39
41
 
40
42
  def to_s
@@ -44,7 +46,7 @@ module Prawn
44
46
  def compress_stream
45
47
  @stream = Zlib::Deflate.deflate(@stream)
46
48
  @data[:Filter] = :FlateDecode
47
- @data[:Length] ||= @stream.length
49
+ @data[:Length] = @stream.length
48
50
  @compressed = true
49
51
  end
50
52
 
@@ -100,6 +102,8 @@ module Prawn
100
102
  obj.values.map{|v| [v] + referenced_objects(v) }
101
103
  when Array
102
104
  obj.map{|v| [v] + referenced_objects(v) }
105
+ when OutlineRoot, OutlineItem
106
+ referenced_objects(obj.to_hash)
103
107
  else []
104
108
  end.flatten.grep(self.class)
105
109
  end
@@ -131,11 +131,11 @@ module Prawn
131
131
  #
132
132
  # Call with an empty array to turn off fallback fonts
133
133
  #
134
- # file = "#{Prawn::BASEDIR}/data/fonts/gkai00mp.ttf"
134
+ # file = "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
135
135
  # font_families["Kai"] = {
136
136
  # :normal => { :file => file, :font => "Kai" }
137
137
  # }
138
- # file = "#{Prawn::BASEDIR}/data/fonts/Action Man.dfont"
138
+ # file = "#{Prawn::DATADIR}/fonts/Action Man.dfont"
139
139
  # font_families["Action Man"] = {
140
140
  # :normal => { :file => file, :font => "ActionMan" },
141
141
  # }
@@ -186,10 +186,10 @@ module Prawn
186
186
  #
187
187
  def text_rendering_mode(mode=nil)
188
188
  return @text_rendering_mode || :fill if mode.nil?
189
- unless MODES.keys.include?(mode)
189
+ unless MODES.key?(mode)
190
190
  raise ArgumentError, "mode must be between one of #{MODES.keys.join(', ')} (#{mode})"
191
191
  end
192
- original_mode = text_rendering_mode
192
+ original_mode = @text_rendering_mode || :fill
193
193
  if original_mode == mode
194
194
  yield
195
195
  else
@@ -71,6 +71,9 @@ module Prawn
71
71
  def apply_font_settings_and_add_fragment_to_line(fragment)
72
72
  result = nil
73
73
  @arranger.apply_font_settings do
74
+ # if font has changed from Unicode to non-Unicode, or vice versa, the characters used for soft hyphens
75
+ # and zero-width spaces will be different
76
+ set_soft_hyphen_and_zero_width_space
74
77
  result = add_fragment_to_line(fragment)
75
78
  end
76
79
  result
@@ -141,11 +144,11 @@ module Prawn
141
144
  end
142
145
 
143
146
  def soft_hyphen
144
- @document.font.normalize_encoding(Prawn::Text::SHY)
147
+ @soft_hyphen
145
148
  end
146
149
 
147
150
  def zero_width_space
148
- @document.font.unicode? ? Prawn::Text::ZWSP : ""
151
+ @zero_width_space
149
152
  end
150
153
 
151
154
  def line_empty?
@@ -168,6 +171,14 @@ module Prawn
168
171
  @line_full = false
169
172
  end
170
173
 
174
+ def set_soft_hyphen_and_zero_width_space
175
+ # this is done once per fragment, after the font settings for the fragment are applied --
176
+ # it could actually be skipped if the font hasn't changed
177
+ font = @document.font
178
+ @soft_hyphen = font.normalize_encoding(Prawn::Text::SHY)
179
+ @zero_width_space = font.unicode? ? Prawn::Text::ZWSP : ""
180
+ end
181
+
171
182
  def fragment_finished(fragment)
172
183
  if fragment == "\n"
173
184
  @newline_encountered = true
@@ -231,20 +242,24 @@ module Prawn
231
242
  end
232
243
 
233
244
  def wrap_by_char(segment)
234
- if @document.font.unicode?
235
- segment.unpack("U*").each do |char_int|
236
- break unless append_char([char_int].pack("U"))
245
+ # this conditional is only necessary for Ruby 1.8 compatibility
246
+ # String#unicode_characters is a helper which iterates over UTF-8 characters
247
+ # under Ruby 1.9, it is implemented simply by aliasing #each_char
248
+ font = @document.font
249
+ if font.unicode?
250
+ segment.unicode_characters do |char|
251
+ break unless append_char(char,font)
237
252
  end
238
253
  else
239
254
  segment.each_char do |char|
240
- break unless append_char(char)
255
+ break unless append_char(char,font)
241
256
  end
242
257
  end
243
258
  end
244
259
 
245
- def append_char(char)
260
+ def append_char(char,font)
246
261
  # kerning doesn't make sense in the context of a single character
247
- char_width = @document.width_of(char)
262
+ char_width = font.compute_width_of(char)
248
263
 
249
264
  if @accumulated_width + char_width <= @width
250
265
  @accumulated_width += char_width
@@ -61,6 +61,7 @@ module Prawn
61
61
  include Prawn::Graphics
62
62
  include Prawn::Images
63
63
  include Prawn::Stamp
64
+ include Prawn::SoftMask
64
65
 
65
66
  # Any module added to this array will be included into instances of
66
67
  # Prawn::Document at the per-object level. These will also be inherited by
@@ -136,6 +137,7 @@ module Prawn
136
137
  # <tt>:compress</tt>:: Compresses content streams before rendering them [false]
137
138
  # <tt>:optimize_objects</tt>:: Reduce number of PDF objects in output, at expense of render time [false]
138
139
  # <tt>:background</tt>:: An image path to be used as background on all pages [nil]
140
+ # <tt>:background_scale</tt>:: Backgound image scale [1] [nil]
139
141
  # <tt>:info</tt>:: Generic hash allowing for custom metadata properties [nil]
140
142
  # <tt>:template</tt>:: The path to an existing PDF file to use as a template [nil]
141
143
  #
@@ -166,7 +168,7 @@ module Prawn
166
168
  # pdf = Prawn::Document.new(:page_size => [200, 300])
167
169
  #
168
170
  # # New document, with background
169
- # pdf = Prawn::Document.new(:background => "#{Prawn::BASEDIR}/data/images/pigs.jpg")
171
+ # pdf = Prawn::Document.new(:background => "#{Prawn::DATADIR}/images/pigs.jpg")
170
172
  #
171
173
  def initialize(options={},&block)
172
174
  options = options.dup
@@ -185,6 +187,7 @@ module Prawn
185
187
  min_version(state.store.min_version) if state.store.min_version
186
188
 
187
189
  @background = options[:background]
190
+ @background_scale = options[:background_scale] || 1
188
191
  @font_size = 12
189
192
 
190
193
  @bounding_box = nil
@@ -241,6 +244,9 @@ module Prawn
241
244
  #
242
245
  # pdf.start_new_page(:template => multipage_template.pdf, :template_page => 2)
243
246
  #
247
+ # Note: templates get indexed by either the object_id of the filename or stream
248
+ # entered so that if you reuse the same template multiple times be sure to use the
249
+ # same instance for more efficient use of resources and smaller rendered pdfs.
244
250
  def start_new_page(options = {})
245
251
  if last_page = state.page
246
252
  last_page_size = last_page.size
@@ -277,7 +283,7 @@ module Prawn
277
283
  state.insert_page(state.page, @page_number)
278
284
  @page_number += 1
279
285
 
280
- canvas { image(@background, :at => bounds.top_left) } if @background
286
+ canvas { image(@background, :scale => @background_scale, :at => bounds.top_left) } if @background
281
287
  @y = @bounding_box.absolute_top
282
288
 
283
289
  float do
@@ -539,15 +545,15 @@ module Prawn
539
545
  # through existing pages after they are created.
540
546
  #
541
547
  # Parameters are:
542
- #
543
- # <tt>string</tt>:: Template string for page number wording.
548
+ #
549
+ # <tt>string</tt>:: Template string for page number wording.
544
550
  # Should include '<page>' and, optionally, '<total>'.
545
551
  # <tt>options</tt>:: A hash for page numbering and text box options.
546
- # <tt>:page_filter</tt>:: A filter to specify which pages to place page numbers on.
552
+ # <tt>:page_filter</tt>:: A filter to specify which pages to place page numbers on.
547
553
  # Refer to the method 'page_match?'
548
554
  # <tt>:start_count_at</tt>:: The starting count to increment pages from.
549
555
  # <tt>:total_pages</tt>:: If provided, will replace <total> with the value given.
550
- # Useful to override the total number of pages when using
556
+ # Useful to override the total number of pages when using
551
557
  # the start_count_at option.
552
558
  # <tt>:color</tt>:: Text fill color.
553
559
  #
@@ -558,7 +564,7 @@ module Prawn
558
564
  # five.
559
565
  #
560
566
  # Prawn::Document.generate("page_with_numbering.pdf") do
561
- # number_pages "<page> in a total of <total>",
567
+ # number_pages "<page> in a total of <total>",
562
568
  # {:start_count_at => 5,
563
569
  # :page_filter => lambda{ |pg| pg != 1 },
564
570
  # :at => [bounds.right - 50, 0],
@@ -577,8 +583,8 @@ module Prawn
577
583
  total_pages = opts.delete(:total_pages)
578
584
  txtcolor = opts.delete(:color)
579
585
  # An explicit height so that we can draw page numbers in the margins
580
- opts[:height] = 50
581
-
586
+ opts[:height] = 50 unless opts.has_key?(:height)
587
+
582
588
  start_count = false
583
589
  pseudopage = 0
584
590
  (1..page_count).each do |p|
@@ -589,7 +595,7 @@ module Prawn
589
595
  else
590
596
  start_count_at.to_i
591
597
  end
592
- end
598
+ end
593
599
  if page_match?(page_filter, p)
594
600
  go_to_page(p)
595
601
  # have to use fill_color here otherwise text reverts back to default fill color
@@ -598,13 +604,13 @@ module Prawn
598
604
  str = string.gsub("<page>","#{pseudopage}").gsub("<total>","#{total_pages}")
599
605
  text_box str, opts
600
606
  start_count = true # increment page count as soon as first match found
601
- end
607
+ end
602
608
  pseudopage += 1 if start_count
603
609
  end
604
610
  end
605
611
 
606
612
  # Provides a way to execute a block of code repeatedly based on a
607
- # page_filter.
613
+ # page_filter.
608
614
  #
609
615
  # Available page filters are:
610
616
  # :all repeats on every page
@@ -612,7 +618,7 @@ module Prawn
612
618
  # :even repeats on even pages
613
619
  # some_array repeats on every page listed in the array
614
620
  # some_range repeats on every page included in the range
615
- # some_lambda yields page number and repeats for true return values
621
+ # some_lambda yields page number and repeats for true return values
616
622
  def page_match?(page_filter, page_number)
617
623
  case page_filter
618
624
  when :all
@@ -626,7 +632,7 @@ module Prawn
626
632
  when Proc
627
633
  page_filter.call(page_number)
628
634
  end
629
- end
635
+ end
630
636
 
631
637
 
632
638
  # Returns true if content streams will be compressed before rendering,
@@ -640,7 +646,7 @@ module Prawn
640
646
 
641
647
  def merge_template_options(page_options, options)
642
648
  object_id = state.store.import_page(options[:template], options[:template_page] || 1)
643
- page_options.merge!(:object_id => object_id )
649
+ page_options.merge!(:object_id => object_id, :page_template => true)
644
650
  end
645
651
 
646
652
  # setting override_settings to true ensures that a new graphic state does not end up using
@@ -154,10 +154,10 @@ module Prawn
154
154
  # Of course, if you use canvas, you will be responsible for ensuring that
155
155
  # you remain within the printable area of your document.
156
156
  #
157
- def bounding_box(*args, &block)
157
+ def bounding_box(pt, *args, &block)
158
158
  init_bounding_box(block) do |parent_box|
159
- map_to_absolute!(args[0])
160
- @bounding_box = BoundingBox.new(self, parent_box, *args)
159
+ pt = map_to_absolute(pt)
160
+ @bounding_box = BoundingBox.new(self, parent_box, pt, *args)
161
161
  end
162
162
  end
163
163
 
@@ -53,17 +53,23 @@ module Prawn
53
53
  @current_column = 0
54
54
  end
55
55
 
56
- # The column width, not the width of the whole box. Used to calculate
57
- # how long a line of text can be.
56
+ # The column width, not the width of the whole box,
57
+ # before left and/or right padding
58
+ def bare_column_width
59
+ (@width - @spacer * (@columns - 1)) / @columns
60
+ end
61
+
62
+ # The column width after padding.
63
+ # Used to calculate how long a line of text can be.
58
64
  #
59
65
  def width
60
- super / @columns - @spacer - @total_left_padding - @total_right_padding
66
+ bare_column_width - (@total_left_padding + @total_right_padding)
61
67
  end
62
68
 
63
69
  # Column width including the spacer.
64
70
  #
65
71
  def width_of_column
66
- width + @spacer
72
+ bare_column_width + @spacer
67
73
  end
68
74
 
69
75
  # x coordinate of the left edge of the current column
@@ -72,6 +78,12 @@ module Prawn
72
78
  absolute_left + (width_of_column * @current_column)
73
79
  end
74
80
 
81
+ # Relative position of the left edge of the current column
82
+ #
83
+ def left
84
+ width_of_column * @current_column
85
+ end
86
+
75
87
  # x co-orordinate of the right edge of the current column
76
88
  #
77
89
  def right_side
@@ -79,6 +91,12 @@ module Prawn
79
91
  absolute_right - (width_of_column * columns_from_right)
80
92
  end
81
93
 
94
+ # Relative position of the right edge of the current column.
95
+ #
96
+ def right
97
+ left + width
98
+ end
99
+
82
100
  # Moves to the next column or starts a new page if currently positioned at
83
101
  # the rightmost column.
84
102
  def move_past_bottom
@@ -53,7 +53,7 @@ module Prawn
53
53
  :current_page => state.page.dictionary.deep_copy(share=[:Parent]),
54
54
  :bounds => bounds.deep_copy,
55
55
  :page_number => page_number,
56
- :page_kids => state.store.pages.data[:Kids].map{|kid| kid.identifier},
56
+ :page_kids => state.store.pages.data[:Kids].compact.map{|kid| kid.identifier},
57
57
  :dests => names? && names.data[:Dests].deep_copy}
58
58
  end
59
59
 
@@ -84,7 +84,7 @@ module Prawn
84
84
  ]
85
85
 
86
86
  def initialize
87
- @mapping_file = "#{Prawn::BASEDIR}/data/encodings/win_ansi.txt"
87
+ @mapping_file = "#{Prawn::DATADIR}/encodings/win_ansi.txt"
88
88
  load_mapping if self.class.mapping.empty?
89
89
  end
90
90
 
@@ -18,6 +18,10 @@ module Prawn
18
18
  # other than :portrait or :landscape
19
19
  #
20
20
  InvalidPageLayout = Class.new(StandardError)
21
+
22
+ # Raised when a table is spanned in an impossible way.
23
+ #
24
+ InvalidTableSpan = Class.new(StandardError)
21
25
 
22
26
  # This error is raised when a method requiring a current page is called
23
27
  # without being on a page.
@@ -257,7 +257,7 @@ module Prawn
257
257
  # will be passed through to Font::AFM.new()
258
258
  #
259
259
  def self.load(document,name,options={})
260
- case name
260
+ case name.to_s
261
261
  when /\.ttf$/i then TTF.new(document, name, options)
262
262
  when /\.dfont$/i then DFont.new(document, name, options)
263
263
  when /\.afm$/i then AFM.new(document, name, options)
@@ -7,6 +7,7 @@
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
9
  require 'prawn/encoding'
10
+ require 'afm'
10
11
 
11
12
  module Prawn
12
13
  class Font
@@ -27,8 +28,8 @@ module Prawn
27
28
  @metrics_path ||= [
28
29
  ".", "/usr/lib/afm",
29
30
  "/usr/local/lib/afm",
30
- "/usr/openwin/lib/fonts/afm/",
31
- Prawn::BASEDIR+'/data/fonts/']
31
+ "/usr/openwin/lib/fonts/afm",
32
+ Prawn::DATADIR+'/fonts']
32
33
  end
33
34
  end
34
35
 
@@ -41,26 +42,28 @@ module Prawn
41
42
 
42
43
  super
43
44
 
44
- @attributes = {}
45
- @glyph_widths = {}
46
- @bounding_boxes = {}
47
- @kern_pairs = {}
45
+ @@winansi ||= Prawn::Encoding::WinAnsi.new # parse data/encodings/win_ansi.txt once only
46
+ @@font_data ||= SynchronizedCache.new # parse each ATM font file once only
48
47
 
49
48
  file_name = @name.dup
50
49
  file_name << ".afm" unless file_name =~ /\.afm$/
51
50
  file_name = file_name[0] == ?/ ? file_name : find_font(file_name)
52
51
 
53
- parse_afm(file_name)
52
+ font_data = @@font_data[file_name] ||= ::AFM::Font.new(file_name)
53
+ @glyph_table = build_glyph_table(font_data)
54
+ @kern_pairs = font_data.kern_pairs
55
+ @kern_pair_table = build_kern_pair_table(@kern_pairs)
56
+ @attributes = font_data.metadata
54
57
 
55
- @ascender = @attributes["ascender"].to_i
56
- @descender = @attributes["descender"].to_i
58
+ @ascender = @attributes["Ascender"].to_i
59
+ @descender = @attributes["Descender"].to_i
57
60
  @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
58
61
  end
59
62
 
60
63
  # The font bbox, as an array of integers
61
64
  #
62
65
  def bbox
63
- @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
66
+ @bbox ||= @attributes['FontBBox'].split(/\s+/).map { |e| Integer(e) }
64
67
  end
65
68
 
66
69
  # NOTE: String *must* be encoded as WinAnsi
@@ -86,8 +89,8 @@ module Prawn
86
89
  # string. Changes the encoding in-place, so the argument itself
87
90
  # is replaced with a string in WinAnsi encoding.
88
91
  #
89
- def normalize_encoding(text)
90
- enc = Prawn::Encoding::WinAnsi.new
92
+ def normalize_encoding(text)
93
+ enc = @@winansi
91
94
  text.unpack("U*").collect { |i| enc[i] }.pack("C*")
92
95
  rescue ArgumentError
93
96
  raise Prawn::Errors::IncompatibleStringEncoding,
@@ -138,7 +141,7 @@ module Prawn
138
141
  end
139
142
 
140
143
  def symbolic?
141
- attributes["characterset"] == "Special"
144
+ attributes["CharacterSet"] == "Special"
142
145
  end
143
146
 
144
147
  def find_font(file)
@@ -149,46 +152,6 @@ module Prawn
149
152
  self.class.metrics_path.join("\n")
150
153
  end
151
154
 
152
- def parse_afm(file_name)
153
- section = []
154
-
155
- File.foreach(file_name) do |line|
156
- case line
157
- when /^Start(\w+)/
158
- section.push $1
159
- next
160
- when /^End(\w+)/
161
- section.pop
162
- next
163
- end
164
-
165
- case section
166
- when ["FontMetrics", "CharMetrics"]
167
- next unless line =~ /^CH?\s/
168
-
169
- name = line[/\bN\s+(\.?\w+)\s*;/, 1]
170
- @glyph_widths[name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
171
- @bounding_boxes[name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
172
- when ["FontMetrics", "KernData", "KernPairs"]
173
- next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
174
- @kern_pairs[[$1, $2]] = $3.to_i
175
- when ["FontMetrics", "KernData", "TrackKern"],
176
- ["FontMetrics", "Composites"]
177
- next
178
- else
179
- parse_generic_afm_attribute(line)
180
- end
181
- end
182
- end
183
-
184
- def parse_generic_afm_attribute(line)
185
- line =~ /(^\w+)\s+(.*)/
186
- key, value = $1.to_s.downcase, $2
187
-
188
- @attributes[key] = @attributes[key] ?
189
- Array(@attributes[key]) << value : value
190
- end
191
-
192
155
  # converts a string into an array with spacing offsets
193
156
  # bewteen characters that need to be kerned
194
157
  #
@@ -198,10 +161,8 @@ module Prawn
198
161
  kerned = [[]]
199
162
  last_byte = nil
200
163
 
201
- kern_pairs = latin_kern_pairs_table
202
-
203
- string.unpack("C*").each do |byte|
204
- if k = last_byte && kern_pairs[[last_byte, byte]]
164
+ string.bytes do |byte|
165
+ if k = last_byte && @kern_pair_table[[last_byte, byte]]
205
166
  kerned << -k << [byte]
206
167
  else
207
168
  kerned.last << byte
@@ -214,30 +175,27 @@ module Prawn
214
175
  e.respond_to?(:force_encoding) ? e.force_encoding("Windows-1252") : e
215
176
  }
216
177
  end
217
-
218
- def latin_kern_pairs_table
219
- return @kern_pairs_table if defined?(@kern_pairs_table)
220
-
178
+
179
+ def build_kern_pair_table(kern_pairs)
221
180
  character_hash = Hash[Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a)]
222
- @kern_pairs_table = @kern_pairs.inject({}) do |h,p|
223
- h[p[0].map { |n| character_hash[n] }] = p[1]
181
+ kern_pairs.inject({}) do |h,p|
182
+ h[
183
+ [character_hash[p[0]], character_hash[p[1]]]
184
+ ] = p[2]
224
185
  h
225
186
  end
226
187
  end
227
188
 
228
- def latin_glyphs_table
229
- @glyphs_table ||= (0..255).map do |i|
230
- @glyph_widths[Encoding::WinAnsi::CHARACTERS[i]].to_i
189
+ def build_glyph_table(font_data)
190
+ (0..255).map do |char|
191
+ metrics = font_data.metrics_for(char)
192
+ metrics ? metrics[:wx] : 0
231
193
  end
232
194
  end
233
195
 
234
- private
235
-
236
196
  def unscaled_width_of(string)
237
- glyph_table = latin_glyphs_table
238
-
239
- string.unpack("C*").inject(0) do |s,r|
240
- s + glyph_table[r]
197
+ string.bytes.inject(0) do |s,r|
198
+ s + @glyph_table[r]
241
199
  end
242
200
  end
243
201
  end