prawn 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (282) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -2
  3. data/Gemfile +18 -0
  4. data/LICENSE +1 -1
  5. data/README.md +17 -4
  6. data/Rakefile +18 -22
  7. data/data/images/indexed_color.dat +0 -0
  8. data/data/images/indexed_color.png +0 -0
  9. data/data/pdfs/nested_pages.pdf +13 -13
  10. data/lib/pdf/core.rb +35 -0
  11. data/lib/{prawn → pdf}/core/annotations.rb +6 -7
  12. data/lib/{prawn → pdf}/core/byte_string.rb +1 -1
  13. data/lib/{prawn → pdf}/core/destinations.rb +23 -23
  14. data/lib/{prawn → pdf}/core/document_state.rb +8 -8
  15. data/lib/pdf/core/filter_list.rb +51 -0
  16. data/lib/pdf/core/filters.rb +36 -0
  17. data/lib/pdf/core/graphics_state.rb +68 -0
  18. data/lib/{prawn → pdf}/core/literal_string.rb +1 -1
  19. data/lib/{prawn → pdf}/core/name_tree.rb +14 -2
  20. data/lib/{prawn → pdf}/core/object_store.rb +80 -24
  21. data/lib/pdf/core/outline.rb +315 -0
  22. data/lib/{prawn → pdf}/core/page.rb +23 -26
  23. data/lib/{prawn/document → pdf/core}/page_geometry.rb +11 -21
  24. data/lib/{prawn → pdf}/core/pdf_object.rb +48 -32
  25. data/lib/{prawn → pdf}/core/reference.rb +35 -44
  26. data/lib/pdf/core/stream.rb +98 -0
  27. data/lib/{prawn → pdf}/core/text.rb +24 -17
  28. data/lib/prawn.rb +95 -17
  29. data/lib/prawn/compatibility.rb +66 -26
  30. data/lib/prawn/document.rb +48 -30
  31. data/lib/prawn/document/bounding_box.rb +3 -3
  32. data/lib/prawn/document/column_box.rb +46 -8
  33. data/lib/prawn/document/graphics_state.rb +10 -73
  34. data/lib/prawn/document/internals.rb +24 -23
  35. data/lib/prawn/document/snapshot.rb +6 -7
  36. data/lib/prawn/document/span.rb +10 -10
  37. data/lib/prawn/encoding.rb +7 -7
  38. data/lib/prawn/errors.rb +18 -29
  39. data/lib/prawn/font.rb +64 -28
  40. data/lib/prawn/font/afm.rb +32 -74
  41. data/lib/prawn/font/dfont.rb +2 -2
  42. data/lib/prawn/font/ttf.rb +28 -57
  43. data/lib/prawn/font_metric_cache.rb +45 -0
  44. data/lib/prawn/graphics.rb +307 -41
  45. data/lib/prawn/graphics/cap_style.rb +3 -3
  46. data/lib/prawn/graphics/color.rb +12 -5
  47. data/lib/prawn/graphics/dash.rb +52 -31
  48. data/lib/prawn/graphics/join_style.rb +7 -7
  49. data/lib/prawn/graphics/patterns.rb +137 -0
  50. data/lib/prawn/graphics/transformation.rb +9 -9
  51. data/lib/prawn/graphics/transparency.rb +1 -1
  52. data/lib/prawn/image_handler.rb +30 -0
  53. data/lib/prawn/images.rb +86 -105
  54. data/lib/prawn/images/image.rb +48 -0
  55. data/lib/prawn/images/jpg.rb +14 -10
  56. data/lib/prawn/images/png.rb +50 -37
  57. data/lib/prawn/layout.rb +2 -2
  58. data/lib/prawn/layout/grid.rb +51 -51
  59. data/lib/prawn/measurement_extensions.rb +5 -5
  60. data/lib/prawn/measurements.rb +25 -21
  61. data/lib/prawn/outline.rb +4 -308
  62. data/lib/prawn/repeater.rb +8 -8
  63. data/lib/prawn/security.rb +50 -36
  64. data/lib/prawn/soft_mask.rb +94 -0
  65. data/lib/prawn/stamp.rb +3 -3
  66. data/lib/prawn/table.rb +292 -118
  67. data/lib/prawn/table/cell.rb +272 -45
  68. data/lib/prawn/table/cell/image.rb +70 -0
  69. data/lib/prawn/table/cell/in_table.rb +2 -2
  70. data/lib/prawn/table/cell/span_dummy.rb +92 -0
  71. data/lib/prawn/table/cell/subtable.rb +2 -2
  72. data/lib/prawn/table/cell/text.rb +42 -24
  73. data/lib/prawn/table/cells.rb +137 -48
  74. data/lib/prawn/text.rb +35 -23
  75. data/lib/prawn/text/box.rb +18 -5
  76. data/lib/prawn/text/formatted.rb +5 -4
  77. data/lib/prawn/text/formatted/arranger.rb +292 -0
  78. data/lib/prawn/text/formatted/box.rb +52 -13
  79. data/lib/prawn/text/formatted/fragment.rb +37 -22
  80. data/lib/prawn/text/formatted/line_wrap.rb +286 -0
  81. data/lib/prawn/text/formatted/parser.rb +14 -6
  82. data/lib/prawn/text/formatted/wrap.rb +151 -0
  83. data/lib/prawn/utilities.rb +44 -0
  84. data/manual/basic_concepts/adding_pages.rb +27 -0
  85. data/manual/basic_concepts/basic_concepts.rb +34 -0
  86. data/manual/basic_concepts/creation.rb +39 -0
  87. data/manual/basic_concepts/cursor.rb +33 -0
  88. data/manual/basic_concepts/measurement.rb +25 -0
  89. data/manual/basic_concepts/origin.rb +38 -0
  90. data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
  91. data/manual/bounding_box/bounding_box.rb +39 -0
  92. data/manual/bounding_box/bounds.rb +49 -0
  93. data/manual/bounding_box/canvas.rb +24 -0
  94. data/manual/bounding_box/creation.rb +23 -0
  95. data/manual/bounding_box/indentation.rb +46 -0
  96. data/manual/bounding_box/nesting.rb +45 -0
  97. data/manual/bounding_box/russian_boxes.rb +40 -0
  98. data/manual/bounding_box/stretchy.rb +31 -0
  99. data/manual/document_and_page_options/background.rb +27 -0
  100. data/manual/document_and_page_options/document_and_page_options.rb +31 -0
  101. data/manual/document_and_page_options/metadata.rb +23 -0
  102. data/manual/document_and_page_options/page_margins.rb +38 -0
  103. data/manual/document_and_page_options/page_size.rb +34 -0
  104. data/manual/example_file.rb +116 -0
  105. data/manual/example_helper.rb +411 -0
  106. data/manual/example_package.rb +53 -0
  107. data/manual/example_section.rb +46 -0
  108. data/manual/graphics/circle_and_ellipse.rb +22 -0
  109. data/manual/graphics/color.rb +24 -0
  110. data/manual/graphics/common_lines.rb +28 -0
  111. data/manual/graphics/fill_and_stroke.rb +42 -0
  112. data/manual/graphics/fill_rules.rb +37 -0
  113. data/manual/graphics/gradients.rb +37 -0
  114. data/manual/graphics/graphics.rb +58 -0
  115. data/manual/graphics/helper.rb +24 -0
  116. data/manual/graphics/line_width.rb +35 -0
  117. data/manual/graphics/lines_and_curves.rb +41 -0
  118. data/manual/graphics/polygon.rb +29 -0
  119. data/manual/graphics/rectangle.rb +21 -0
  120. data/manual/graphics/rotate.rb +28 -0
  121. data/manual/graphics/scale.rb +41 -0
  122. data/manual/graphics/soft_masks.rb +46 -0
  123. data/manual/graphics/stroke_cap.rb +31 -0
  124. data/manual/graphics/stroke_dash.rb +48 -0
  125. data/manual/graphics/stroke_join.rb +30 -0
  126. data/manual/graphics/translate.rb +29 -0
  127. data/manual/graphics/transparency.rb +35 -0
  128. data/manual/images/absolute_position.rb +23 -0
  129. data/manual/images/fit.rb +21 -0
  130. data/manual/images/horizontal.rb +25 -0
  131. data/manual/images/images.rb +40 -0
  132. data/manual/images/plain_image.rb +18 -0
  133. data/manual/images/scale.rb +22 -0
  134. data/manual/images/vertical.rb +28 -0
  135. data/manual/images/width_and_height.rb +25 -0
  136. data/manual/layout/boxes.rb +27 -0
  137. data/manual/layout/content.rb +25 -0
  138. data/manual/layout/layout.rb +28 -0
  139. data/manual/layout/simple_grid.rb +23 -0
  140. data/manual/manual/cover.rb +35 -0
  141. data/manual/manual/foreword.rb +85 -0
  142. data/manual/manual/how_to_read_this_manual.rb +41 -0
  143. data/manual/manual/manual.rb +35 -0
  144. data/manual/outline/add_subsection_to.rb +61 -0
  145. data/manual/outline/insert_section_after.rb +47 -0
  146. data/manual/outline/outline.rb +32 -0
  147. data/manual/outline/sections_and_pages.rb +67 -0
  148. data/manual/repeatable_content/page_numbering.rb +54 -0
  149. data/manual/repeatable_content/repeatable_content.rb +31 -0
  150. data/manual/repeatable_content/repeater.rb +55 -0
  151. data/manual/repeatable_content/stamp.rb +41 -0
  152. data/manual/security/encryption.rb +31 -0
  153. data/manual/security/permissions.rb +38 -0
  154. data/manual/security/security.rb +28 -0
  155. data/manual/syntax_highlight.rb +52 -0
  156. data/manual/table/basic_block.rb +53 -0
  157. data/manual/table/before_rendering_page.rb +26 -0
  158. data/manual/table/cell_border_lines.rb +24 -0
  159. data/manual/table/cell_borders_and_bg.rb +31 -0
  160. data/manual/table/cell_dimensions.rb +30 -0
  161. data/manual/table/cell_text.rb +38 -0
  162. data/manual/table/column_widths.rb +30 -0
  163. data/manual/table/content_and_subtables.rb +39 -0
  164. data/manual/table/creation.rb +27 -0
  165. data/manual/table/filtering.rb +36 -0
  166. data/manual/table/flow_and_header.rb +17 -0
  167. data/manual/table/image_cells.rb +33 -0
  168. data/manual/table/position.rb +29 -0
  169. data/manual/table/row_colors.rb +20 -0
  170. data/manual/table/span.rb +30 -0
  171. data/manual/table/style.rb +22 -0
  172. data/manual/table/table.rb +52 -0
  173. data/manual/table/width.rb +27 -0
  174. data/manual/templates/full_template.rb +25 -0
  175. data/manual/templates/page_template.rb +48 -0
  176. data/manual/templates/templates.rb +27 -0
  177. data/manual/text/alignment.rb +44 -0
  178. data/manual/text/color.rb +24 -0
  179. data/manual/text/column_box.rb +32 -0
  180. data/manual/text/fallback_fonts.rb +37 -0
  181. data/manual/text/font.rb +41 -0
  182. data/manual/text/font_size.rb +45 -0
  183. data/manual/text/font_style.rb +23 -0
  184. data/manual/text/formatted_callbacks.rb +60 -0
  185. data/manual/text/formatted_text.rb +54 -0
  186. data/manual/text/free_flowing_text.rb +51 -0
  187. data/manual/text/group.rb +29 -0
  188. data/manual/text/inline.rb +43 -0
  189. data/manual/text/kerning_and_character_spacing.rb +39 -0
  190. data/manual/text/leading.rb +25 -0
  191. data/manual/text/line_wrapping.rb +41 -0
  192. data/manual/text/paragraph_indentation.rb +26 -0
  193. data/manual/text/positioned_text.rb +38 -0
  194. data/manual/text/registering_families.rb +48 -0
  195. data/manual/text/rendering_and_color.rb +37 -0
  196. data/manual/text/right_to_left_text.rb +43 -0
  197. data/manual/text/rotation.rb +43 -0
  198. data/manual/text/single_usage.rb +37 -0
  199. data/manual/text/text.rb +75 -0
  200. data/manual/text/text_box_excess.rb +32 -0
  201. data/manual/text/text_box_extensions.rb +45 -0
  202. data/manual/text/text_box_overflow.rb +44 -0
  203. data/manual/text/utf8.rb +28 -0
  204. data/{examples/m17n → manual/text}/win_ansi_charset.rb +14 -10
  205. data/prawn.gemspec +18 -12
  206. data/spec/acceptance/png.rb +23 -0
  207. data/spec/annotations_spec.rb +16 -32
  208. data/spec/bounding_box_spec.rb +128 -15
  209. data/spec/cell_spec.rb +169 -38
  210. data/spec/column_box_spec.rb +33 -0
  211. data/spec/destinations_spec.rb +5 -5
  212. data/spec/document_spec.rb +150 -104
  213. data/spec/extensions/encoding_helpers.rb +10 -0
  214. data/spec/extensions/mocha.rb +1 -0
  215. data/spec/filters_spec.rb +34 -0
  216. data/spec/font_metric_cache_spec.rb +52 -0
  217. data/spec/font_spec.rb +183 -97
  218. data/spec/formatted_text_arranger_spec.rb +43 -43
  219. data/spec/formatted_text_box_spec.rb +30 -20
  220. data/spec/formatted_text_fragment_spec.rb +8 -8
  221. data/spec/graphics_spec.rb +158 -69
  222. data/spec/grid_spec.rb +15 -15
  223. data/spec/image_handler_spec.rb +42 -0
  224. data/spec/images_spec.rb +49 -24
  225. data/spec/inline_formatted_text_parser_spec.rb +73 -19
  226. data/spec/jpg_spec.rb +4 -4
  227. data/spec/line_wrap_spec.rb +26 -26
  228. data/spec/measurement_units_spec.rb +6 -6
  229. data/spec/name_tree_spec.rb +21 -21
  230. data/spec/object_store_spec.rb +39 -39
  231. data/spec/outline_spec.rb +93 -53
  232. data/spec/pdf_object_spec.rb +88 -86
  233. data/spec/png_spec.rb +31 -28
  234. data/spec/reference_spec.rb +32 -32
  235. data/spec/repeater_spec.rb +25 -11
  236. data/spec/security_spec.rb +44 -12
  237. data/spec/snapshot_spec.rb +8 -9
  238. data/spec/soft_mask_spec.rb +117 -0
  239. data/spec/span_spec.rb +10 -15
  240. data/spec/spec_helper.rb +25 -8
  241. data/spec/stamp_spec.rb +29 -30
  242. data/spec/stream_spec.rb +58 -0
  243. data/spec/stroke_styles_spec.rb +36 -18
  244. data/spec/table/span_dummy_spec.rb +17 -0
  245. data/spec/table_spec.rb +697 -105
  246. data/spec/template_spec.rb +108 -54
  247. data/spec/text_at_spec.rb +18 -17
  248. data/spec/text_box_spec.rb +111 -62
  249. data/spec/text_rendering_mode_spec.rb +5 -5
  250. data/spec/text_spacing_spec.rb +4 -4
  251. data/spec/text_spec.rb +57 -49
  252. data/spec/transparency_spec.rb +5 -5
  253. metadata +421 -213
  254. data/data/fonts/Action Man.dfont +0 -0
  255. data/data/fonts/Activa.ttf +0 -0
  256. data/data/fonts/Chalkboard.ttf +0 -0
  257. data/data/fonts/DejaVuSans.ttf +0 -0
  258. data/data/fonts/Dustismo_Roman.ttf +0 -0
  259. data/data/fonts/comicsans.ttf +0 -0
  260. data/data/fonts/gkai00mp.ttf +0 -0
  261. data/data/images/rails.dat +0 -0
  262. data/data/images/rails.png +0 -0
  263. data/examples/bounding_box/russian_boxes.rb +0 -37
  264. data/examples/example_helper.rb +0 -11
  265. data/examples/general/context_sensitive_headers.rb +0 -38
  266. data/examples/graphics/cmyk.rb +0 -13
  267. data/examples/graphics/gradient.rb +0 -23
  268. data/examples/graphics/png_types.rb +0 -23
  269. data/examples/graphics/remote_images.rb +0 -13
  270. data/examples/m17n/full_win_ansi_character_list.rb +0 -20
  271. data/examples/m17n/sjis.rb +0 -29
  272. data/examples/table/bill.rb +0 -54
  273. data/examples/table/header.rb +0 -15
  274. data/examples/text/font_calculations.rb +0 -92
  275. data/examples/text/hyphenation.rb +0 -45
  276. data/examples/text/indent_paragraphs.rb +0 -24
  277. data/lib/prawn/core.rb +0 -85
  278. data/lib/prawn/core/text/formatted/arranger.rb +0 -294
  279. data/lib/prawn/core/text/formatted/line_wrap.rb +0 -273
  280. data/lib/prawn/core/text/formatted/wrap.rb +0 -153
  281. data/lib/prawn/graphics/gradient.rb +0 -84
  282. data/lib/prawn/security/arcfour.rb +0 -51
