prawn 0.11.1.pre → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (204) hide show
  1. data/COPYING +2 -340
  2. data/HACKING +1 -1
  3. data/LICENSE +3 -3
  4. data/Rakefile +17 -6
  5. data/data/encodings/win_ansi.txt +1 -1
  6. data/data/images/prawn.png +0 -0
  7. data/data/pdfs/form.pdf +820 -0
  8. data/data/pdfs/multipage_template.pdf +127 -0
  9. data/examples/bounding_box/bounding_boxes.rb +4 -3
  10. data/examples/bounding_box/indentation.rb +2 -1
  11. data/examples/bounding_box/russian_boxes.rb +3 -2
  12. data/examples/bounding_box/stretched_nesting.rb +2 -1
  13. data/examples/general/background.rb +2 -1
  14. data/examples/general/canvas.rb +2 -1
  15. data/examples/general/context_sensitive_headers.rb +2 -1
  16. data/examples/general/float.rb +2 -1
  17. data/examples/general/margin.rb +2 -1
  18. data/examples/general/measurement_units.rb +2 -1
  19. data/examples/general/metadata-info.rb +2 -1
  20. data/examples/general/multi_page_layout.rb +2 -1
  21. data/examples/general/outlines.rb +2 -1
  22. data/examples/general/page_geometry.rb +2 -1
  23. data/examples/general/page_numbering.rb +27 -2
  24. data/examples/general/page_templates.rb +20 -0
  25. data/examples/general/repeaters.rb +2 -1
  26. data/examples/general/stamp.rb +4 -3
  27. data/examples/general/templates.rb +2 -1
  28. data/examples/graphics/basic_images.rb +2 -1
  29. data/examples/graphics/cmyk.rb +2 -1
  30. data/examples/graphics/curves.rb +4 -3
  31. data/examples/graphics/gradient.rb +23 -0
  32. data/examples/graphics/hexagon.rb +3 -2
  33. data/examples/graphics/image_fit.rb +3 -2
  34. data/examples/graphics/image_flow.rb +2 -1
  35. data/examples/graphics/image_position.rb +3 -2
  36. data/examples/graphics/line.rb +2 -1
  37. data/examples/graphics/png_types.rb +3 -2
  38. data/examples/graphics/polygons.rb +3 -2
  39. data/examples/graphics/remote_images.rb +2 -1
  40. data/examples/graphics/rounded_polygons.rb +2 -1
  41. data/examples/graphics/rounded_rectangle.rb +2 -1
  42. data/examples/graphics/ruport_style_helpers.rb +3 -2
  43. data/examples/graphics/stroke_bounds.rb +2 -1
  44. data/examples/graphics/stroke_cap_and_join.rb +2 -1
  45. data/examples/graphics/stroke_dash.rb +2 -1
  46. data/examples/graphics/transformations.rb +2 -1
  47. data/examples/graphics/transparency.rb +4 -3
  48. data/examples/grid/bounding_boxes.rb +2 -1
  49. data/examples/grid/column_gutter_grid.rb +2 -1
  50. data/examples/grid/multi_boxes.rb +2 -1
  51. data/examples/grid/show_grid.rb +2 -1
  52. data/examples/grid/simple_grid.rb +2 -1
  53. data/examples/m17n/chinese_text_wrapping.rb +2 -1
  54. data/examples/m17n/euro.rb +3 -2
  55. data/examples/m17n/full_win_ansi_character_list.rb +20 -0
  56. data/examples/m17n/sjis.rb +2 -1
  57. data/examples/m17n/utf8.rb +3 -2
  58. data/examples/m17n/win_ansi_charset.rb +2 -1
  59. data/examples/security/hello_foo.rb +2 -1
  60. data/examples/table/bill.rb +2 -1
  61. data/examples/table/borders.rb +25 -0
  62. data/examples/table/cell.rb +3 -2
  63. data/examples/table/checkerboard.rb +2 -1
  64. data/examples/table/header.rb +3 -2
  65. data/examples/table/inline_format_table.rb +2 -1
  66. data/examples/table/multi_page_table.rb +2 -1
  67. data/examples/table/simple_table.rb +2 -1
  68. data/examples/table/subtable.rb +2 -1
  69. data/examples/table/widths.rb +2 -1
  70. data/examples/text/alignment.rb +2 -1
  71. data/examples/text/character_spacing.rb +2 -1
  72. data/examples/text/dfont.rb +2 -1
  73. data/examples/text/family_based_styling.rb +3 -2
  74. data/examples/text/font_calculations.rb +2 -1
  75. data/examples/text/font_size.rb +2 -1
  76. data/examples/text/hyphenation.rb +2 -2
  77. data/examples/text/indent_paragraphs.rb +7 -5
  78. data/examples/text/inline_format.rb +7 -6
  79. data/examples/text/kerning.rb +2 -1
  80. data/examples/text/rendering_mode.rb +21 -0
  81. data/examples/text/rotated.rb +2 -1
  82. data/examples/text/shaped_text_box.rb +2 -1
  83. data/examples/text/simple_text.rb +2 -1
  84. data/examples/text/simple_text_ttf.rb +2 -1
  85. data/examples/text/span.rb +3 -2
  86. data/examples/text/text_box.rb +7 -5
  87. data/examples/text/text_box_returning_excess.rb +4 -3
  88. data/examples/text/text_flow.rb +2 -1
  89. data/lib/prawn.rb +1 -1
  90. data/lib/prawn/core/object_store.rb +42 -14
  91. data/lib/prawn/core/page.rb +22 -8
  92. data/lib/prawn/core/text.rb +141 -13
  93. data/lib/prawn/core/text/formatted/arranger.rb +39 -12
  94. data/lib/prawn/core/text/formatted/line_wrap.rb +205 -60
  95. data/lib/prawn/core/text/formatted/wrap.rb +72 -35
  96. data/lib/prawn/document.rb +174 -70
  97. data/lib/prawn/document/bounding_box.rb +122 -83
  98. data/lib/prawn/document/column_box.rb +113 -0
  99. data/lib/prawn/document/graphics_state.rb +90 -2
  100. data/lib/prawn/document/internals.rb +5 -3
  101. data/lib/prawn/errors.rb +5 -0
  102. data/lib/prawn/font.rb +4 -4
  103. data/lib/prawn/font/afm.rb +11 -0
  104. data/lib/prawn/font/ttf.rb +5 -0
  105. data/lib/prawn/graphics.rb +77 -14
  106. data/lib/prawn/graphics/cap_style.rb +13 -5
  107. data/lib/prawn/graphics/color.rb +54 -35
  108. data/lib/prawn/graphics/dash.rb +27 -16
  109. data/lib/prawn/graphics/gradient.rb +84 -0
  110. data/lib/prawn/graphics/join_style.rb +12 -3
  111. data/lib/prawn/graphics/transparency.rb +4 -4
  112. data/lib/prawn/images.rb +18 -160
  113. data/lib/prawn/images/jpg.rb +39 -0
  114. data/lib/prawn/images/png.rb +130 -0
  115. data/lib/prawn/repeater.rb +6 -13
  116. data/lib/prawn/security.rb +6 -1
  117. data/lib/prawn/stamp.rb +12 -4
  118. data/lib/prawn/table.rb +36 -4
  119. data/lib/prawn/table/cell.rb +224 -63
  120. data/lib/prawn/table/cell/text.rb +20 -10
  121. data/lib/prawn/table/cells.rb +23 -6
  122. data/lib/prawn/text.rb +54 -91
  123. data/lib/prawn/text/box.rb +29 -283
  124. data/lib/prawn/text/formatted/box.rb +349 -24
  125. data/lib/prawn/text/formatted/fragment.rb +63 -2
  126. data/lib/prawn/text/formatted/parser.rb +2 -1
  127. data/prawn.gemspec +21 -5
  128. data/spec/bounding_box_spec.rb +61 -28
  129. data/spec/cell_spec.rb +168 -30
  130. data/spec/document_spec.rb +187 -3
  131. data/spec/extensions/mocha.rb +45 -0
  132. data/spec/font_spec.rb +32 -1
  133. data/spec/formatted_text_arranger_spec.rb +35 -40
  134. data/spec/formatted_text_box_spec.rb +287 -443
  135. data/spec/formatted_text_fragment_spec.rb +87 -0
  136. data/spec/graphics_spec.rb +128 -12
  137. data/spec/grid_spec.rb +1 -1
  138. data/spec/images_spec.rb +11 -3
  139. data/spec/inline_formatted_text_parser_spec.rb +8 -0
  140. data/spec/line_wrap_spec.rb +200 -208
  141. data/spec/object_store_spec.rb +10 -0
  142. data/spec/outline_spec.rb +7 -3
  143. data/spec/repeater_spec.rb +58 -10
  144. data/spec/security_spec.rb +6 -0
  145. data/spec/spec_helper.rb +12 -8
  146. data/spec/stamp_spec.rb +52 -1
  147. data/spec/stroke_styles_spec.rb +30 -0
  148. data/spec/table_spec.rb +93 -3
  149. data/spec/template_spec.rb +132 -6
  150. data/spec/text_at_spec.rb +14 -4
  151. data/spec/text_box_spec.rb +309 -70
  152. data/spec/text_rendering_mode_spec.rb +45 -0
  153. data/spec/text_spec.rb +60 -17
  154. data/spec/text_with_inline_formatting_spec.rb +4 -162
  155. metadata +241 -241
  156. data/lib/prawn/core/text/line_wrap.rb +0 -211
  157. data/lib/prawn/core/text/wrap.rb +0 -82
  158. data/vendor/pdf-inspector/README +0 -18
  159. data/vendor/pdf-inspector/lib/pdf/inspector.rb +0 -26
  160. data/vendor/pdf-inspector/lib/pdf/inspector/extgstate.rb +0 -18
  161. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +0 -131
  162. data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +0 -25
  163. data/vendor/pdf-inspector/lib/pdf/inspector/text.rb +0 -46
  164. data/vendor/pdf-inspector/lib/pdf/inspector/xobject.rb +0 -19
  165. data/vendor/ttfunk/data/fonts/DejaVuSans.ttf +0 -0
  166. data/vendor/ttfunk/data/fonts/comicsans.ttf +0 -0
  167. data/vendor/ttfunk/example.rb +0 -45
  168. data/vendor/ttfunk/lib/ttfunk.rb +0 -102
  169. data/vendor/ttfunk/lib/ttfunk/directory.rb +0 -17
  170. data/vendor/ttfunk/lib/ttfunk/encoding/mac_roman.rb +0 -88
  171. data/vendor/ttfunk/lib/ttfunk/encoding/windows_1252.rb +0 -69
  172. data/vendor/ttfunk/lib/ttfunk/reader.rb +0 -44
  173. data/vendor/ttfunk/lib/ttfunk/resource_file.rb +0 -78
  174. data/vendor/ttfunk/lib/ttfunk/subset.rb +0 -18
  175. data/vendor/ttfunk/lib/ttfunk/subset/base.rb +0 -141
  176. data/vendor/ttfunk/lib/ttfunk/subset/mac_roman.rb +0 -50
  177. data/vendor/ttfunk/lib/ttfunk/subset/unicode.rb +0 -48
  178. data/vendor/ttfunk/lib/ttfunk/subset/unicode_8bit.rb +0 -63
  179. data/vendor/ttfunk/lib/ttfunk/subset/windows_1252.rb +0 -55
  180. data/vendor/ttfunk/lib/ttfunk/subset_collection.rb +0 -72
  181. data/vendor/ttfunk/lib/ttfunk/table.rb +0 -46
  182. data/vendor/ttfunk/lib/ttfunk/table/cmap.rb +0 -34
  183. data/vendor/ttfunk/lib/ttfunk/table/cmap/format00.rb +0 -54
  184. data/vendor/ttfunk/lib/ttfunk/table/cmap/format04.rb +0 -126
  185. data/vendor/ttfunk/lib/ttfunk/table/cmap/subtable.rb +0 -79
  186. data/vendor/ttfunk/lib/ttfunk/table/glyf.rb +0 -64
  187. data/vendor/ttfunk/lib/ttfunk/table/glyf/compound.rb +0 -81
  188. data/vendor/ttfunk/lib/ttfunk/table/glyf/simple.rb +0 -37
  189. data/vendor/ttfunk/lib/ttfunk/table/head.rb +0 -44
  190. data/vendor/ttfunk/lib/ttfunk/table/hhea.rb +0 -41
  191. data/vendor/ttfunk/lib/ttfunk/table/hmtx.rb +0 -47
  192. data/vendor/ttfunk/lib/ttfunk/table/kern.rb +0 -79
  193. data/vendor/ttfunk/lib/ttfunk/table/kern/format0.rb +0 -62
  194. data/vendor/ttfunk/lib/ttfunk/table/loca.rb +0 -43
  195. data/vendor/ttfunk/lib/ttfunk/table/maxp.rb +0 -40
  196. data/vendor/ttfunk/lib/ttfunk/table/name.rb +0 -125
  197. data/vendor/ttfunk/lib/ttfunk/table/os2.rb +0 -78
  198. data/vendor/ttfunk/lib/ttfunk/table/post.rb +0 -91
  199. data/vendor/ttfunk/lib/ttfunk/table/post/format10.rb +0 -43
  200. data/vendor/ttfunk/lib/ttfunk/table/post/format20.rb +0 -35
  201. data/vendor/ttfunk/lib/ttfunk/table/post/format25.rb +0 -23
  202. data/vendor/ttfunk/lib/ttfunk/table/post/format30.rb +0 -17
  203. data/vendor/ttfunk/lib/ttfunk/table/post/format40.rb +0 -17
  204. data/vendor/ttfunk/lib/ttfunk/table/simple.rb +0 -14
