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
@@ -0,0 +1,61 @@
1
+ module Prawn
2
+ class Table
3
+ # @private
4
+ class ColumnWidthCalculator
5
+ def initialize(cells)
6
+ @cells = cells
7
+
8
+ @widths_by_column = Hash.new(0)
9
+ @rows_with_a_span_dummy = Hash.new(false)
10
+ end
11
+
12
+ def natural_widths
13
+ @cells.each do |cell|
14
+ @rows_with_a_span_dummy[cell.row] = true if cell.is_a?(Cell::SpanDummy)
15
+ end
16
+
17
+ #calculate natural column width for all rows that do not include a span dummy
18
+ @cells.each do |cell|
19
+ unless @rows_with_a_span_dummy[cell.row]
20
+ @widths_by_column[cell.column] =
21
+ [@widths_by_column[cell.column], cell.width.to_f].max
22
+ end
23
+ end
24
+
25
+ #integrate natural column widths for all rows that do include a span dummy
26
+ @cells.each do |cell|
27
+ next unless @rows_with_a_span_dummy[cell.row]
28
+ #the width of a SpanDummy cell will be calculated by the "mother" cell
29
+ next if cell.is_a?(Cell::SpanDummy)
30
+
31
+ if cell.colspan == 1
32
+ @widths_by_column[cell.column] =
33
+ [@widths_by_column[cell.column], cell.width.to_f].max
34
+ else
35
+ #calculate the current with of all cells that will be spanned by the current cell
36
+ current_width_of_spanned_cells =
37
+ @widths_by_column.to_a[cell.column..(cell.column + cell.colspan - 1)]
38
+ .collect{|key, value| value}.inject(0, :+)
39
+
40
+ #update the Hash only if the new with is at least equal to the old one
41
+ #due to arithmetic errors we need to ignore a small difference in the new and the old sum
42
+ #the same had to be done in the column_widht_calculator#natural_width
43
+ update_hash = ((cell.width.to_f - current_width_of_spanned_cells) >
44
+ Prawn::FLOAT_PRECISION)
45
+
46
+ if update_hash
47
+ # Split the width of colspanned cells evenly by columns
48
+ width_per_column = cell.width.to_f / cell.colspan
49
+ # Update the Hash
50
+ cell.colspan.times do |i|
51
+ @widths_by_column[cell.column + i] = width_per_column
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ @widths_by_column.sort_by { |col, _| col }.map { |_, w| w }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -5,15 +5,18 @@
5
5
  # Copyright May 2008, Gregory Brown. All Rights Reserved.
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
- require "prawn/core/text"
9
- require "prawn/text/formatted"
10
- require "prawn/text/box"
8
+
11
9
  require "zlib"
12
10
 
11
+ require "pdf/core/text"
12
+
13
+ require_relative "text/formatted"
14
+ require_relative "text/box"
15
+
13
16
  module Prawn
14
17
  module Text
15
18
 
16
- include Prawn::Core::Text
19
+ include PDF::Core::Text
17
20
  include Prawn::Text::Formatted
18
21
 
19
22
  # No-Break Space
@@ -23,11 +26,13 @@ module Prawn
23
26
  # Soft Hyphen (invisible, except when causing a line break)
24
27
  Prawn::Text::SHY = "­"
25
28
 
29
+ # @group Stable API
30
+
26
31
  # If you want text to flow onto a new page or between columns, this is the
27
32
  # method to use. If, instead, if you want to place bounded text outside of
28
33
  # the flow of a document (for captions, labels, charts, etc.), use Text::Box
29
34
  # or its convenience method text_box.
30
- #
35
+ #
31
36
  # Draws text on the page. Prawn attempts to wrap the text to fit within your
32
37
  # current bounding box (or margin_box if no bounding box is being used).
33
38
  # Text will flow onto the next page when it reaches the bottom of the
@@ -49,28 +54,29 @@ module Prawn
49
54
  # entire document, set default_kerning = false for that document
50
55
  #
51
56
  # === Text Positioning Details
52
- #
57
+ #
53
58
  # The text is positioned at font.ascender below the baseline,
54
59
  # making it easy to use this method within bounding boxes and spans.
55
60
  #
56
61
  # == Encoding
57
62
  #
58
63
  # Note that strings passed to this function should be encoded as UTF-8.
59
- # If you get unexpected characters appearing in your rendered document,
64
+ # If you get unexpected characters appearing in your rendered document,
60
65
  # check this.
61
66
  #
62
67
  # If the current font is a built-in one, although the string must be
63
68
  # encoded as UTF-8, only characters that are available in WinAnsi