@@ -45,10 +45,19 @@ module Prawn
45
45
  # the appropriate tags if you which to draw attention to the link
46
46
  # <tt>:anchor</tt>::
47
47
  # a destination that has already been or will be registered using
48
- # Prawn::Core::Destinations#add_dest. A clickable link will be
48
+ # PDF::Core::Destinations#add_dest. A clickable link will be
49
49
  # created to that destination. Note that you must explicitly underline
50
50
  # and color using the appropriate tags if you which to draw attention
51
51
  # to the link
52
+ # <tt>:local</tt>::
53
+ # a file or application to be opened locally. A clickable link will be
54
+ # created to the provided local file or application. If the file is
55
+ # another PDF, it will be opened in a new window. Note that you must
56
+ # explicitly underline and color using the appropriate tags if you which
57
+ # to draw attention to the link
58
+ # <tt>:draw_text_callback</tt>:
59
+ # if provided, this Proc will be called instead of #draw_text! once
60
+ # per fragment for every low-level addition of text to the page.
52
61
  # <tt>:callback</tt>::
53
62
  # an object (or array of such objects) with two methods:
54
63
  # #render_behind and #render_in_front, which are called immediately
@@ -89,10 +98,10 @@ module Prawn
89
98
  # vertical space was consumed by the printed text