@@ -66,9 +66,6 @@ module Prawn
66
66
  #
67
67
  # Accepts the same options as Text::Box with the below exceptions
68
68
  #
69
- # <tt>:overflow</tt>::
70
- # does not accept :ellipses
71
- #
72
69
  # == Returns
73
70
  #
74
71
  # Returns a formatted text array representing any text that did not print
@@ -81,10 +78,7 @@ module Prawn
81
78
  # Raises <tt>Prawn::Errrors::CannotFit</tt> if not wide enough to print
82
79
  # any text
83
80
  #
84
- # Raises <tt>NotImplementedError</tt> if <tt>:ellipses</tt> <tt>overflow</tt>
85
- # option included
86
- #
87
- def formatted_text_box(array, options)
81
+ def formatted_text_box(array, options={})
88
82
  Text::Formatted::Box.new(array, options.merge(:document => self)).render
89
83
  end
90
84
 
@@ -94,34 +88,213 @@ module Prawn
94
88
  # calculations prior to placing text on the page, or to determine how much
95
89
  # vertical space was consumed by the printed text
96
90
  #
97
- class Box < Prawn::Text::Box
91
+ class Box
98
92
  include Prawn::Core::Text::Formatted::Wrap
99
93
 
100
- def initialize(array, options={})
101
- super(array, options)
102
- if @overflow == :ellipses
103
- raise NotImplementedError, "ellipses overflow unavailable with" +
104
- "formatted box"
94
+ def valid_options
95
+ Prawn::Core::Text::VALID_OPTIONS + [:at, :height, :width,
96
+ :align, :valign,
97
+ :rotate, :rotate_around,
98
+ :overflow, :min_font_size,
99
+ :leading, :character_spacing,
100
+ :mode, :single_line,
101
+ :skip_encoding,
102
+ :document,
103
+ :direction,
104
+ :fallback_fonts]
105
+ end
106
+
107
+ # The text that was successfully printed (or, if <tt>dry_run</tt> was
108
+ # used, the text that would have been successfully printed)
109
+ attr_reader :text
110
+
111
+ # True iff nothing printed (or, if <tt>dry_run</tt> was
112
+ # used, nothing would have been successfully printed)
113
+ def nothing_printed?
114
+ @nothing_printed
115
+ end
116
+
117
+ # True iff everything printed (or, if <tt>dry_run</tt> was
118
+ # used, everything would have been successfully printed)
119
+ def everything_printed?
120
+ @everything_printed
121
+ end
122
+
123
+ # The upper left corner of the text box
124
+ attr_reader :at
125
+ # The line height of the last line printed
126
+ attr_reader :line_height
127
+ # The height of the ascender of the last line printed
128
+ attr_reader :ascender
129
+ # The height of the descender of the last line printed
130
+ attr_reader :descender
131
+ # The leading used during printing
132
+ attr_reader :leading
133
+
134
+ def line_gap
135
+ line_height - (ascender + descender)
136
+ end
137
+
138
+ #
139
+ # Example (see Prawn::Text::Core::Formatted::Wrap for what is required
140
+ # of the wrap method if you want to override the default wrapping
141
+ # algorithm):
142
+ #
143
+ #
144
+ # module MyWrap
145
+ #
146
+ # def wrap(array)
147
+ # initialize_wrap([{ :text => 'all your base are belong to us' }])
148
+ # @line_wrap.wrap_line(:document => @document,
149
+ # :kerning => @kerning,
150
+ # :width => 10000,
151
+ # :arranger => @arranger)
152
+ # fragment = @arranger.retrieve_fragment
153
+ # format_and_draw_fragment(fragment, 0, @line_wrap.width, 0)
154
+ # []
155
+ # end
156
+ #
157
+ # end
158
+ #
159
+ # Prawn::Text::Formatted::Box.extensions << MyWrap
160
+ #
161
+ # box = Prawn::Text::Formatted::Box.new('hello world')
162
+ # box.render('why can't I print anything other than' +
163
+ # '"all your base are belong to us"?')
164
+ #
165
+ #
166
+ def self.extensions
167
+ @extensions ||= []
168
+ end
169
+
170
+ def self.inherited(base) #:nodoc:
171
+ extensions.each { |e| base.extensions << e }
172
+ end
173
+
174
+ # See Prawn::Text#text_box for valid options
175
+ #
176
+ def initialize(formatted_text, options={})
177
+ @inked = false
178
+ Prawn.verify_options(valid_options, options)
179
+ options = options.dup
180
+
181
+ self.class.extensions.reverse_each { |e| extend e }
182
+
183
+ @overflow = options[:overflow] || :truncate
184
+
185
+ self.original_text = formatted_text
186
+ @text = nil
187
+
188
+ @document = options[:document]
189
+ @direction = options[:direction] || @document.text_direction
190
+ @fallback_fonts = options[:fallback_fonts] ||
191
+ @document.fallback_fonts
192
+ @at = (options[:at] ||
193
+ [@document.bounds.left, @document.bounds.top]).dup
194
+ @width = options[:width] ||
195
+ @document.bounds.right - @at[0]
196
+ @height = options[:height] || default_height
197
+ @align = options[:align] ||
198
+ (@direction == :rtl ? :right : :left)
199
+ @vertical_align = options[:valign] || :top
200
+ @leading = options[:leading] || @document.default_leading
201
+ @character_spacing = options[:character_spacing] ||
202
+ @document.character_spacing
203
+ @mode = options[:mode] || @document.text_rendering_mode
204
+ @rotate = options[:rotate] || 0
205
+ @rotate_around = options[:rotate_around] || :upper_left
206
+ @single_line = options[:single_line]
207
+ @skip_encoding = options[:skip_encoding] || @document.skip_encoding
208
+
209
+ if @overflow == :expand
210
+ # if set to expand, then we simply set the bottom
211
+ # as the bottom of the document bounds, since that
212
+ # is the maximum we should expand to
213
+ @height = default_height
214
+ @overflow = :truncate
215
+ end
216
+ @min_font_size = options[:min_font_size] || 5
217
+ if options[:kerning].nil? then
218
+ options[:kerning] = @document.default_kerning?
105
219
  end