64
69
  # are allowed.
65
70
  #
66
- # If an empty box is rendered to your PDF instead of the character you
71
+ # If an empty box is rendered to your PDF instead of the character you
67
72
  # wanted it usually means the current font doesn't include that character.
68
73
  #
69
74
  # == Options (default values marked in [])
70
75
  #
71
76
  # <tt>:inline_format</tt>::
72
77
  # <tt>boolean</tt>. If true, then the string parameter is interpreted
73
- # as a HTML-esque string that recognizes the following tags:
78
+ # as a HTML-esque string that recognizes the following tags
79
+ # (assuming the default text formatter is used):
74
80
  # <tt>\<b></b></tt>:: bold
75
81
  # <tt>\<i></i></tt>:: italic
76
82
  # <tt>\<u></u></tt>:: underline
@@ -94,11 +100,6 @@ module Prawn
94
100
  # <tt>\<link></link></tt>::
95
101
  # with the following attributes
96
102
  # <tt>href="http://example.com"</tt>:: an external link
97
- # <tt>anchor="ToC"</tt>::
98
- # where the value of the anchor attribute is the name of a
99
- # destination that has already been or will be registered
100
- # using Prawn::Core::Destinations#add_dest. A clickable link
101
- # will be created to that destination.
102
103
  # Note that you must explicitly underline and color using the
103
104
  # appropriate tags if you which to draw attention to the link
104
105
  #
@@ -143,7 +144,7 @@ module Prawn
143
144
  # text should render with the fill color, stroke color or
144
145
  # both. See the comments to text_rendering_mode() to see
145
146
  # a list of valid options. [0]
146
- #
147
+ #
147
148
  # == Exceptions
148
149
  #
149
150
  # Raises <tt>ArgumentError</tt> if <tt>:at</tt> option included
@@ -156,9 +157,10 @@ module Prawn
156
157
  # we modify the options. don't change the user's hash
157
158
  options = options.dup
158
159
 
159
- if options[:inline_format]
160
+ if p = options[:inline_format]
161
+ p = [] unless p.is_a?(Array)
160
162
  options.delete(:inline_format)
161
- array = Text::Formatted::Parser.to_array(string)
163
+ array = self.text_formatter.format(string, *p)
162
164
  else
163
165
  array = [{ :text => string }]
164
166
  end
@@ -166,7 +168,6 @@ module Prawn
166
168
  formatted_text(array, options)
167
169
  end
168
170
 
169
-
170
171
  # Draws formatted text to the page.
171
172
  # Formatted text is comprised of an array of hashes, where each hash defines
172
173
  # text and format information. See Text::Formatted#formatted_text_box for
@@ -191,13 +192,13 @@ module Prawn
191
192
  options = inspect_options_for_text(options.dup)
192
193
 
193
194
  if color = options.delete(:color)
194
- array = array.map do |fragment|
195
+ array = array.map do |fragment|
195
196
  fragment[:color] ? fragment : fragment.merge(:color => color)
196
197
  end
197
198
  end
198
199
 
199
200
  if @indent_paragraphs
200
- Text::Formatted::Parser.array_paragraphs(array).each do |paragraph|
201
+ self.text_formatter.array_paragraphs(array).each do |paragraph|
201
202
  options[:skip_encoding] = false
202
203
  remaining_text = draw_indented_formatted_line(paragraph, options)
203
204
  options[:skip_encoding] = true
@@ -224,7 +225,7 @@ module Prawn
224
225
 
225
226
  # Draws text on the page, beginning at the point specified by the :at option
226
227
  # the string is assumed to be pre-formatted to properly fit the page.
227
- #
228
+ #
228
229
  # pdf.draw_text "Hello World", :at => [100,100]
229
230
  # pdf.draw_text "Goodbye World", :at => [50,50], :size => 16
230
231
  #
@@ -246,14 +247,14 @@ module Prawn
246
247
  # == Encoding
247
248
  #
248
249
  # Note that strings passed to this function should be encoded as UTF-8.
249
- # If you get unexpected characters appearing in your rendered document,
250
+ # If you get unexpected characters appearing in your rendered document,
250
251
  # check this.
251
252
  #
252
253
  # If the current font is a built-in one, although the string must be
253
254
  # encoded as UTF-8, only characters that are available in WinAnsi
254
255
  # are allowed.
255
256
  #
256
- # If an empty box is rendered to your PDF instead of the character you
257
+ # If an empty box is rendered to your PDF instead of the character you
257
258
  # wanted it usually means the current font doesn't include that character.
258
259
  #