90
99
  #
91
100
  class Box
92
- include Prawn::Core::Text::Formatted::Wrap
101
+ include Prawn::Text::Formatted::Wrap
93
102
 
94
103
  def valid_options
95
- Prawn::Core::Text::VALID_OPTIONS + [:at, :height, :width,
104
+ PDF::Core::Text::VALID_OPTIONS + [:at, :height, :width,
96
105
  :align, :valign,
97
106
  :rotate, :rotate_around,
98
107
  :overflow, :min_font_size,
@@ -101,7 +110,8 @@ module Prawn
101
110
  :skip_encoding,
102
111
  :document,
103
112
  :direction,
104
- :fallback_fonts]
113
+ :fallback_fonts,
114
+ :draw_text_callback]
105
115
  end
106
116
 
107
117
  # The text that was successfully printed (or, if <tt>dry_run</tt> was
@@ -139,7 +149,7 @@ module Prawn
139
149
  # Example (see Prawn::Text::Core::Formatted::Wrap for what is required
140
150
  # of the wrap method if you want to override the default wrapping
141
151
  # algorithm):
142
- #
152
+ #
143
153
  #
144
154
  # module MyWrap
145
155
  #
@@ -205,6 +215,12 @@ module Prawn
205
215
  @rotate_around = options[:rotate_around] || :upper_left