220
+ @options = { :kerning => options[:kerning],
221
+ :size => options[:size],
222
+ :style => options[:style] }
223
+
224
+ super(formatted_text, options)
225
+ end
226
+
227
+ # Render text to the document based on the settings defined in initialize.
228
+ #
229
+ # In order to facilitate look-ahead calculations, <tt>render</tt> accepts
230
+ # a <tt>:dry_run => true</tt> option. If provided, then everything is
231
+ # executed as if rendering, with the exception that nothing is drawn on
232
+ # the page. Useful for look-ahead computations of height, unprinted text,
233
+ # etc.
234
+ #
235
+ # Returns any text that did not print under the current settings
236
+ #
237
+ def render(flags={})
238
+ unprinted_text = []
239
+
240
+ @document.save_font do
241
+ @document.character_spacing(@character_spacing) do
242
+ @document.text_rendering_mode(@mode) do
243
+ process_options
244
+
245
+ if @skip_encoding
246
+ text = original_text
247
+ else
248
+ text = normalize_encoding
249
+ end
250
+
251
+ @document.font_size(@font_size) do
252
+ shrink_to_fit(text) if @overflow == :shrink_to_fit
253
+ process_vertical_alignment(text)
254
+ @inked = true unless flags[:dry_run]
255
+ if @rotate != 0 && @inked
256
+ unprinted_text = render_rotated(text)
257
+ else
258
+ unprinted_text = wrap(text)
259
+ end
260
+ @inked = false
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ unprinted_text
267
+ end
268
+
269
+ # The width available at this point in the box
270
+ #
271
+ def available_width
272
+ @width
106
273
  end
