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
@@ -10,7 +10,8 @@
10
10
  module Prawn
11
11
  module Text
12
12
  module Formatted
13
-
13
+ # @group Stable API
14
+
14
15
  # Draws the requested formatted text into a box. When the text overflows
15
16
  # the rectangle shrink to fit or truncate the text. Text boxes are
16
17
  # independent of the document y position.
@@ -45,10 +46,16 @@ module Prawn
45
46
  # the appropriate tags if you which to draw attention to the link
46
47
  # <tt>:anchor</tt>::
47
48
  # a destination that has already been or will be registered using
48
- # Prawn::Core::Destinations#add_dest. A clickable link will be
49
+ # PDF::Core::Destinations#add_dest. A clickable link will be
49
50
  # created to that destination. Note that you must explicitly underline
50
51
  # and color using the appropriate tags if you which to draw attention
51
52
  # to the link
53
+ # <tt>:local</tt>::
54
+ # a file or application to be opened locally. A clickable link will be
55
+ # created to the provided local file or application. If the file is
56
+ # another PDF, it will be opened in a new window. Note that you must
57
+ # explicitly underline and color using the appropriate tags if you which
58
+ # to draw attention to the link
52
59
  # <tt>:draw_text_callback</tt>:
53
60
  # if provided, this Proc will be called instead of #draw_text! once
54
61
  # per fragment for every low-level addition of text to the page.
@@ -92,21 +99,9 @@ module Prawn
92
99
  # vertical space was consumed by the printed text
93
100
  #
94
101
  class Box
95
- include Prawn::Core::Text::Formatted::Wrap
102
+ include Prawn::Text::Formatted::Wrap
96
103
 
97
- def valid_options
98
- Prawn::Core::Text::VALID_OPTIONS + [:at, :height, :width,
99
- :align, :valign,
100
- :rotate, :rotate_around,
101
- :overflow, :min_font_size,
102
- :leading, :character_spacing,
103
- :mode, :single_line,
104
- :skip_encoding,
105
- :document,
106
- :direction,
107
- :fallback_fonts,
108
- :draw_text_callback]
109
- end
104
+ # @group Experimental API
110
105
 
111
106
  # The text that was successfully printed (or, if <tt>dry_run</tt> was
112
107
  # used, the text that would have been successfully printed)
@@ -139,42 +134,6 @@ module Prawn
139
134
  line_height - (ascender + descender)
140
135
  end
141
136
 
142
- #
143
- # Example (see Prawn::Text::Core::Formatted::Wrap for what is required
144
- # of the wrap method if you want to override the default wrapping
145
- # algorithm):
146
- #
147
- #
148
- # module MyWrap
149
- #
150
- # def wrap(array)
151
- # initialize_wrap([{ :text => 'all your base are belong to us' }])
152
- # @line_wrap.wrap_line(:document => @document,
153
- # :kerning => @kerning,
154
- # :width => 10000,
155
- # :arranger => @arranger)
156
- # fragment = @arranger.retrieve_fragment
157
- # format_and_draw_fragment(fragment, 0, @line_wrap.width, 0)
158
- # []
159
- # end
160
- #
161
- # end
162
- #
163
- # Prawn::Text::Formatted::Box.extensions << MyWrap
164
- #
165
- # box = Prawn::Text::Formatted::Box.new('hello world')
166
- # box.render('why can't I print anything other than' +
167
- # '"all your base are belong to us"?')
168
- #
169
- #
170
- def self.extensions
171
- @extensions ||= []
172
- end
173
-
174
- def self.inherited(base) #:nodoc:
175
- extensions.each { |e| base.extensions << e }
176
- end
177
-
178
137
  # See Prawn::Text#text_box for valid options
179
138
  #
180
139
  def initialize(formatted_text, options={})
@@ -211,6 +170,11 @@ module Prawn
211
170
  @skip_encoding = options[:skip_encoding] || @document.skip_encoding
212
171
  @draw_text_callback = options[:draw_text_callback]
