prawn-git 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/COPYING +2 -0
  4. data/GPLv2 +340 -0
  5. data/GPLv3 +674 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +56 -0
  8. data/Rakefile +55 -0
  9. data/data/fonts/Courier-Bold.afm +342 -0
  10. data/data/fonts/Courier-BoldOblique.afm +342 -0
  11. data/data/fonts/Courier-Oblique.afm +342 -0
  12. data/data/fonts/Courier.afm +342 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/data/images/16bit.alpha +0 -0
  25. data/data/images/16bit.color +0 -0
  26. data/data/images/16bit.png +0 -0
  27. data/data/images/arrow.png +0 -0
  28. data/data/images/arrow2.png +0 -0
  29. data/data/images/dice.alpha +0 -0
  30. data/data/images/dice.color +0 -0
  31. data/data/images/dice.png +0 -0
  32. data/data/images/dice_interlaced.png +0 -0
  33. data/data/images/fractal.jpg +0 -0
  34. data/data/images/indexed_color.dat +0 -0
  35. data/data/images/indexed_color.png +0 -0
  36. data/data/images/letterhead.jpg +0 -0
  37. data/data/images/license.md +8 -0
  38. data/data/images/page_white_text.alpha +0 -0
  39. data/data/images/page_white_text.color +0 -0
  40. data/data/images/page_white_text.png +0 -0
  41. data/data/images/pal_bk.png +0 -0
  42. data/data/images/pigs.jpg +0 -0
  43. data/data/images/prawn.png +0 -0
  44. data/data/images/ruport.png +0 -0
  45. data/data/images/ruport_data.dat +0 -0
  46. data/data/images/ruport_transparent.png +0 -0
  47. data/data/images/ruport_type0.png +0 -0
  48. data/data/images/stef.jpg +0 -0
  49. data/data/images/tru256.bmp +0 -0
  50. data/data/images/web-links.dat +1 -0
  51. data/data/images/web-links.png +0 -0
  52. data/data/pdfs/complex_template.pdf +0 -0
  53. data/data/pdfs/contains_ttf_font.pdf +0 -0
  54. data/data/pdfs/encrypted.pdf +0 -0
  55. data/data/pdfs/form.pdf +820 -0
  56. data/data/pdfs/hexagon.pdf +61 -0
  57. data/data/pdfs/indirect_reference.pdf +86 -0
  58. data/data/pdfs/multipage_template.pdf +127 -0
  59. data/data/pdfs/nested_pages.pdf +118 -0
  60. data/data/pdfs/page_without_mediabox.pdf +193 -0
  61. data/data/pdfs/resources_as_indirect_object.pdf +83 -0
  62. data/data/pdfs/two_hexagons.pdf +90 -0
  63. data/data/pdfs/version_1_6.pdf +61 -0
  64. data/data/shift_jis_text.txt +1 -0
  65. data/lib/prawn.rb +89 -0
  66. data/lib/prawn/document.rb +706 -0
  67. data/lib/prawn/document/bounding_box.rb +539 -0
  68. data/lib/prawn/document/column_box.rb +144 -0
  69. data/lib/prawn/document/internals.rb +58 -0
  70. data/lib/prawn/document/span.rb +57 -0
  71. data/lib/prawn/encoding.rb +87 -0
  72. data/lib/prawn/errors.rb +80 -0
  73. data/lib/prawn/font.rb +413 -0
  74. data/lib/prawn/font/afm.rb +256 -0
  75. data/lib/prawn/font/dfont.rb +43 -0
  76. data/lib/prawn/font/ttf.rb +355 -0
  77. data/lib/prawn/font_metric_cache.rb +46 -0
  78. data/lib/prawn/graphics.rb +646 -0
  79. data/lib/prawn/graphics/cap_style.rb +47 -0
  80. data/lib/prawn/graphics/color.rb +232 -0
  81. data/lib/prawn/graphics/dash.rb +109 -0
  82. data/lib/prawn/graphics/join_style.rb +49 -0
  83. data/lib/prawn/graphics/patterns.rb +126 -0
  84. data/lib/prawn/graphics/transformation.rb +157 -0
  85. data/lib/prawn/graphics/transparency.rb +101 -0
  86. data/lib/prawn/grid.rb +279 -0
  87. data/lib/prawn/image_handler.rb +44 -0
  88. data/lib/prawn/images.rb +199 -0
  89. data/lib/prawn/images/image.rb +49 -0
  90. data/lib/prawn/images/jpg.rb +91 -0
  91. data/lib/prawn/images/png.rb +290 -0
  92. data/lib/prawn/measurement_extensions.rb +50 -0
  93. data/lib/prawn/measurements.rb +77 -0
  94. data/lib/prawn/outline.rb +289 -0
  95. data/lib/prawn/repeater.rb +124 -0
  96. data/lib/prawn/security.rb +288 -0
  97. data/lib/prawn/security/arcfour.rb +54 -0
  98. data/lib/prawn/soft_mask.rb +94 -0
  99. data/lib/prawn/stamp.rb +136 -0
  100. data/lib/prawn/text.rb +437 -0
  101. data/lib/prawn/text/box.rb +141 -0
  102. data/lib/prawn/text/formatted.rb +7 -0
  103. data/lib/prawn/text/formatted/arranger.rb +290 -0
  104. data/lib/prawn/text/formatted/box.rb +614 -0
  105. data/lib/prawn/text/formatted/fragment.rb +264 -0
  106. data/lib/prawn/text/formatted/line_wrap.rb +277 -0
  107. data/lib/prawn/text/formatted/parser.rb +224 -0
  108. data/lib/prawn/text/formatted/wrap.rb +160 -0
  109. data/lib/prawn/utilities.rb +46 -0
  110. data/lib/prawn/version.rb +5 -0
  111. data/lib/prawn/view.rb +91 -0
  112. data/manual/absolute_position.pdf +0 -0
  113. data/manual/basic_concepts/adding_pages.rb +27 -0
  114. data/manual/basic_concepts/basic_concepts.rb +36 -0
  115. data/manual/basic_concepts/creation.rb +39 -0
  116. data/manual/basic_concepts/cursor.rb +33 -0
  117. data/manual/basic_concepts/measurement.rb +25 -0
  118. data/manual/basic_concepts/origin.rb +38 -0
  119. data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
  120. data/manual/basic_concepts/view.rb +42 -0
  121. data/manual/bounding_box/bounding_box.rb +39 -0
  122. data/manual/bounding_box/bounds.rb +49 -0
  123. data/manual/bounding_box/canvas.rb +24 -0
  124. data/manual/bounding_box/creation.rb +23 -0
  125. data/manual/bounding_box/indentation.rb +46 -0
  126. data/manual/bounding_box/nesting.rb +45 -0
  127. data/manual/bounding_box/russian_boxes.rb +40 -0
  128. data/manual/bounding_box/stretchy.rb +31 -0
  129. data/manual/contents.rb +29 -0
  130. data/manual/cover.rb +39 -0
  131. data/manual/document_and_page_options/background.rb +27 -0
  132. data/manual/document_and_page_options/document_and_page_options.rb +32 -0
  133. data/manual/document_and_page_options/metadata.rb +23 -0
  134. data/manual/document_and_page_options/page_margins.rb +38 -0
  135. data/manual/document_and_page_options/page_size.rb +34 -0
  136. data/manual/document_and_page_options/print_scaling.rb +20 -0
  137. data/manual/example_helper.rb +7 -0
  138. data/manual/graphics/circle_and_ellipse.rb +22 -0
  139. data/manual/graphics/color.rb +24 -0
  140. data/manual/graphics/common_lines.rb +30 -0
  141. data/manual/graphics/fill_and_stroke.rb +42 -0
  142. data/manual/graphics/fill_rules.rb +37 -0
  143. data/manual/graphics/gradients.rb +37 -0
  144. data/manual/graphics/graphics.rb +58 -0
  145. data/manual/graphics/helper.rb +24 -0
  146. data/manual/graphics/line_width.rb +35 -0
  147. data/manual/graphics/lines_and_curves.rb +41 -0
  148. data/manual/graphics/polygon.rb +29 -0
  149. data/manual/graphics/rectangle.rb +21 -0
  150. data/manual/graphics/rotate.rb +28 -0
  151. data/manual/graphics/scale.rb +41 -0
  152. data/manual/graphics/soft_masks.rb +46 -0
  153. data/manual/graphics/stroke_cap.rb +31 -0
  154. data/manual/graphics/stroke_dash.rb +48 -0
  155. data/manual/graphics/stroke_join.rb +30 -0
  156. data/manual/graphics/translate.rb +29 -0
  157. data/manual/graphics/transparency.rb +35 -0
  158. data/manual/how_to_read_this_manual.rb +40 -0
  159. data/manual/images/absolute_position.rb +23 -0
  160. data/manual/images/fit.rb +21 -0
  161. data/manual/images/horizontal.rb +25 -0
  162. data/manual/images/images.rb +40 -0
  163. data/manual/images/plain_image.rb +18 -0
  164. data/manual/images/scale.rb +22 -0
  165. data/manual/images/vertical.rb +28 -0
  166. data/manual/images/width_and_height.rb +25 -0
  167. data/manual/layout/boxes.rb +27 -0
  168. data/manual/layout/content.rb +25 -0
  169. data/manual/layout/layout.rb +28 -0
  170. data/manual/layout/simple_grid.rb +23 -0
  171. data/manual/outline/add_subsection_to.rb +61 -0
  172. data/manual/outline/insert_section_after.rb +47 -0
  173. data/manual/outline/outline.rb +32 -0
  174. data/manual/outline/sections_and_pages.rb +67 -0
  175. data/manual/repeatable_content/alternate_page_numbering.rb +32 -0
  176. data/manual/repeatable_content/page_numbering.rb +54 -0
  177. data/manual/repeatable_content/repeatable_content.rb +32 -0
  178. data/manual/repeatable_content/repeater.rb +55 -0
  179. data/manual/repeatable_content/stamp.rb +41 -0
  180. data/manual/security/encryption.rb +31 -0
  181. data/manual/security/permissions.rb +38 -0
  182. data/manual/security/security.rb +28 -0
  183. data/manual/table.rb +16 -0
  184. data/manual/text/alignment.rb +44 -0
  185. data/manual/text/color.rb +24 -0
  186. data/manual/text/column_box.rb +32 -0
  187. data/manual/text/fallback_fonts.rb +37 -0
  188. data/manual/text/font.rb +41 -0
  189. data/manual/text/font_size.rb +45 -0
  190. data/manual/text/font_style.rb +23 -0
  191. data/manual/text/formatted_callbacks.rb +60 -0
  192. data/manual/text/formatted_text.rb +50 -0
  193. data/manual/text/free_flowing_text.rb +51 -0
  194. data/manual/text/inline.rb +41 -0
  195. data/manual/text/kerning_and_character_spacing.rb +39 -0
  196. data/manual/text/leading.rb +25 -0
  197. data/manual/text/line_wrapping.rb +41 -0
  198. data/manual/text/paragraph_indentation.rb +34 -0
  199. data/manual/text/positioned_text.rb +38 -0
  200. data/manual/text/registering_families.rb +48 -0
  201. data/manual/text/rendering_and_color.rb +37 -0
  202. data/manual/text/right_to_left_text.rb +47 -0
  203. data/manual/text/rotation.rb +43 -0
  204. data/manual/text/single_usage.rb +37 -0
  205. data/manual/text/text.rb +73 -0
  206. data/manual/text/text_box_excess.rb +32 -0
  207. data/manual/text/text_box_extensions.rb +45 -0
  208. data/manual/text/text_box_overflow.rb +48 -0
  209. data/manual/text/utf8.rb +28 -0
  210. data/manual/text/win_ansi_charset.rb +60 -0
  211. data/prawn.gemspec +45 -0
  212. data/spec/acceptance/png.rb +25 -0
  213. data/spec/annotations_spec.rb +74 -0
  214. data/spec/bounding_box_spec.rb +510 -0
  215. data/spec/column_box_spec.rb +65 -0
  216. data/spec/data/curves.pdf +66 -0
  217. data/spec/destinations_spec.rb +15 -0
  218. data/spec/document_spec.rb +748 -0
  219. data/spec/extensions/encoding_helpers.rb +11 -0
  220. data/spec/extensions/mocha.rb +46 -0
  221. data/spec/font_metric_cache_spec.rb +52 -0
  222. data/spec/font_spec.rb +474 -0
  223. data/spec/formatted_text_arranger_spec.rb +421 -0
  224. data/spec/formatted_text_box_spec.rb +705 -0
  225. data/spec/formatted_text_fragment_spec.rb +298 -0
  226. data/spec/graphics_spec.rb +683 -0
  227. data/spec/grid_spec.rb +96 -0
  228. data/spec/image_handler_spec.rb +54 -0
  229. data/spec/images_spec.rb +153 -0
  230. data/spec/inline_formatted_text_parser_spec.rb +564 -0
  231. data/spec/jpg_spec.rb +25 -0
  232. data/spec/line_wrap_spec.rb +367 -0
  233. data/spec/measurement_units_spec.rb +25 -0
  234. data/spec/outline_spec.rb +430 -0
  235. data/spec/png_spec.rb +245 -0
  236. data/spec/reference_spec.rb +25 -0
  237. data/spec/repeater_spec.rb +160 -0
  238. data/spec/security_spec.rb +158 -0
  239. data/spec/soft_mask_spec.rb +79 -0
  240. data/spec/span_spec.rb +44 -0
  241. data/spec/spec_helper.rb +54 -0
  242. data/spec/stamp_spec.rb +160 -0
  243. data/spec/stroke_styles_spec.rb +211 -0
  244. data/spec/text_at_spec.rb +143 -0
  245. data/spec/text_box_spec.rb +1043 -0
  246. data/spec/text_rendering_mode_spec.rb +45 -0
  247. data/spec/text_spacing_spec.rb +93 -0
  248. data/spec/text_spec.rb +557 -0
  249. data/spec/text_with_inline_formatting_spec.rb +35 -0
  250. data/spec/transparency_spec.rb +91 -0
  251. data/spec/view_spec.rb +43 -0
  252. metadata +509 -0