107
274
 
108
275
  # The height actually used during the previous <tt>render</tt>
109
276
  #
110
277
  def height
111
278
  return 0 if @baseline_y.nil? || @descender.nil?
112
- @baseline_y.abs + @line_height - @ascender
279
+ (@baseline_y - @descender).abs
113
280
  end
114
281
 
115
282
  # <tt>fragment</tt> is a Prawn::Text::Formatted::Fragment object
116
283
  #
117
284
  def draw_fragment(fragment, accumulated_width=0, line_width=0, word_spacing=0) #:nodoc:
118
285
  case(@align)
119
- when :left, :justify
286
+ when :left
120
287
  x = @at[0]
121
288
  when :center
122
289
  x = @at[0] + @width * 0.5 - line_width * 0.5
123
290
  when :right
124
291
  x = @at[0] + @width - line_width
292
+ when :justify
293
+ if @direction == :ltr
294
+ x = @at[0]
295
+ else
296
+ x = @at[0] + @width - line_width
297
+ end
125
298
  end
126
299
 
127
300
  x += accumulated_width
@@ -133,13 +306,14 @@ module Prawn
133
306
  fragment.left = x
134
307
  fragment.baseline = y
135
308
 
136
- draw_fragment_underlays(fragment)
137
-
138
309
  if @inked