259
260
  # == Options (default values marked in [])
@@ -282,7 +283,7 @@ module Prawn
282
283
  text = text.to_s.dup
283
284
  save_font do
284
285
  process_text_options(options)
285
- font.normalize_encoding!(text) unless @skip_encoding
286
+ font.normalize_encoding!(text)
286
287
  font_size(options[:size]) { draw_text!(text, options) }
287
288
  end
288
289
  end
@@ -326,7 +327,7 @@ module Prawn
326
327
  box = Text::Formatted::Box.new(array,
327
328
  options.merge(:height => 100000000,
328
329
  :document => self))
329
- printed = box.render(:dry_run => true)
330
+ box.render(:dry_run => true)
330
331
 
331
332
  height = box.height
332
333
  height += box.line_gap + box.leading if @final_gap
@@ -382,7 +383,7 @@ module Prawn
382
383
  if options[:kerning].nil? then
383
384
  options[:kerning] = default_kerning?
384
385
  end
385
- valid_options = Prawn::Core::Text::VALID_OPTIONS + [:at, :rotate]
386
+ valid_options = PDF::Core::Text::VALID_OPTIONS + [:at, :rotate]
386
387
  Prawn.verify_options(valid_options, options)
387
388
  options
388
389
  end
@@ -7,8 +7,11 @@
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
  #
9
9
 
10
+ require_relative "formatted/box"
11
+
10
12
  module Prawn
11
13
  module Text
14
+ # @group Stable API
12
15
 
13
16
  # Draws the requested text into a box. When the text overflows
14
17
  # the rectangle, you shrink to fit, or truncate the text. Text
@@ -17,14 +20,14 @@ module Prawn
17
20
  # == Encoding
18
21
  #
19
22
  # Note that strings passed to this function should be encoded as UTF-8.
20
- # If you get unexpected characters appearing in your rendered document,
23
+ # If you get unexpected characters appearing in your rendered document,
21
24
  # check this.
22
25
  #
23
26
  # If the current font is a built-in one, although the string must be
24
27
  # encoded as UTF-8, only characters that are available in WinAnsi
25
28
  # are allowed.
26
29
  #
27
- # If an empty box is rendered to your PDF instead of the character you
30
+ # If an empty box is rendered to your PDF instead of the character you
28
31
  # wanted it usually means the current font doesn't include that character.
29
32
  #
30
33
  # == Options (default values marked in [])
@@ -67,7 +70,7 @@ module Prawn
67
70
  # <tt>:valign</tt>::
68
71
  # <tt>:top</tt>, <tt>:center</tt>, or <tt>:bottom</tt>. Vertical
69
72
  # alignment within the bounding box [:top]
70
- #
73
+ #
71
74
  # <tt>:rotate</tt>::
72
75
  # <tt>number</tt>. The angle to rotate the text
73
76
  # <tt>:rotate_around</tt>::
@@ -108,8 +111,9 @@ module Prawn
108
111
  options = options.dup
109
112
  options[:document] = self
110
113
 
111
- box = if options.delete(:inline_format)
112
- array = Text::Formatted::Parser.to_array(string)
114
+ box = if p = options.delete(:inline_format)
115
+ p = [] unless p.is_a?(Array)
116
+ array = self.text_formatter.format(string, *p)
113
117
  Text::Formatted::Box.new(array, options)
114
118
  else
115
119
  Text::Box.new(string, options)
@@ -118,8 +122,10 @@ module Prawn
118
122
  box.render
119
123
  end
120
124
 
125
+ # @group Experimental API
126
+
121
127
  # Generally, one would use the Prawn::Text#text_box convenience
122
- # method. However, using Text::Box.new in conjunction with
128
+ # method. However, using Text::Box.new in conjunction with
123
129
  # #render(:dry_run=> true) enables one to do look-ahead calculations prior
124
130
  # to placing text on the page, or to determine how much vertical space was
125
131
  # consumed by the printed text