206
216
  @single_line = options[:single_line]
207
217
  @skip_encoding = options[:skip_encoding] || @document.skip_encoding
218
+ @draw_text_callback = options[:draw_text_callback]
219
+
220
+ # if the text rendering mode is :unknown, force it back to :fill
221
+ if @mode == :unknown
222
+ @mode = :fill
223
+ end
208
224
 
209
225
  if @overflow == :expand
210
226
  # if set to expand, then we simply set the bottom
@@ -273,7 +289,7 @@ module Prawn
273
289
  end
274
290
 
275
291
  # The height actually used during the previous <tt>render</tt>
276
- #
292
+ #
277
293
  def height
278
294
  return 0 if @baseline_y.nil? || @descender.nil?
279
295
  (@baseline_y - @descender).abs
@@ -310,8 +326,13 @@ module Prawn
310
326
  draw_fragment_underlays(fragment)
311
327
 
312
328
  @document.word_spacing(word_spacing) {
313
- @document.draw_text!(fragment.text, :at => [x, y],
314
- :kerning => @kerning)
329
+ if @draw_text_callback
330
+ @draw_text_callback.call(fragment.text, :at => [x, y],
331
+ :kerning => @kerning)
332
+ else
333
+ @document.draw_text!(fragment.text, :at => [x, y],
334
+ :kerning => @kerning)
335
+ end
315
336
  }