310
+ draw_fragment_underlays(fragment)
311
+
139
312
  @document.word_spacing(word_spacing) {
140
313
  @document.draw_text!(fragment.text, :at => [x, y],
141
314
  :kerning => @kerning)
142
315
  }
316
+
143
317
  draw_fragment_overlays(fragment)
144
318
  end
145
319
  end
@@ -150,16 +324,95 @@ module Prawn
150
324
  @original_array.collect { |hash| hash.dup }
151
325
  end
152
326
 
153
- def original_text=(array)
154
- @original_array = array
327
+ def original_text=(formatted_text)
328
+ @original_array = formatted_text
155
329
  end
156
330
 
157
331
  def normalize_encoding
158
- array = original_text
159
- array.each do |hash|
160
- hash[:text] = @document.font.normalize_encoding(hash[:text])
332
+ formatted_text = original_text
333
+
334
+ unless @fallback_fonts.empty?
335
+ formatted_text = process_fallback_fonts(formatted_text)
161
336
  end
162
- array
337
+
338
+ formatted_text.each do |hash|
339
+ if hash[:font]
340
+ @document.font(hash[:font]) do
341
+ hash[:text] = @document.font.normalize_encoding(hash[:text])
342
+ end
343
+ else
344
+ hash[:text] = @document.font.normalize_encoding(hash[:text])
345
+ end
346
+ end
347
+
348
+ formatted_text
349
+ end
350
+
351
+ def process_fallback_fonts(formatted_text)
352
+ modified_formatted_text = []
353
+
354
+ formatted_text.each do |hash|
355
+ fragments = analyze_glyphs_for_fallback_font_support(hash)
356
+ modified_formatted_text.concat(fragments)
357
+ end
358
+
359
+ modified_formatted_text
360
+ end
361
+
362
+ def analyze_glyphs_for_fallback_font_support(hash)
363
+ font_glyph_pairs = []
364
+
365
+ original_font = @document.font.family
366
+ fragment_font = hash[:font] || original_font
367
+ @document.font(fragment_font)
368
+
369
+ fallback_fonts = @fallback_fonts.dup
370
+ # always default back to the current font if the glyph is missing from
371
+ # all fonts
372
+ fallback_fonts << fragment_font
373
+
374
+ hash[:text].unpack("U*").each do |char_int|
375
+ char = [char_int].pack("U")
376
+ @document.font(fragment_font)
377
+ font_glyph_pairs << [find_font_for_this_glyph(char,
378
+ @document.font.family,
379
+ fallback_fonts.dup),
380
+ char]
381
+ end
382
+
383
+ @document.font(original_font)
384
+
385
+ form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
386
+ end
387
+
388
+ def find_font_for_this_glyph(char, current_font, fallback_fonts)
389
+ if fallback_fonts.length == 0 || @document.font.glyph_present?(char)
390
+ current_font
391
+ else
392
+ current_font = fallback_fonts.shift
393
+ @document.font(current_font)
394
+ find_font_for_this_glyph(char, @document.font.family, fallback_fonts)
395
+ end
396
+ end
397
+
398
+ def form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
399
+ fragments = []
400
+ fragment = nil
401
+ current_font = nil
402
+
403
+ font_glyph_pairs.each do |font, char|
404
+ if font != current_font
405
+ current_font = font
406
+ fragment = hash.dup
407
+ fragment[:text] = char
408
+ fragment[:font] = font
409
+ fragments << fragment
410
+ else
411
+ fragment[:text] += char
412
+ end
413
+ end
414
+
415
+ fragments
163
416
  end