@@ -0,0 +1,264 @@
1
+ # encoding: utf-8
2
+
3
+ # text/formatted/fragment.rb : Implements information about a formatted fragment
4
+ #
5
+ # Copyright March 2010, Daniel Nelson. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module Prawn
10
+ module Text
11
+ module Formatted
12
+
13
+
14
+ # Prawn::Text::Formatted::Fragment is a state store for a formatted text
15
+ # fragment. It does not render anything.
16
+ #
17
+ # @private
18
+ class Fragment
19
+
20
+ attr_reader :format_state, :text
21
+ attr_writer :width
22
+ attr_accessor :line_height, :descender, :ascender
23
+ attr_accessor :word_spacing, :left, :baseline
24
+
25
+ def initialize(text, format_state, document)
26
+ @format_state = format_state
27
+ @document = document
28
+ @word_spacing = 0
29
+
30
+ # keep the original value of "text", so we can reinitialize @text if formatting parameters
31
+ # like text direction are changed
32
+ @original_text = text
33
+ @text = process_text(@original_text)
34
+ end
35
+
36
+ def width
37
+ if @word_spacing == 0 then @width
38
+ else @width + @word_spacing * space_count
39
+ end
40
+ end
41
+
42
+ def height
43
+ top - bottom
44
+ end
45
+
46
+ def subscript?
47
+ styles.include?(:subscript)
48
+ end
49
+
50
+ def superscript?
51
+ styles.include?(:superscript)
52
+ end
53
+
54
+ def y_offset
55
+ if subscript? then -descender
56
+ elsif superscript? then 0.85 * ascender
57
+ else 0
58
+ end
59
+ end
60
+
61
+ def bounding_box
62
+ [left, bottom, right, top]
63
+ end
64
+
65
+ def absolute_bounding_box
66
+ box = bounding_box
67
+ box[0] += @document.bounds.absolute_left
68
+ box[2] += @document.bounds.absolute_left
69
+ box[1] += @document.bounds.absolute_bottom
70
+ box[3] += @document.bounds.absolute_bottom
71
+ box
72
+ end
73
+
74
+ def underline_points
75
+ y = baseline - 1.25
76
+ [[left, y], [right, y]]
77
+ end
78
+
79
+ def strikethrough_points
80
+ y = baseline + ascender * 0.3
81
+ [[left, y], [right, y]]
82
+ end
83
+
84
+ def styles
85
+ @format_state[:styles] || []
86
+ end
87
+
88
+ def link
89
+ @format_state[:link]
90
+ end
91
+
92
+ def anchor
93
+ @format_state[:anchor]
94
+ end
95
+
96
+ def local
97
+ @format_state[:local]
98
+ end
99
+
100
+ def color
101
+ @format_state[:color]
102
+ end
103
+
104
+ def font
105
+ @format_state[:font]
106
+ end
107
+
108
+ def size
109
+ @format_state[:size]
110
+ end
111
+
112
+ def character_spacing
113
+ @format_state[:character_spacing] ||
114
+ @document.character_spacing
115
+ end
116
+
117
+ def direction
118
+ @format_state[:direction]
119
+ end
120
+
121
+ def default_direction=(direction)
122
+ unless @format_state[:direction]
123
+ @format_state[:direction] = direction
124
+ @text = process_text(@original_text)
125
+ end
126
+ end
127
+
128
+ def include_trailing_white_space!
129
+ @format_state.delete(:exclude_trailing_white_space)
130
+ @text = process_text(@original_text)
131
+ end
132
+
133
+ def space_count
134
+ @text.count(" ")
135
+ end
136
+
137
+ def callback_objects
138
+ callback = @format_state[:callback]
139
+ if callback.nil?
140
+ []
141
+ elsif callback.is_a?(Array)
142
+ callback
143
+ else
144
+ [callback]
145
+ end
146
+ end
147
+
148
+ def right
149
+ left + width
150
+ end
151
+
152
+ def top
153
+ baseline + ascender
154
+ end
155
+
156
+ def bottom
157
+ baseline - descender
158
+ end
159
+
160
+ def top_left
161
+ [left, top]
162
+ end
163
+
164
+ def top_right
165
+ [right, top]
166
+ end
167
+
168
+ def bottom_right
169
+ [right, bottom]
170
+ end
171
+
172
+ def bottom_left
173
+ [left, bottom]
174
+ end
175
+
176
+ def absolute_left
177
+ absolute_bounding_box[0]
178
+ end
179
+
180
+ def absolute_right
181
+ absolute_bounding_box[2]
182
+ end
183
+
184
+ def absolute_top
185
+ absolute_bounding_box[3]
186
+ end
187
+
188
+ def absolute_bottom
189
+ absolute_bounding_box[1]
190
+ end
191
+
192
+ def absolute_top_left
193
+ [absolute_left, absolute_top]
194
+ end
195
+
196
+ def absolute_top_right
197
+ [absolute_right, absolute_top]
198
+ end
199
+
200
+ def absolute_bottom_left
201
+ [absolute_left, absolute_bottom]
202
+ end
203
+
204
+ def absolute_bottom_right
205
+ [absolute_right, absolute_bottom]
206
+ end
207
+
208
+ private
209
+
210
+ def process_text(text)
211
+ string = strip_zero_width_spaces(text)
212
+
213
+ if exclude_trailing_white_space?
214
+ string = string.rstrip
215
+
216
+ if soft_hyphens_need_processing?(string)
217
+ string = process_soft_hyphens(string[0..-2]) + string[-1..-1]
218
+ end
219
+ else
220
+ if soft_hyphens_need_processing?(string)
221
+ string = process_soft_hyphens(string)
222
+ end
223
+ end
224
+
225
+ case direction
226
+ when :rtl
227
+ string.reverse
228
+ else
229
+ string
230
+ end
231
+ end
232
+
233
+ def exclude_trailing_white_space?
234
+ @format_state[:exclude_trailing_white_space]
235
+ end
236
+
237
+ def soft_hyphens_need_processing?(string)
238
+ string.length > 0 && normalized_soft_hyphen
239
+ end
240
+
241
+ def normalized_soft_hyphen
242
+ @format_state[:normalized_soft_hyphen]
243
+ end
244
+
245
+ def process_soft_hyphens(string)
246
+ if string.encoding != normalized_soft_hyphen.encoding
247
+ string.force_encoding(normalized_soft_hyphen.encoding)
248
+ end
249
+
250
+ string.gsub(normalized_soft_hyphen, "")
251
+ end
252
+
253
+ def strip_zero_width_spaces(string)
254
+ if string.encoding == ::Encoding::UTF_8
255
+ string.gsub(Prawn::Text::ZWSP, "")
256
+ else
257
+ string
258
+ end
259
+ end
260
+
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,277 @@
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
+ def tokenize(fragment)
33
+ fragment.scan(scan_pattern)
34
+ end
35
+
36
+ # Work in conjunction with the PDF::Formatted::Arranger
37
+ # defined in the :arranger option to determine what formatted text
38
+ # will fit within the width defined by the :width option
39
+ #
40
+ def wrap_line(options)
41
+ initialize_line(options)
42
+
43
+ while fragment = @arranger.next_string
44
+ @fragment_output = ""
45
+
46
+ fragment.lstrip! if first_fragment_on_this_line?(fragment)
47
+ next if empty_line?(fragment)
48
+
49
+ unless apply_font_settings_and_add_fragment_to_line(fragment)
50
+ break
51
+ end
52
+ end
53
+ @arranger.finalize_line
54
+ @accumulated_width = @arranger.line_width
55
+ @space_count = @arranger.space_count
56
+ @arranger.line
57
+ end
58
+
59
+ private
60
+
61
+ def first_fragment_on_this_line?(fragment)
62
+ line_empty? && fragment != "\n"
63
+ end
64
+
65
+ def empty_line?(fragment)
66
+ empty = line_empty? && fragment.empty? && is_next_string_newline?
67
+ @arranger.update_last_string("", "", soft_hyphen) if empty
68
+ empty
69
+ end
70
+
71
+ def is_next_string_newline?
72
+ @arranger.preview_next_string == "\n"
73
+ end
74
+
75
+ def apply_font_settings_and_add_fragment_to_line(fragment)
76
+ result = nil
77
+ @arranger.apply_font_settings do
78
+ # if font has changed from Unicode to non-Unicode, or vice versa, the characters used for soft hyphens
79
+ # and zero-width spaces will be different
80
+ set_soft_hyphen_and_zero_width_space
81
+ result = add_fragment_to_line(fragment)
82
+ end
83
+ result
84
+ end
85
+
86
+ # returns true if all text was printed without running into the end of
87
+ # the line
88
+ #
89
+ def add_fragment_to_line(fragment)
90
+ if fragment == ""
91
+ true
92
+ elsif fragment == "\n"
93
+ @newline_encountered = true
94
+ false
95
+ else
96
+ tokenize(fragment).each do |segment|
97
+ if segment == zero_width_space
98
+ segment_width = 0
99
+ else
100
+ segment_width = @document.width_of(segment, :kerning => @kerning)
101
+ end
102
+
103
+ if @accumulated_width + segment_width <= @width
104
+ @accumulated_width += segment_width
105
+ if segment[-1] == soft_hyphen
106
+ sh_width = @document.width_of("#{soft_hyphen}", :kerning => @kerning)
107
+ @accumulated_width -= sh_width
108
+ end
109
+ @fragment_output += segment
110
+ else
111
+ end_of_the_line_reached(segment)
112
+ fragment_finished(fragment)
113
+ return false
114
+ end
115
+ end
116
+
117
+ fragment_finished(fragment)
118
+ true
119
+ end
120
+ end
121
+
122
+ # The pattern used to determine chunks of text to place on a given line
123
+ #
124
+ def scan_pattern
125
+ pattern = "[^#{break_chars}]+#{soft_hyphen}|" +
126
+ "[^#{break_chars}]+#{hyphen}+|" +
127
+ "[^#{break_chars}]+|" +
128
+ "[#{whitespace}]+|" +
129
+ "#{hyphen}+[^#{break_chars}]*|" +
130
+ "#{soft_hyphen}"
131
+
132
+ Regexp.new(pattern)
133
+ end
134
+
135
+ # The pattern used to determine whether any word breaks exist on a
136
+ # current line, which in turn determines whether character level
137
+ # word breaking is needed
138
+ #
139
+ def word_division_scan_pattern
140
+ Regexp.new("\\s|[#{zero_width_space}#{soft_hyphen}#{hyphen}]")
141
+ end
142
+
143
+ def break_chars
144
+ "#{whitespace}#{soft_hyphen}#{hyphen}"
145
+ end
146
+
147
+ def whitespace
148
+ " \\t#{zero_width_space}"
149
+ end
150
+
151
+ def hyphen
152
+ "-"
153
+ end
154
+
155
+ def soft_hyphen
156
+ @soft_hyphen
157
+ end
158
+
159
+ def zero_width_space
160
+ @zero_width_space
161
+ end
162
+
163
+ def line_empty?
164
+ @line_empty && @accumulated_width == 0
165
+ end
166
+
167
+ def initialize_line(options)
168
+ @document = options[:document]
169
+ @kerning = options[:kerning]
170
+ @width = options[:width]
171
+
172
+ @disable_wrap_by_char = options[:disable_wrap_by_char]
173
+
174
+ @accumulated_width = 0
175
+ @line_empty = true
176
+ @line_contains_more_than_one_word = false
177
+
178
+ @arranger = options[:arranger]
179
+ @arranger.initialize_line
180
+
181
+ @newline_encountered = false
182
+ @line_full = false
183
+ end
184
+
185
+ def set_soft_hyphen_and_zero_width_space
186
+ # this is done once per fragment, after the font settings for the fragment are applied --
187
+ # it could actually be skipped if the font hasn't changed
188
+ font = @document.font
189
+ @soft_hyphen = font.normalize_encoding(Prawn::Text::SHY)
190
+ @zero_width_space = font.unicode? ? Prawn::Text::ZWSP : ""
191
+ end
192
+
193
+ def fragment_finished(fragment)
194
+ if fragment == "\n"
195
+ @newline_encountered = true
196
+ @line_empty = false
197
+ else
198
+ update_output_based_on_last_fragment(fragment, soft_hyphen)
199
+ update_line_status_based_on_last_output
200
+ determine_whether_to_pull_preceding_fragment_to_join_this_one(fragment)
201
+ end
202
+ remember_this_fragment_for_backward_looking_ops
203
+ end
204
+
205
+ def update_output_based_on_last_fragment(fragment, normalized_soft_hyphen=nil)
206
+ remaining_text = fragment.slice(@fragment_output.length..fragment.length)
207
+ raise Prawn::Errors::CannotFit if line_finished? && line_empty? &&
208
+ @fragment_output.empty? && !fragment.strip.empty?
209
+ @arranger.update_last_string(@fragment_output, remaining_text, normalized_soft_hyphen)
210
+ end
211
+
212
+ def determine_whether_to_pull_preceding_fragment_to_join_this_one(current_fragment)
213
+ if @fragment_output.empty? &&
214
+ !current_fragment.empty? &&
215
+ @line_contains_more_than_one_word
216
+ unless previous_fragment_ended_with_breakable? ||
217
+ fragment_begins_with_breakable?(current_fragment)
218
+ @fragment_output = @previous_fragment_output_without_last_word
219
+ update_output_based_on_last_fragment(@previous_fragment)
220
+ end
221
+ end
222
+ end
223
+
224
+ def remember_this_fragment_for_backward_looking_ops
225
+ @previous_fragment = @fragment_output.dup
226
+ pf = @previous_fragment
227
+ @previous_fragment_ended_with_breakable = pf =~ /[#{break_chars}]$/
228
+ last_word = pf.slice(/[^#{break_chars}]*$/)
229
+ last_word_length = last_word.nil? ? 0 : last_word.length
230
+ @previous_fragment_output_without_last_word = pf.slice(0, pf.length - last_word_length)
231
+ end
232
+
233
+ def previous_fragment_ended_with_breakable?
234
+ @previous_fragment_ended_with_breakable
235
+ end
236
+
237
+ def fragment_begins_with_breakable?(fragment)
238
+ fragment =~ /^[#{break_chars}]/
239
+ end
240
+
241
+ def line_finished?
242
+ @line_full || paragraph_finished?
243
+ end
244
+
245
+ def update_line_status_based_on_last_output
246
+ @line_contains_more_than_one_word = true if @fragment_output =~ word_division_scan_pattern
247
+ end
248
+
249
+ def end_of_the_line_reached(segment)
250
+ update_line_status_based_on_last_output
251
+ wrap_by_char(segment) unless @disable_wrap_by_char || @line_contains_more_than_one_word
252
+ @line_full = true
253
+ end
254
+
255
+ def wrap_by_char(segment)
256
+ font = @document.font
257
+ segment.each_char do |char|
258
+ break unless append_char(char,font)
259
+ end
260
+ end
261
+
262
+ def append_char(char,font)
263
+ # kerning doesn't make sense in the context of a single character
264
+ char_width = font.compute_width_of(char)
265
+
266
+ if @accumulated_width + char_width <= @width
267
+ @accumulated_width += char_width
268
+ @fragment_output << char
269
+ true
270
+ else
271
+ false
272
+ end
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end