213
172
 
173
+ # if the text rendering mode is :unknown, force it back to :fill
174
+ if @mode == :unknown
175
+ @mode = :fill
176
+ end
177
+
214
178
  if @overflow == :expand
215
179
  # if set to expand, then we simply set the bottom
216
180
  # as the bottom of the document bounds, since that
@@ -278,7 +242,7 @@ module Prawn
278
242
  end
279
243
 
280
244
  # The height actually used during the previous <tt>render</tt>
281
- #
245
+ #
282
246
  def height
283
247
  return 0 if @baseline_y.nil? || @descender.nil?
284
248
  (@baseline_y - @descender).abs
@@ -328,6 +292,58 @@ module Prawn
328
292
  end
329
293
  end
330
294
 
295
+ # @group Extension API
296
+
297
+ # Example (see Prawn::Text::Core::Formatted::Wrap for what is required
298
+ # of the wrap method if you want to override the default wrapping
299
+ # algorithm):
300
+ #
301
+ #
302
+ # module MyWrap
303
+ #
304
+ # def wrap(array)
305
+ # initialize_wrap([{ :text => 'all your base are belong to us' }])
306
+ # @line_wrap.wrap_line(:document => @document,
307
+ # :kerning => @kerning,
308
+ # :width => 10000,
309
+ # :arranger => @arranger)
310
+ # fragment = @arranger.retrieve_fragment
311
+ # format_and_draw_fragment(fragment, 0, @line_wrap.width, 0)
312
+ # []
313
+ # end
314
+ #
315
+ # end
316
+ #
317
+ # Prawn::Text::Formatted::Box.extensions << MyWrap
318
+ #
319
+ # box = Prawn::Text::Formatted::Box.new('hello world')
320
+ # box.render('why can't I print anything other than' +
321
+ # '"all your base are belong to us"?')
322
+ #
323
+ #
324
+ def self.extensions
325
+ @extensions ||= []
326
+ end
327
+
328
+ # @private
329
+ def self.inherited(base)
330
+ extensions.each { |e| base.extensions << e }
331
+ end
332
+
333
+ def valid_options
334
+ PDF::Core::Text::VALID_OPTIONS + [:at, :height, :width,
335
+ :align, :valign,
336
+ :rotate, :rotate_around,
337
+ :overflow, :min_font_size,
338
+ :leading, :character_spacing,
339
+ :mode, :single_line,
340
+ :skip_encoding,
341
+ :document,
342
+ :direction,
343
+ :fallback_fonts,
344
+ :draw_text_callback]
345
+ end
346
+
331
347
  private
332
348
 
333
349
  def original_text
@@ -381,7 +397,7 @@ module Prawn
381
397
  # all fonts
382
398
  fallback_fonts << fragment_font
383
399
 
384
- hash[:text].unicode_characters do |char|
400
+ hash[:text].each_char do |char|
385
401
  @document.font(fragment_font)
386
402
  font_glyph_pairs << [find_font_for_this_glyph(char,
387
403
  @document.font.family,
@@ -450,7 +466,7 @@ module Prawn
450
466
  # we need to wait until render() is called so that the fonts are set
451
467
  # up properly for wrapping. So guard with a boolean to ensure this is
452
468
  # only run once.
453
- return if @vertical_alignment_processed
469
+ return if defined?(@vertical_alignment_processed) && @vertical_alignment_processed
454
470
  @vertical_alignment_processed = true
455
471
 
456
472
  return if @vertical_align == :top
@@ -521,6 +537,7 @@ module Prawn
521
537
  draw_fragment_overlay_styles(fragment)
522
538
  draw_fragment_overlay_link(fragment)
523
539
  draw_fragment_overlay_anchor(fragment)
540
+ draw_fragment_overlay_local(fragment)
524
541
  fragment.callback_objects.each do |obj|
525
542
  obj.render_in_front(fragment) if obj.respond_to?(:render_in_front)
526
543
  end
@@ -533,7 +550,7 @@ module Prawn
533
550
  :Border => [0, 0, 0],
534
551
  :A => { :Type => :Action,
535
552
  :S => :URI,
536
- :URI => Prawn::Core::LiteralString.new(fragment.link) })
553
+ :URI => PDF::Core::LiteralString.new(fragment.link) })
537
554
  end