164
417
 
165
418
  def move_baseline_down
@@ -170,6 +423,78 @@ module Prawn
170
423
  end
171
424
  end
172
425
 
426
+ # Returns the default height to be used if none is provided or if the
427
+ # overflow option is set to :expand. If we are in a stretchy bounding
428
+ # box, assume we can stretch to the bottom of the innermost non-stretchy
429
+ # box.
430
+ #
431
+ def default_height
432
+ # Find the "frame", the innermost non-stretchy bbox.
433
+ frame = @document.bounds
434
+ frame = frame.parent while frame.stretchy? && frame.parent
435
+
436
+ @at[1] + @document.bounds.absolute_bottom - frame.absolute_bottom
437
+ end
438
+
439
+ def process_vertical_alignment(text)
440
+ return if @vertical_align == :top
441
+ wrap(text)
442
+
443
+ case @vertical_align
444
+ when :center
445
+ @at[1] = @at[1] - (@height - height) * 0.5
446
+ when :bottom
447
+ @at[1] = @at[1] - (@height - height)
448
+ end
449
+ @height = height
450
+ end
451
+
452
+ # Decrease the font size until the text fits or the min font
453
+ # size is reached
454
+ def shrink_to_fit(text)
455
+ wrap(text)
456
+ until @everything_printed || @font_size <= @min_font_size
457
+ @font_size = [@font_size - 0.5, @min_font_size].max
458
+ @document.font_size = @font_size
459
+ wrap(text)
460
+ end
461
+ end
462
+
463
+ def process_options
464
+ # must be performed within a save_font bock because
465
+ # document.process_text_options sets the font
466
+ @document.process_text_options(@options)
467
+ @font_size = @options[:size]
468
+ @kerning = @options[:kerning]
469
+ end
470
+
471
+ def render_rotated(text)
472
+ unprinted_text = ''
473
+
474
+ case @rotate_around
475
+ when :center
476
+ x = @at[0] + @width * 0.5
477
+ y = @at[1] - @height * 0.5
478
+ when :upper_right
479
+ x = @at[0] + @width
480
+ y = @at[1]
481
+ when :lower_right
482
+ x = @at[0] + @width
483
+ y = @at[1] - @height
484
+ when :lower_left
485
+ x = @at[0]
486
+ y = @at[1] - @height
487
+ else
488
+ x = @at[0]
489
+ y = @at[1]
490
+ end
491
+
492
+ @document.rotate(@rotate, :origin => [x, y]) do
493
+ unprinted_text = wrap(text)
494
+ end
495
+ unprinted_text
496
+ end
497
+
173
498
  def draw_fragment_underlays(fragment)