316
337
 
317
338
  draw_fragment_overlays(fragment)
@@ -371,8 +392,7 @@ module Prawn
371
392
  # all fonts
372
393
  fallback_fonts << fragment_font
373
394
 
374
- hash[:text].unpack("U*").each do |char_int|
375
- char = [char_int].pack("U")
395
+ hash[:text].unicode_characters do |char|
376
396
  @document.font(fragment_font)
377
397
  font_glyph_pairs << [find_font_for_this_glyph(char,
378
398
  @document.font.family,
@@ -437,6 +457,13 @@ module Prawn
437
457
  end
438
458
 
439
459
  def process_vertical_alignment(text)
460
+ # The vertical alignment must only be done once per text box, but
461
+ # we need to wait until render() is called so that the fonts are set
462
+ # up properly for wrapping. So guard with a boolean to ensure this is
463
+ # only run once.
464
+ return if defined?(@vertical_alignment_processed) && @vertical_alignment_processed
465
+ @vertical_alignment_processed = true
466
+
440
467
  return if @vertical_align == :top
441
468
  wrap(text)
442
469
 
@@ -444,7 +471,7 @@ module Prawn
444
471
  when :center
445
472
  @at[1] = @at[1] - (@height - height) * 0.5
446
473
  when :bottom
447
- @at[1] = @at[1] - (@height - height)
474
+ @at[1] = @at[1] - (@height - height) + @descender
448
475
  end
449
476
  @height = height
450
477
  end
@@ -505,6 +532,7 @@ module Prawn
505
532
  draw_fragment_overlay_styles(fragment)
506
533
  draw_fragment_overlay_link(fragment)
507
534
  draw_fragment_overlay_anchor(fragment)
535
+ draw_fragment_overlay_local(fragment)
508
536
  fragment.callback_objects.each do |obj|
509
537
  obj.render_in_front(fragment) if obj.respond_to?(:render_in_front)
510
538
  end
@@ -517,7 +545,7 @@ module Prawn
517
545
  :Border => [0, 0, 0],
518
546
  :A => { :Type => :Action,
519
547
  :S => :URI,
520
- :URI => Prawn::Core::LiteralString.new(fragment.link) })
548
+ :URI => PDF::Core::LiteralString.new(fragment.link) })
521
549
  end