538
555
 
539
556
  def draw_fragment_overlay_anchor(fragment)
@@ -544,12 +561,23 @@ module Prawn
544
561
  :Dest => fragment.anchor)
545
562
  end
546
563
 
564
+ def draw_fragment_overlay_local(fragment)
565
+ return unless fragment.local
566
+ box = fragment.absolute_bounding_box
567
+ @document.link_annotation(box,
568
+ :Border => [0, 0, 0],
569
+ :A => { :Type => :Action,
570
+ :S => :Launch,
571
+ :F => PDF::Core::LiteralString.new(fragment.local),
572
+ :NewWindow => true })
573
+ end
574
+
547
575
  def draw_fragment_overlay_styles(fragment)
548
576
  underline = fragment.styles.include?(:underline)
549
577
  if underline
550
578
  @document.stroke_line(fragment.underline_points)
551
579
  end
552
-
580
+
553
581
  strikethrough = fragment.styles.include?(:strikethrough)
554
582
  if strikethrough
555
583
  @document.stroke_line(fragment.strikethrough_points)
@@ -10,9 +10,11 @@ module Prawn
10
10
  module Text
11
11
  module Formatted
12
12
 
13
+
13
14
  # Prawn::Text::Formatted::Fragment is a state store for a formatted text
14
15
  # fragment. It does not render anything.
15
16
  #
17
+ # @private
16
18
  class Fragment
17
19
 
18
20
  attr_reader :format_state, :text
@@ -91,6 +93,10 @@ module Prawn
91
93
  @format_state[:anchor]
92
94
  end
93
95
 
96
+ def local
97
+ @format_state[:local]
98
+ end
99
+
94
100
  def color
95
101
  @format_state[:color]
96
102
  end
@@ -208,11 +214,7 @@ module Prawn
208
214
  end
209
215
  case direction
210
216
  when :rtl
211
- if ruby_18 { true }
212
- string.scan(/./mu).reverse.join
213
- else
214
- string.reverse
215
- end
217
+ string.reverse
216
218
  else
217
219
  string
218
220
  end
@@ -228,11 +230,9 @@ module Prawn
228
230
 
229
231
  def process_soft_hyphens(string)
230
232
  if string.length > 0 && normalized_soft_hyphen
231
- ruby_19 {
232
- if string.encoding != normalized_soft_hyphen.encoding
233
- string.force_encoding(normalized_soft_hyphen.encoding)
234
- end
235
- }
233
+ if string.encoding != normalized_soft_hyphen.encoding
234
+ string.force_encoding(normalized_soft_hyphen.encoding)
235
+ end
236
236
  string[0..-2].gsub(normalized_soft_hyphen, "") + string[-1..-1]
237
237
  else
238
238
  string
@@ -240,7 +240,7 @@ module Prawn
240
240
  end
241
241
 
242
242
  def strip_zero_width_spaces(string)
243
- if !"".respond_to?(:encoding) || string.encoding.to_s == "UTF-8"
243
+ if string.encoding == ::Encoding::UTF_8
244
244
  string.gsub(Prawn::Text::ZWSP, "")
245
245
  else
246
246
  string