174
499
  fragment.callback_objects.each do |obj|
175
500
  obj.render_behind(fragment) if obj.respond_to?(:render_behind)
@@ -192,7 +517,7 @@ module Prawn
192
517
  :Border => [0, 0, 0],
193
518
  :A => { :Type => :Action,
194
519
  :S => :URI,
195
- :URI => Prawn::Core::LiteralString.new(fragment.link) })
520
+ :URI => Prawn::Core::LiteralString.new(fragment.link) })
196
521
  end
197
522
 
198
523
  def draw_fragment_overlay_anchor(fragment)
@@ -15,7 +15,7 @@ module Prawn
15
15
  #
16
16
  class Fragment
17
17
 
18
- attr_reader :text, :format_state
18
+ attr_reader :format_state
19
19
  attr_writer :width
20
20
  attr_accessor :line_height, :descender, :ascender
21
21
  attr_accessor :word_spacing, :left, :baseline
@@ -27,9 +27,27 @@ module Prawn
27
27
  @word_spacing = 0
28
28
  end
29
29
 
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
46
+ end
47
+
30
48
  def width
31
49
  if @word_spacing == 0 then @width
32
- else @width + @word_spacing * @text.count(" ")
50
+ else @width + @word_spacing * space_count
33
51
  end
34
52
  end