522
550
 
523
551
  def draw_fragment_overlay_anchor(fragment)
@@ -528,12 +556,23 @@ module Prawn
528
556
  :Dest => fragment.anchor)
529
557
  end
530
558
 
559
+ def draw_fragment_overlay_local(fragment)
560
+ return unless fragment.local
561
+ box = fragment.absolute_bounding_box
562
+ @document.link_annotation(box,
563
+ :Border => [0, 0, 0],
564
+ :A => { :Type => :Action,
565
+ :S => :Launch,
566
+ :F => PDF::Core::LiteralString.new(fragment.local),
567
+ :NewWindow => true })
568
+ end
569
+
531
570
  def draw_fragment_overlay_styles(fragment)
532
571
  underline = fragment.styles.include?(:underline)
533
572
  if underline
534
573
  @document.stroke_line(fragment.underline_points)
535
574
  end
536
-
575
+
537
576
  strikethrough = fragment.styles.include?(:strikethrough)
538
577
  if strikethrough
539
578
  @document.stroke_line(fragment.strikethrough_points)
@@ -15,34 +15,20 @@ module Prawn
15
15
  #
16
16
  class Fragment
17
17
 
18
- attr_reader :format_state
18
+ attr_reader :format_state, :text
19
19
  attr_writer :width
20
20
  attr_accessor :line_height, :descender, :ascender
21
21
  attr_accessor :word_spacing, :left, :baseline
22
22
 
23
23
  def initialize(text, format_state, document)
24
- @text = text
25
24
  @format_state = format_state
26
25
  @document = document
27
26
  @word_spacing = 0
28
- end
29
27
 
30
- def text
31
- string = strip_zero_width_spaces(@text)
32
- if exclude_trailing_white_space?
33
- string = string.rstrip
34
- string = process_soft_hyphens(string)
35
- end
36
- case direction
37
- when :rtl
38
- if ruby_18 { true }
39
- string.scan(/./mu).reverse.join
40
- else
41
- string.reverse
42
- end
43
- else
44
- string
45
- end
28
+ # keep the original value of "text", so we can reinitialize @text if formatting parameters
29
+ # like text direction are changed
30
+ @original_text = text
31
+ @text = process_text(@original_text)
46
32
  end
47
33
 
48
34
  def width
@@ -105,6 +91,10 @@ module Prawn
105
91
  @format_state[:anchor]
106
92
  end
107
93
 
94
+ def local
95
+ @format_state[:local]
96
+ end
97
+
108
98
  def color
109
99
  @format_state[:color]
110
100
  end
@@ -127,16 +117,19 @@ module Prawn
127
117
  end
128
118
 
129
119
  def default_direction=(direction)
130
- @format_state[:direction] = direction unless @format_state[:direction]
120
+ unless @format_state[:direction]
121
+ @format_state[:direction] = direction
122
+ @text = process_text(@original_text)
123
+ end
131
124
  end
132
125
 
133
126
  def include_trailing_white_space!
134
127
  @format_state.delete(:exclude_trailing_white_space)
128
+ @text = process_text(@original_text)
135
129
  end
136
130
 
137
131
  def space_count
138
- string = exclude_trailing_white_space? ? @text.rstrip : @text
139
- string.count(" ")
132
+ @text.count(" ")
140
133
  end
141
134
 
142
135
  def callback_objects
@@ -212,6 +205,23 @@ module Prawn
212
205
 
213
206
  private
214
207
 
208
+ def process_text(text)
209
+ string = strip_zero_width_spaces(text)
210
+ if exclude_trailing_white_space?
211
+ string = process_soft_hyphens(string.rstrip)
212
+ end
213
+ case direction
214
+ when :rtl
215
+ if ruby_18 { true }
216
+ string.scan(/./mu).reverse.join
217
+ else
218
+ string.reverse
219
+ end
220
+ else
221
+ string
222
+ end
223
+ end
224
+
215
225
  def exclude_trailing_white_space?
216
226
  @format_state[:exclude_trailing_white_space]
217
227
  end
@@ -222,6 +232,11 @@ module Prawn
222
232
 
223
233
  def process_soft_hyphens(string)
224
234
  if string.length > 0 && normalized_soft_hyphen
235
+ ruby_19 {
236
+ if string.encoding != normalized_soft_hyphen.encoding
237
+ string.force_encoding(normalized_soft_hyphen.encoding)
238
+ end
239
+ }
225
240
  string[0..-2].gsub(normalized_soft_hyphen, "") + string[-1..-1]
226
241
  else
227
242
  string