@@ -0,0 +1,266 @@
1
+ # encoding: utf-8
2
+
3
+ # core/text/formatted/line_wrap.rb : Implements individual line wrapping of
4
+ # 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
+
11
+ module Prawn
12
+ module Text
13
+ module Formatted #:nodoc:
14
+
15
+ # @private
16
+ class LineWrap #:nodoc:
17
+
18
+ # The width of the last wrapped line
19
+ #
20
+ def width
21
+ @accumulated_width || 0
22
+ end
23
+
24
+ # The number of spaces in the last wrapped line
25
+ attr_reader :space_count
26
+
27
+ # Whether this line is the last line in the paragraph
28
+ def paragraph_finished?
29
+ @newline_encountered || is_next_string_newline? || @arranger.finished?
30
+ end
31
+
32
+ # Work in conjunction with the PDF::Formatted::Arranger
33
+ # defined in the :arranger option to determine what formatted text
34
+ # will fit within the width defined by the :width option
35
+ #
36
+ def wrap_line(options)
37
+ initialize_line(options)
38
+
39
+ while fragment = @arranger.next_string
40
+ @fragment_output = ""
41
+
42
+ fragment.lstrip! if first_fragment_on_this_line?(fragment)
43
+ next if empty_line?(fragment)
44
+
45
+ unless apply_font_settings_and_add_fragment_to_line(fragment)
46
+ break
47
+ end
48
+ end
49
+ @arranger.finalize_line
50
+ @accumulated_width = @arranger.line_width
51
+ @space_count = @arranger.space_count
52
+ @arranger.line
53
+ end
54
+
55
+ private
56
+
57
+ def first_fragment_on_this_line?(fragment)
58
+ line_empty? && fragment != "\n"
59
+ end
60
+
61
+ def empty_line?(fragment)
62
+ empty = line_empty? && fragment.empty? && is_next_string_newline?
63
+ @arranger.update_last_string("", "", soft_hyphen) if empty
64
+ empty
65
+ end
66
+
67
+ def is_next_string_newline?
68
+ @arranger.preview_next_string == "\n"
69
+ end
70
+
71
+ def apply_font_settings_and_add_fragment_to_line(fragment)
72
+ result = nil
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
77
+ result = add_fragment_to_line(fragment)
78
+ end
79
+ result
80
+ end
81
+
82
+ # returns true iff all text was printed without running into the end of
83
+ # the line
84
+ #
85
+ def add_fragment_to_line(fragment)
86
+ if fragment == ""
87
+ true
88
+ elsif fragment == "\n"
89
+ @newline_encountered = true
90
+ false
91
+ else
92
+ fragment.scan(scan_pattern).each do |segment|
93
+ if segment == zero_width_space
94
+ segment_width = 0
95
+ else
96
+ segment_width = @document.width_of(segment, :kerning => @kerning)
97
+ end
98
+
99
+ if @accumulated_width + segment_width <= @width
100
+ @accumulated_width += segment_width
101
+ @fragment_output += segment
102
+ else
103
+ end_of_the_line_reached(segment)
104
+ fragment_finished(fragment)
105
+ return false
106
+ end
107
+ end
108
+
109
+ fragment_finished(fragment)
110
+ true
111
+ end
112
+ end
113
+
114
+ # The pattern used to determine chunks of text to place on a given line
115
+ #
116
+ def scan_pattern
117
+ pattern = "[^#{break_chars}]+#{soft_hyphen}|" +
118
+ "[^#{break_chars}]+#{hyphen}+|" +
119
+ "[^#{break_chars}]+|" +
120
+ "[#{whitespace}]+|" +
121
+ "#{hyphen}+[^#{break_chars}]*|" +
122
+ "#{soft_hyphen}"
123
+ Regexp.new(pattern)
124
+ end
125
+
126
+ # The pattern used to determine whether any word breaks exist on a
127
+ # current line, which in turn determines whether character level
128
+ # word breaking is needed
129
+ #
130
+ def word_division_scan_pattern
131
+ Regexp.new("\\s|[#{zero_width_space}#{soft_hyphen}#{hyphen}]")
132
+ end
133
+
134
+ def break_chars
135
+ "#{whitespace}#{soft_hyphen}#{hyphen}"
136
+ end
137
+
138
+ def whitespace
139
+ " \\t#{zero_width_space}"
140
+ end
141
+
142
+ def hyphen
143
+ "-"
144
+ end
145
+
146
+ def soft_hyphen
147
+ @soft_hyphen
148
+ end
149
+
150
+ def zero_width_space
151
+ @zero_width_space
152
+ end
153
+
154
+ def line_empty?
155
+ @line_empty && @accumulated_width == 0
156
+ end
157
+
158
+ def initialize_line(options)
159
+ @document = options[:document]
160
+ @kerning = options[:kerning]
161
+ @width = options[:width]
162
+
163
+ @accumulated_width = 0
164
+ @line_empty = true
165
+ @line_contains_more_than_one_word = false
166
+
167
+ @arranger = options[:arranger]
168
+ @arranger.initialize_line
169
+
170
+ @newline_encountered = false
171
+ @line_full = false
172
+ end
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
+
182
+ def fragment_finished(fragment)
183
+ if fragment == "\n"
184
+ @newline_encountered = true
185
+ @line_empty = false
186
+ else
187
+ update_output_based_on_last_fragment(fragment, soft_hyphen)
188
+ update_line_status_based_on_last_output
189
+ determine_whether_to_pull_preceding_fragment_to_join_this_one(fragment)
190
+ end
191
+ remember_this_fragment_for_backward_looking_ops
192
+ end
193
+
194
+ def update_output_based_on_last_fragment(fragment, normalized_soft_hyphen=nil)
195
+ remaining_text = fragment.slice(@fragment_output.length..fragment.length)
196
+ raise Prawn::Errors::CannotFit if line_finished? && line_empty? &&
197
+ @fragment_output.empty? && !fragment.strip.empty?
198
+ @arranger.update_last_string(@fragment_output, remaining_text, normalized_soft_hyphen)
199
+ end
200
+
201
+ def determine_whether_to_pull_preceding_fragment_to_join_this_one(current_fragment)
202
+ if @fragment_output.empty? &&
203
+ !current_fragment.empty? &&
204
+ @line_contains_more_than_one_word
205
+ unless previous_fragment_ended_with_breakable? ||
206
+ fragment_begins_with_breakable?(current_fragment)
207
+ @fragment_output = @previous_fragment_output_without_last_word
208
+ update_output_based_on_last_fragment(@previous_fragment)
209
+ end
210
+ end
211
+ end
212
+
213
+ def remember_this_fragment_for_backward_looking_ops
214
+ @previous_fragment = @fragment_output.dup
215
+ pf = @previous_fragment
216
+ @previous_fragment_ended_with_breakable = pf =~ /[#{break_chars}]$/
217
+ last_word = pf.slice(/[^#{break_chars}]*$/)
218
+ last_word_length = last_word.nil? ? 0 : last_word.length
219
+ @previous_fragment_output_without_last_word = pf.slice(0, pf.length - last_word_length)
220
+ end
221
+
222
+ def previous_fragment_ended_with_breakable?
223
+ @previous_fragment_ended_with_breakable
224
+ end
225
+
226
+ def fragment_begins_with_breakable?(fragment)
227
+ fragment =~ /^[#{break_chars}]/
228
+ end
229
+
230
+ def line_finished?
231
+ @line_full || paragraph_finished?
232
+ end
233
+
234
+ def update_line_status_based_on_last_output
235
+ @line_contains_more_than_one_word = true if @fragment_output =~ word_division_scan_pattern
236
+ end
237
+
238
+ def end_of_the_line_reached(segment)
239
+ update_line_status_based_on_last_output
240
+ wrap_by_char(segment) unless @line_contains_more_than_one_word
241
+ @line_full = true
242
+ end
243
+
244
+ def wrap_by_char(segment)
245
+ font = @document.font
246
+ segment.each_char do |char|
247
+ break unless append_char(char,font)
248
+ end
249
+ end
250
+
251
+ def append_char(char,font)
252
+ # kerning doesn't make sense in the context of a single character
253
+ char_width = font.compute_width_of(char)
254
+
255
+ if @accumulated_width + char_width <= @width
256
+ @accumulated_width += char_width
257
+ @fragment_output << char
258
+ true
259
+ else
260
+ false
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end