35
53
 
@@ -104,6 +122,23 @@ module Prawn
104
122
  @document.character_spacing
105
123
  end
106
124
 
125
+ def direction
126
+ @format_state[:direction]
127
+ end
128
+
129
+ def default_direction=(direction)
130
+ @format_state[:direction] = direction unless @format_state[:direction]
131
+ end
132
+
133
+ def include_trailing_white_space!
134
+ @format_state.delete(:exclude_trailing_white_space)
135
+ end
136
+
137
+ def space_count
138
+ string = exclude_trailing_white_space? ? @text.rstrip : @text
139
+ string.count(" ")
140
+ end
141
+
107
142
  def callback_objects
108
143
  callback = @format_state[:callback]
109
144
  if callback.nil?
@@ -175,6 +210,32 @@ module Prawn
175
210
  [absolute_right, absolute_bottom]
176
211
  end
177
212
 
213
+ private
214
+
215
+ def exclude_trailing_white_space?
216
+ @format_state[:exclude_trailing_white_space]
217
+ end
218
+
219
+ def normalized_soft_hyphen
220
+ @format_state[:normalized_soft_hyphen]
221
+ end
222
+
223
+ def process_soft_hyphens(string)
224
+ if string.length > 0 && normalized_soft_hyphen
225
+ string[0..-2].gsub(normalized_soft_hyphen, "") + string[-1..-1]
226
+ else
227
+ string
228
+ end
229
+ end
230
+
231
+ def strip_zero_width_spaces(string)
232
+ if !"".respond_to?(:encoding) || string.encoding.to_s == "UTF-8"
233
+ string.gsub(Prawn::Text::ZWSP, "")
234
+ else
235
+ string
236
+ end
237
+ end
238
+
178
239
  end
179
240
  end
180
241
  end