@@ -1,4 +1,5 @@
1
- require "prawn/core/text/formatted/wrap"
2
- require "prawn/text/formatted/box"
3
- require "prawn/text/formatted/parser"
4
- require "prawn/text/formatted/fragment"
1
+ require_relative "formatted/wrap"
2
+
3
+ require_relative "formatted/box"
4
+ require_relative "formatted/parser"
5
+ require_relative "formatted/fragment"
@@ -0,0 +1,290 @@
1
+ # encoding: utf-8
2
+
3
+ # core/text/formatted/arranger.rb : Implements a data structure for 2-stage
4
+ # processing of lines of formatted text
5
+ #
6
+ # Copyright February 2010, Daniel Nelson. All Rights Reserved.
7
+ #
8
+ # This is free software. Please see the LICENSE and COPYING files for details.
9
+
10
+ module Prawn
11
+ module Text
12
+ module Formatted #:nodoc:
13
+
14
+ # @private
15
+
16
+ class Arranger #:nodoc:
17
+ attr_reader :max_line_height
18
+ attr_reader :max_descender
19
+ attr_reader :max_ascender
20
+ attr_accessor :consumed
21
+
22
+ # The following present only for testing purposes
23
+ attr_reader :unconsumed
24
+ attr_reader :fragments
25
+ attr_reader :current_format_state
26
+
27
+ def initialize(document, options={})
28
+ @document = document
29
+ @fragments = []
30
+ @unconsumed = []
31
+ @kerning = options[:kerning]
32
+ end
33
+
34
+ def space_count
35
+ if @unfinalized_line
36
+ raise "Lines must be finalized before calling #space_count"
37
+ end
38
+ @fragments.inject(0) do |sum, fragment|
39
+ sum + fragment.space_count
40
+ end
41
+ end
42
+
43
+ def line_width
44
+ if @unfinalized_line
45
+ raise "Lines must be finalized before calling #line_width"
46
+ end
47
+ @fragments.inject(0) do |sum, fragment|
48
+ sum + fragment.width
49
+ end
50
+ end
51
+
52
+ def line
53
+ if @unfinalized_line
54
+ raise "Lines must be finalized before calling #line"
55
+ end
56
+ @fragments.collect do |fragment|
57
+ fragment.text.dup.force_encoding(::Encoding::UTF_8)
58
+ end.join
59
+ end
60
+
61
+ def finalize_line
62
+ @unfinalized_line = false
63
+ omit_trailing_whitespace_from_line_width
64
+ @fragments = []
65
+ @consumed.each do |hash|
66
+ text = hash[:text]
67
+ format_state = hash.dup
68
+ format_state.delete(:text)
69
+ fragment = Prawn::Text::Formatted::Fragment.new(text,
70
+ format_state,
71
+ @document)
72
+ @fragments << fragment
73
+ set_fragment_measurements(fragment)
74
+ set_line_measurement_maximums(fragment)
75
+ end
76
+ end
77
+
78
+ def format_array=(array)
79
+ initialize_line
80
+ @unconsumed = []
81
+ array.each do |hash|
82
+ hash[:text].scan(/[^\n]+|\n/) do |line|
83
+ @unconsumed << hash.merge(:text => line)
84
+ end
85
+ end
86
+ end
87
+
88
+ def initialize_line
89
+ @unfinalized_line = true
90
+ @max_line_height = 0
91
+ @max_descender = 0
92
+ @max_ascender = 0
93
+
94
+ @consumed = []
95
+ @fragments = []
96
+ end
97
+
98
+ def finished?
99
+ @unconsumed.length == 0
100
+ end
101
+
102
+ def next_string
103
+ unless @unfinalized_line
104
+ raise "Lines must not be finalized when calling #next_string"
105
+ end
106
+ hash = @unconsumed.shift
107
+ if hash.nil?
108
+ nil
109
+ else
110
+ @consumed << hash.dup
111
+ @current_format_state = hash.dup
112
+ @current_format_state.delete(:text)
113
+ hash[:text]
114
+ end
115
+ end
116
+
117
+ def preview_next_string
118
+ hash = @unconsumed.first
119
+ if hash.nil? then nil
120
+ else hash[:text]
121
+ end
122
+ end
123
+
124
+ def apply_color_and_font_settings(fragment, &block)
125
+ if fragment.color
126
+ original_fill_color = @document.fill_color
127
+ original_stroke_color = @document.stroke_color
128
+ @document.fill_color(*fragment.color)
129
+ @document.stroke_color(*fragment.color)
130
+ apply_font_settings(fragment, &block)
131
+ @document.stroke_color = original_stroke_color
132
+ @document.fill_color = original_fill_color
133
+ else
134
+ apply_font_settings(fragment, &block)
135
+ end
136
+ end
137
+
138
+ def apply_font_settings(fragment=nil, &block)
139
+ if fragment.nil?
140
+ font = current_format_state[:font]
141
+ size = current_format_state[:size]
142
+ character_spacing = current_format_state[:character_spacing] ||
143
+ @document.character_spacing
144
+ styles = current_format_state[:styles]
145
+ font_style = font_style(styles)
146
+ else
147
+ font = fragment.font
148
+ size = fragment.size
149
+ character_spacing = fragment.character_spacing
150
+ styles = fragment.styles
151
+ font_style = font_style(styles)
152
+ end
153
+
154
+ @document.character_spacing(character_spacing) do
155
+ if font || font_style != :normal
156
+ raise "Bad font family" unless @document.font.family
157
+ @document.font(font || @document.font.family, :style => font_style) do
158
+ apply_font_size(size, styles, &block)
159
+ end
160
+ else
161
+ apply_font_size(size, styles, &block)
162
+ end
163
+ end
164
+ end
165
+
166
+ def update_last_string(printed, unprinted, normalized_soft_hyphen=nil)
167
+ return if printed.nil?
168
+ if printed.empty?
169
+ @consumed.pop
170
+ else
171
+ @consumed.last[:text] = printed
172
+ if normalized_soft_hyphen
173
+ @consumed.last[:normalized_soft_hyphen] = normalized_soft_hyphen
174
+ end
175
+ end
176
+
177
+ unless unprinted.empty?
178
+ @unconsumed.unshift(@current_format_state.merge(:text => unprinted))
179
+ end
180
+
181
+ load_previous_format_state if printed.empty?
182
+ end
183
+
184
+ def retrieve_fragment
185
+ if @unfinalized_line
186
+ raise "Lines must be finalized before fragments can be retrieved"
187
+ end
188
+ @fragments.shift
189
+ end
190
+
191
+ def repack_unretrieved
192
+ new_unconsumed = []
193
+ while fragment = retrieve_fragment
194
+ fragment.include_trailing_white_space!
195
+ new_unconsumed << fragment.format_state.merge(:text => fragment.text)
196
+ end
197
+ @unconsumed = new_unconsumed.concat(@unconsumed)
198
+ end
199
+
200
+ def font_style(styles)
201
+ if styles.nil?
202
+ :normal
203
+ elsif styles.include?(:bold) && styles.include?(:italic)
204
+ :bold_italic
205
+ elsif styles.include?(:bold)
206
+ :bold
207
+ elsif styles.include?(:italic)
208
+ :italic
209
+ else
210
+ :normal
211
+ end
212
+ end
213
+
214
+ private
215
+
216
+ def load_previous_format_state
217
+ if @consumed.empty?
218
+ @current_format_state = {}
219
+ else
220
+ hash = @consumed.last
221
+ @current_format_state = hash.dup
222
+ @current_format_state.delete(:text)
223
+ end
224
+ end
225
+
226
+ def apply_font_size(size, styles)
227
+ if subscript?(styles) || superscript?(styles)
228
+ relative_size = 0.583
229
+ if size.nil?
230
+ size = @document.font_size * relative_size
231
+ else
232
+ size = size * relative_size
233
+ end
234
+ end
235
+ if size.nil?
236
+ yield
237
+ else
238
+ @document.font_size(size) { yield }
239
+ end
240
+ end
241
+
242
+ def subscript?(styles)
243
+ if styles.nil? then false
244
+ else styles.include?(:subscript)
245
+ end
246
+ end
247
+
248
+ def superscript?(styles)
249
+ if styles.nil? then false
250
+ else styles.include?(:superscript)
251
+ end
252
+ end
253
+
254
+ def omit_trailing_whitespace_from_line_width
255
+ @consumed.reverse_each do |hash|
256
+ if hash[:text] == "\n"
257
+ break
258
+ elsif hash[:text].strip.empty? && @consumed.length > 1
259
+ # this entire fragment is trailing white space
260
+ hash[:exclude_trailing_white_space] = true
261
+ else
262
+ # this fragment contains the first non-white space we have
263
+ # encountered since the end of the line
264
+ hash[:exclude_trailing_white_space] = true
265
+ break
266
+ end
267
+ end
268
+ end
269
+
270
+ def set_fragment_measurements(fragment)
271
+ apply_font_settings(fragment) do
272
+ fragment.width = @document.width_of(fragment.text,
273
+ :kerning => @kerning)
274
+ fragment.line_height = @document.font.height
275
+ fragment.descender = @document.font.descender
276
+ fragment.ascender = @document.font.ascender
277
+ end
278
+ end
279
+
280
+ def set_line_measurement_maximums(fragment)
281
+ @max_line_height = [defined?(@max_line_height) && @max_line_height, fragment.line_height].compact.max
282
+ @max_descender = [defined?(@max_descender) && @max_descender, fragment.descender].compact.max
283
+ @max_ascender = [defined?(@max_ascender) && @max_ascender, fragment.ascender].compact.max
284
+ end
285
+
286
+ end
287
+
288
+ end
289
+ end
290
+ end