@@ -0,0 +1,286 @@
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
+ class LineWrap #:nodoc:
16
+
17
+ # The width of the last wrapped line
18
+ #
19
+ def width
20
+ @accumulated_width || 0
21
+ end
22
+
23
+ # The number of spaces in the last wrapped line
24
+ attr_reader :space_count
25
+
26
+ # Whether this line is the last line in the paragraph
27
+ def paragraph_finished?
28
+ @newline_encountered || is_next_string_newline? || @arranger.finished?
29
+ end
30
+
31
+ # Work in conjunction with the PDF::Formatted::Arranger
32
+ # defined in the :arranger option to determine what formatted text
33
+ # will fit within the width defined by the :width option
34
+ #
35
+ def wrap_line(options)
36
+ initialize_line(options)
37
+
38
+ while fragment = @arranger.next_string
39
+ @fragment_output = ""
40
+
41
+ fragment.lstrip! if first_fragment_on_this_line?(fragment)
42
+ next if empty_line?(fragment)
43
+
44
+ unless apply_font_settings_and_add_fragment_to_line(fragment)
45
+ break
46
+ end
47
+ end
48
+ @arranger.finalize_line
49
+ @accumulated_width = @arranger.line_width
50
+ @space_count = @arranger.space_count
51
+ @arranger.line
52
+ end
53
+
54
+ private
55
+
56
+ def first_fragment_on_this_line?(fragment)
57
+ line_empty? && fragment != "\n"
58
+ end
59
+
60
+ def empty_line?(fragment)
61
+ empty = line_empty? && fragment.empty? && is_next_string_newline?
62
+ @arranger.update_last_string("", "", soft_hyphen) if empty
63
+ empty
64
+ end
65
+
66
+ def is_next_string_newline?
67
+ @arranger.preview_next_string == "\n"
68
+ end
69
+
70
+ def apply_font_settings_and_add_fragment_to_line(fragment)
71
+ result = nil
72
+ @arranger.apply_font_settings do
73
+ # if font has changed from Unicode to non-Unicode, or vice versa, the characters used for soft hyphens
74
+ # and zero-width spaces will be different
75
+ set_soft_hyphen_and_zero_width_space
76
+ result = add_fragment_to_line(fragment)
77
+ end
78
+ result
79
+ end
80
+
81
+ # returns true iff all text was printed without running into the end of
82
+ # the line
83
+ #
84
+ def add_fragment_to_line(fragment)
85
+ if fragment == ""
86
+ true
87
+ elsif fragment == "\n"
88
+ @newline_encountered = true
89
+ false
90
+ else
91
+ fragment.scan(scan_pattern).each do |segment|
92
+ if segment == zero_width_space
93
+ segment_width = 0
94
+ else
95
+ segment_width = @document.width_of(segment, :kerning => @kerning)
96
+ end
97
+
98
+ if @accumulated_width + segment_width <= @width
99
+ @accumulated_width += segment_width
100
+ @fragment_output += segment
101
+ else
102
+ end_of_the_line_reached(segment)
103
+ fragment_finished(fragment)
104
+ return false
105
+ end
106
+ end
107
+
108
+ fragment_finished(fragment)
109
+ true
110
+ end
111
+ end
112
+
113
+ # The pattern used to determine chunks of text to place on a given line
114
+ #
115
+ def scan_pattern
116
+ pattern = "[^#{break_chars}]+#{soft_hyphen}|" +
117
+ "[^#{break_chars}]+#{hyphen}+|" +
118
+ "[^#{break_chars}]+|" +
119
+ "[#{whitespace}]+|" +
120
+ "#{hyphen}+[^#{break_chars}]*|" +
121
+ "#{soft_hyphen}"
122
+ new_regexp(pattern)
123
+ end
124
+
125
+ # The pattern used to determine whether any word breaks exist on a
126
+ # current line, which in turn determines whether character level
127
+ # word breaking is needed
128
+ #
129
+ def word_division_scan_pattern
130
+ new_regexp("\\s|[#{zero_width_space}#{soft_hyphen}#{hyphen}]")
131
+ end
132
+
133
+ def break_chars
134
+ "#{whitespace}#{soft_hyphen}#{hyphen}"
135
+ end
136
+
137
+ def whitespace
138
+ " \\t#{zero_width_space}"
139
+ end
140
+
141
+ def hyphen
142
+ "-"
143
+ end
144
+
145
+ def soft_hyphen
146
+ @soft_hyphen
147
+ end
148
+
149
+ def zero_width_space
150
+ @zero_width_space
151
+ end
152
+
153
+ def line_empty?
154
+ @line_empty && @accumulated_width == 0
155
+ end
156
+
157
+ def initialize_line(options)
158
+ @document = options[:document]
159
+ @kerning = options[:kerning]
160
+ @width = options[:width]
161
+
162
+ @accumulated_width = 0
163
+ @line_empty = true
164
+ @line_contains_more_than_one_word = false
165
+
166
+ @arranger = options[:arranger]
167
+ @arranger.initialize_line
168
+
169
+ @newline_encountered = false
170
+ @line_full = false
171
+ end
172
+
173
+ def set_soft_hyphen_and_zero_width_space
174
+ # this is done once per fragment, after the font settings for the fragment are applied --
175
+ # it could actually be skipped if the font hasn't changed
176
+ font = @document.font
177
+ @soft_hyphen = font.normalize_encoding(Prawn::Text::SHY)
178
+ @zero_width_space = font.unicode? ? Prawn::Text::ZWSP : ""
179
+ end
180
+
181
+ def fragment_finished(fragment)
182
+ if fragment == "\n"
183
+ @newline_encountered = true
184
+ @line_empty = false
185
+ else
186
+ update_output_based_on_last_fragment(fragment, soft_hyphen)
187
+ update_line_status_based_on_last_output
188
+ determine_whether_to_pull_preceding_fragment_to_join_this_one(fragment)
189
+ end
190
+ remember_this_fragment_for_backward_looking_ops
191
+ end
192
+
193
+ def update_output_based_on_last_fragment(fragment, normalized_soft_hyphen=nil)
194
+ remaining_text = fragment.slice(@fragment_output.length..fragment.length)
195
+ raise Prawn::Errors::CannotFit if line_finished? && line_empty? &&
196
+ @fragment_output.empty? && !fragment.strip.empty?
197
+ @arranger.update_last_string(@fragment_output, remaining_text, normalized_soft_hyphen)
198
+ end
199
+
200
+ def determine_whether_to_pull_preceding_fragment_to_join_this_one(current_fragment)
201
+ if @fragment_output.empty? &&
202
+ !current_fragment.empty? &&
203
+ @line_contains_more_than_one_word
204
+ unless previous_fragment_ended_with_breakable? ||
205
+ fragment_begins_with_breakable?(current_fragment)
206
+ @fragment_output = @previous_fragment_output_without_last_word
207
+ update_output_based_on_last_fragment(@previous_fragment)
208
+ end
209
+ end
210
+ end
211
+
212
+ def remember_this_fragment_for_backward_looking_ops
213
+ @previous_fragment = @fragment_output.dup
214
+ pf = @previous_fragment
215
+ @previous_fragment_ended_with_breakable = pf =~ /[#{break_chars}]$/
216
+ last_word = pf.slice(/[^#{break_chars}]*$/)
217
+ last_word_length = last_word.nil? ? 0 : last_word.length
218
+ @previous_fragment_output_without_last_word = pf.slice(0, pf.length - last_word_length)
219
+ end
220
+
221
+ def previous_fragment_ended_with_breakable?
222
+ @previous_fragment_ended_with_breakable
223
+ end
224
+
225
+ def fragment_begins_with_breakable?(fragment)
226
+ fragment =~ /^[#{break_chars}]/
227
+ end
228
+
229
+ def line_finished?
230
+ @line_full || paragraph_finished?
231
+ end
232
+
233
+ def update_line_status_based_on_last_output
234
+ @line_contains_more_than_one_word = true if @fragment_output =~ word_division_scan_pattern
235
+ end
236
+
237
+ def end_of_the_line_reached(segment)
238
+ update_line_status_based_on_last_output
239
+ wrap_by_char(segment) unless @line_contains_more_than_one_word
240
+ @line_full = true
241
+ end
242
+
243
+ def wrap_by_char(segment)
244
+ # this conditional is only necessary for Ruby 1.8 compatibility
245
+ # String#unicode_characters is a helper which iterates over UTF-8 characters
246
+ # under Ruby 1.9, it is implemented simply by aliasing #each_char
247
+ font = @document.font
248
+ if font.unicode?
249
+ segment.unicode_characters do |char|
250
+ break unless append_char(char,font)
251
+ end
252
+ else
253
+ segment.each_char do |char|
254
+ break unless append_char(char,font)
255
+ end
256
+ end
257
+ end
258
+
259
+ def append_char(char,font)
260
+ # kerning doesn't make sense in the context of a single character
261
+ char_width = font.compute_width_of(char)
262
+
263
+ if @accumulated_width + char_width <= @width
264
+ @accumulated_width += char_width
265
+ @fragment_output << char
266
+ true
267
+ else
268
+ false
269
+ end
270
+ end
271
+
272
+ def new_regexp(pattern)
273
+ regexp = ruby_19 {
274
+ Regexp.new(pattern)
275
+ }
276
+ regexp = regexp || ruby_18 {
277
+ lang = @document.font.unicode? ? 'U' : 'N'
278
+ Regexp.new(pattern, 0, lang)
279
+ }
280
+ regexp
281
+ end
282
+
283
+ end
284
+ end
285
+ end
286
+ end