prawn-core 0.6.3 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Rakefile +1 -1
  2. data/examples/general/context_sensitive_headers.rb +37 -0
  3. data/examples/general/float.rb +11 -0
  4. data/examples/general/repeaters.rb +43 -0
  5. data/examples/m17n/chinese_text_wrapping.rb +1 -3
  6. data/examples/text/font_calculations.rb +6 -6
  7. data/examples/text/text_box.rb +80 -17
  8. data/lib/prawn/core.rb +3 -1
  9. data/lib/prawn/document/bounding_box.rb +9 -0
  10. data/lib/prawn/document/column_box.rb +13 -2
  11. data/lib/prawn/document/internals.rb +21 -3
  12. data/lib/prawn/document/snapshot.rb +7 -2
  13. data/lib/prawn/document/span.rb +3 -3
  14. data/lib/prawn/document.rb +78 -19
  15. data/lib/prawn/font/afm.rb +10 -7
  16. data/lib/prawn/font/ttf.rb +6 -4
  17. data/lib/prawn/font.rb +34 -24
  18. data/lib/prawn/graphics/cap_style.rb +5 -2
  19. data/lib/prawn/graphics/color.rb +117 -57
  20. data/lib/prawn/graphics/dash.rb +4 -2
  21. data/lib/prawn/graphics/join_style.rb +6 -3
  22. data/lib/prawn/graphics/transparency.rb +65 -18
  23. data/lib/prawn/images/jpg.rb +1 -1
  24. data/lib/prawn/images/png.rb +1 -1
  25. data/lib/prawn/object_store.rb +30 -1
  26. data/lib/prawn/reference.rb +25 -3
  27. data/lib/prawn/repeater.rb +117 -0
  28. data/lib/prawn/stamp.rb +102 -40
  29. data/lib/prawn/text/box.rb +344 -0
  30. data/lib/prawn/text.rb +255 -0
  31. data/spec/document_spec.rb +125 -4
  32. data/spec/object_store_spec.rb +33 -0
  33. data/spec/repeater_spec.rb +79 -0
  34. data/spec/stamp_spec.rb +8 -0
  35. data/spec/text_box_spec.rb +282 -69
  36. data/spec/text_spec.rb +49 -29
  37. data/spec/transparency_spec.rb +14 -0
  38. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +2 -2
  39. metadata +158 -155
  40. data/examples/general/measurement_units.pdf +0 -4667
  41. data/lib/prawn/document/text/box.rb +0 -90
  42. data/lib/prawn/document/text/wrapping.rb +0 -62
  43. data/lib/prawn/document/text.rb +0 -184
@@ -11,9 +11,8 @@ require 'zlib'
11
11
  module Prawn
12
12
 
13
13
  class Reference #:nodoc:
14
-
15
- attr_accessor :gen, :data, :offset, :stream
16
- attr_reader :identifier
14
+
15
+ attr_accessor :gen, :data, :offset, :stream, :live, :identifier
17
16
 
18
17
  def initialize(id, data)
19
18
  @identifier = id
@@ -59,6 +58,29 @@ module Prawn
59
58
  @stream = other_ref.stream
60
59
  @compressed = other_ref.compressed?
61
60
  end
61
+
62
+ # Marks this and all referenced objects live, recursively.
63
+ def mark_live
64
+ return if @live
65
+ @live = true
66
+ referenced_objects.each { |o| o.mark_live }
67
+ end
68
+
69
+ private
70
+
71
+ # All objects referenced by this one. Used for GC.
72
+ def referenced_objects(obj=@data)
73
+ case obj
74
+ when Reference
75
+ []
76
+ when Hash
77
+ obj.values.map{|v| [v] + referenced_objects(v) }
78
+ when Array
79
+ obj.map{|v| [v] + referenced_objects(v) }
80
+ else []
81
+ end.flatten.grep(Reference)
82
+ end
83
+
62
84
  end
63
85
 
64
86
  module_function
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+ #
3
+ # repeater.rb : Implements repeated page elements.
4
+ # Heavy inspired by repeating_element() in PDF::Wrapper
5
+ # http://pdf-wrapper.rubyforge.org/
6
+ #
7
+ # Copyright November 2009, Gregory Brown. All Rights Reserved.
8
+ #
9
+ # This is free software. Please see the LICENSE and COPYING files for details.
10
+
11
+ module Prawn
12
+
13
+ class Document
14
+
15
+ # A list of all repeaters in the document.
16
+ # See Document#repeat for details
17
+ #
18
+ def repeaters
19
+ @repeaters ||= []
20
+ end
21
+
22
+ # Provides a way to execute a block of code repeatedly based on a
23
+ # page_filter. Since Stamp is used under the hood, this method is very space
24
+ # efficient.
25
+ #
26
+ # Available page filters are:
27
+ # :all -- repeats on every page
28
+ # :odd -- repeats on odd pages
29
+ # :even -- repeats on even pages
30
+ # some_array -- repeats on every page listed in the array
31
+ # some_range -- repeats on every page included in the range
32
+ # some_lambda -- yields page number and repeats for true return values
33
+ #
34
+ # Example:
35
+ #
36
+ # Prawn::Document.generate("repeat.pdf", :skip_page_creation => true) do
37
+ #
38
+ # repeat :all do
39
+ # text "ALLLLLL", :at => bounds.top_left
40
+ # end
41
+ #
42
+ # repeat :odd do
43
+ # text "ODD", :at => [0,0]
44
+ # end
45
+ #
46
+ # repeat :even do
47
+ # text "EVEN", :at => [0,0]
48
+ # end
49
+ #
50
+ # repeat [1,2] do
51
+ # text "[1,2]", :at => [100,0]
52
+ # end
53
+ #
54
+ # repeat 2..4 do
55
+ # text "2..4", :at => [200,0]
56
+ # end
57
+ #
58
+ # repeat(lambda { |pg| pg % 3 == 0 }) do
59
+ # text "Every third", :at => [250, 20]
60
+ # end
61
+ #
62
+ # 10.times do
63
+ # start_new_page
64
+ # text "A wonderful page", :at => [400,400]
65
+ # end
66
+ #
67
+ # end
68
+ #
69
+ def repeat(page_filter, &block)
70
+ repeaters << Prawn::Repeater.new(self, page_filter, &block)
71
+ end
72
+ end
73
+
74
+ class Repeater #:nodoc:
75
+ class << self
76
+ attr_writer :count
77
+
78
+ def count
79
+ @count ||= 0
80
+ end
81
+ end
82
+
83
+ attr_reader :name
84
+
85
+ def initialize(document, page_filter, &block)
86
+ @document = document
87
+ @page_filter = page_filter
88
+ @stamp_name = "prawn_repeater(#{Repeater.count})"
89
+
90
+ @document.create_stamp(@stamp_name, &block)
91
+
92
+ Repeater.count += 1
93
+ end
94
+
95
+ def match?(page_number)
96
+ case @page_filter
97
+ when :all
98
+ true
99
+ when :odd
100
+ page_number % 2 == 1
101
+ when :even
102
+ page_number % 2 == 0
103
+ when Range, Array
104
+ @page_filter.include?(page_number)
105
+ when Proc
106
+ @page_filter.call(page_number)
107
+ end
108
+ end
109
+
110
+ def run(page_number)
111
+ @document.stamp(@stamp_name) if match?(page_number)
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+
data/lib/prawn/stamp.rb CHANGED
@@ -8,25 +8,58 @@
8
8
  #
9
9
 
10
10
  module Prawn
11
- module Stamp
12
-
13
- def stamp(user_defined_name)
14
- raise Prawn::Errors::InvalidName if user_defined_name.empty?
15
- unless stamp_dictionary_registry[user_defined_name]
16
- raise Prawn::Errors::UndefinedObjectName
17
- end
18
-
19
- dict = stamp_dictionary_registry[user_defined_name]
20
11
 
21
- stamp_dictionary_name = dict[:stamp_dictionary_name]
22
- stamp_dictionary = dict[:stamp_dictionary]
12
+ # The Prawn::Stamp module is used to create content that will be
13
+ # included multiple times in a document. Using a stamp has three
14
+ # advantages over creating content anew each time it is placed on
15
+ # the page:
16
+ # i. faster document creation
17
+ # ii. smaller final document
18
+ # iii. faster display on subsequent displays of the repeated
19
+ # element because the viewer application can cache the rendered
20
+ # results
21
+ #
22
+ # Example:
23
+ # pdf.create_stamp("my_stamp") {
24
+ # pdf.fill_circle_at([10, 15], :radius => 5)
25
+ # pdf.text("hello world", :at => [20, 10])
26
+ # }
27
+ # pdf.stamp("my_stamp")
28
+ #
29
+ module Stamp
23
30
 
24
- add_content "/#{stamp_dictionary_name} Do"
25
-
26
- page_xobjects.merge!(stamp_dictionary_name => stamp_dictionary)
31
+ # Renders the stamp named <tt>name</tt> to the page
32
+ # raises <tt>Prawn::Errors::InvalidName</tt> if name.empty?
33
+ # raises <tt>Prawn::Errors::UndefinedObjectName</tt> if no stamp
34
+ # has been created with this name
35
+ #
36
+ # Example:
37
+ # pdf.create_stamp("my_stamp") {
38
+ # pdf.fill_circle_at([10, 15], :radius => 5)
39
+ # pdf.text("hello world", :at => [20, 10])
40
+ # }
41
+ # pdf.stamp("my_stamp")
42
+ #
43
+ def stamp(name)
44
+ dictionary_name, dictionary = stamp_dictionary(name)
45
+ add_content "/#{dictionary_name} Do"
46
+ page_xobjects.merge!(dictionary_name => dictionary)
27
47
  end
28
-
29
- def stamp_at(user_defined_name, point)
48
+
49
+ # Renders the stamp named <tt>name</tt> at a position offset from
50
+ # the initial coords at which the elements of the stamp was
51
+ # created
52
+ #
53
+ # Example:
54
+ # pdf.create_stamp("circle") do
55
+ # pdf.fill_circle_at([0, 0], :radius => 25)
56
+ # end
57
+ # # draws a circle at 100, 100
58
+ # pdf.stamp_at("circle", [100, 100])
59
+ #
60
+ # See stamp() for exceptions that might be raised
61
+ #
62
+ def stamp_at(name, point)
30
63
  # Save the graphics state
31
64
  add_content "q"
32
65
 
@@ -36,40 +69,36 @@ module Prawn
36
69
  add_content translate_position
37
70
 
38
71
  # Draw the stamp in the now translated user space
39
- stamp(user_defined_name)
72
+ stamp(name)
40
73
 
41
74
  # Restore the graphics state to remove the translation
42
75
  add_content "Q"
43
76
  end
44
-
45
- def create_stamp(user_defined_name="", &block)
46
- raise Prawn::Errors::InvalidName if user_defined_name.empty?
47
-
48
- if stamp_dictionary_registry[user_defined_name]
49
- raise Prawn::Errors::NameTaken
50
- end
51
77
 
52
- # BBox origin is the lower left margin of the page, so we need
53
- # it to be the full dimension of the page, or else things that
54
- # should appear near the top or right margin are invisible
55
- stamp_dictionary = ref!(:Type => :XObject,
56
- :Subtype => :Form,
57
- :BBox => [0, 0, page_dimensions[2], page_dimensions[3]])
78
+ # Creates a re-usable stamp named <tt>name</tt>
79
+ #
80
+ # raises <tt>Prawn::Errors::NameTaken</tt> if a stamp already
81
+ # exists in this document with this name
82
+ # raises <tt>Prawn::Errors::InvalidName</tt> if name.empty?
83
+ #
84
+ # Example:
85
+ # pdf.create_stamp("my_stamp") {
86
+ # pdf.fill_circle_at([10, 15], :radius => 5)
87
+ # pdf.text("hello world", :at => [20, 10])
88
+ # }
89
+ #
90
+ def create_stamp(name, &block)
91
+ dictionary = create_stamp_dictionary(name)
58
92
 
59
- stamp_dictionary_name = "Stamp#{next_stamp_dictionary_id}"
60
-
61
- stamp_dictionary_registry[user_defined_name] =
62
- { :stamp_dictionary_name => stamp_dictionary_name,
63
- :stamp_dictionary => stamp_dictionary}
64
-
65
-
66
93
  @active_stamp_stream = ""
67
- @active_stamp_dictionary = stamp_dictionary
94
+ @active_stamp_dictionary = dictionary
68
95
 
96
+ update_colors
69
97
  yield if block_given?
98
+ update_colors
70
99
 
71
- stamp_dictionary.data[:Length] = @active_stamp_stream.length + 1
72
- stamp_dictionary << @active_stamp_stream
100
+ dictionary.data[:Length] = @active_stamp_stream.length + 1
101
+ dictionary << @active_stamp_stream
73
102
 
74
103
  @active_stamp_stream = nil
75
104
  @active_stamp_dictionary = nil
@@ -85,5 +114,38 @@ module Prawn
85
114
  stamp_dictionary_registry.length + 1
86
115
  end
87
116
 
117
+ def stamp_dictionary(name)
118
+ raise Prawn::Errors::InvalidName if name.empty?
119
+ if stamp_dictionary_registry[name].nil?
120
+ raise Prawn::Errors::UndefinedObjectName
121
+ end
122
+
123
+ dict = stamp_dictionary_registry[name]
124
+
125
+ dictionary_name = dict[:stamp_dictionary_name]
126
+ dictionary = dict[:stamp_dictionary]
127
+ [dictionary_name, dictionary]
128
+ end
129
+
130
+ def create_stamp_dictionary(name)
131
+ raise Prawn::Errors::InvalidName if name.empty?
132
+ raise Prawn::Errors::NameTaken unless stamp_dictionary_registry[name].nil?
133
+ # BBox origin is the lower left margin of the page, so we need
134
+ # it to be the full dimension of the page, or else things that
135
+ # should appear near the top or right margin are invisible
136
+ dictionary = ref!(:Type => :XObject,
137
+ :Subtype => :Form,
138
+ :BBox => [0, 0,
139
+ page_dimensions[2], page_dimensions[3]])
140
+
141
+ dictionary_name = "Stamp#{next_stamp_dictionary_id}"
142
+
143
+ stamp_dictionary_registry[name] = {
144
+ :stamp_dictionary_name => dictionary_name,
145
+ :stamp_dictionary => dictionary
146
+ }
147
+ dictionary
148
+ end
149
+
88
150
  end
89
151
  end
@@ -0,0 +1,344 @@
1
+ # encoding: utf-8
2
+
3
+ # text/rectangle.rb : Implements text boxes
4
+ #
5
+ # Copyright November 2009, 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
+
12
+ # Draws the requested text into a box. When the text overflows
13
+ # the rectangle, you can display ellipses, shrink to fit, or
14
+ # truncate the text. Text boxes are independent of the document
15
+ # y position.
16
+ #
17
+ # == Encoding
18
+ #
19
+ # Note that strings passed to this function should be encoded as UTF-8.
20
+ # If you get unexpected characters appearing in your rendered document,
21
+ # check this.
22
+ #
23
+ # If the current font is a built-in one, although the string must be
24
+ # encoded as UTF-8, only characters that are available in WinAnsi
25
+ # are allowed.
26
+ #
27
+ # If an empty box is rendered to your PDF instead of the character you
28
+ # wanted it usually means the current font doesn't include that character.
29
+ #
30
+ # == Options (default values marked in [])
31
+ #
32
+ # <tt>:kerning</tt>:: <tt>boolean</tt>. Whether or not to use kerning (if it
33
+ # is available with the current font) [true]
34
+ # <tt>:size</tt>:: <tt>number</tt>. The font size to use. [current font
35
+ # size]
36
+ # <tt>:style</tt>:: The style to use. The requested style must be part of
37
+ # the current font familly. [current style]
38
+ #
39
+ # <tt>:at</tt>:: <tt>[x, y]</tt>. The upper left corner of the box
40
+ # [@document.bounds.left, @document.bounds.top]
41
+ # <tt>:width</tt>:: <tt>number</tt>. The width of the box
42
+ # [@document.bounds.right - @at[0]]
43
+ # <tt>:height</tt>:: <tt>number</tt>. The height of the box [@at[1] -
44
+ # @document.bounds.bottom]
45
+ # <tt>:align</tt>:: <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
46
+ # Alignment within the bounding box [:left]
47
+ # <tt>:valign</tt>:: <tt>:top</tt>, <tt>:center</tt>, or <tt>:bottom</tt>.
48
+ # Vertical alignment within the bounding box [:top]
49
+ # <tt>:leading</tt>:: <tt>number</tt>. Additional space between lines [0]
50
+ # <tt>:overflow</tt>:: <tt>:truncate</tt>, <tt>:shrink_to_fit</tt>,
51
+ # <tt>:expand</tt>, or <tt>:ellipses</tt>. This
52
+ # controls the behavior when
53
+ # the amount of text exceeds the available space
54
+ # [:truncate]
55
+ # <tt>:min_font_size</tt>:: <tt>number</tt>. The minimum font size to use
56
+ # when :overflow is set to :shrink_to_fit (that is
57
+ # the font size will not be
58
+ # reduced to less than this value, even if it
59
+ # means that some text will be cut off). [5]
60
+ # <tt>:wrap_block</tt>:: <tt>proc</tt>. A proc used for custom line
61
+ # wrapping. The proc must accept a single
62
+ # <tt>line</tt> of text and an <tt>options</tt> hash
63
+ # and return the string from that single line that
64
+ # can fit on the line under the conditions defined by
65
+ # <tt>options</tt>. If omitted, the default wrapping
66
+ # proc is used. The options hash passed into the
67
+ # wrap_block proc includes the following options:
68
+ # <tt>:width</tt>:: the width available for the
69
+ # current line of text
70
+ # <tt>:document</tt>:: the pdf object
71
+ # <tt>:kerning</tt>:: boolean
72
+ # <tt>:size</tt>:: the font size
73
+ #
74
+ # Returns any text that did not print under the current settings
75
+ #
76
+ def text_box(text, options)
77
+ Text::Box.new(text, options.merge(:document => self)).render
78
+ end
79
+
80
+ # Generally, one would use the text_box convenience method. However, using
81
+ # Text::Box.new in conjunction with render() enables one to do look-ahead
82
+ # calculations prior to placing text on the page, or to determine how much
83
+ # vertical space was consumed by the printed text
84
+ #
85
+ class Box
86
+
87
+ # The text that was successfully printed (or, if <tt>dry_run</tt> was
88
+ # used, the test that would have been successfully printed)
89
+ attr_reader :text
90
+ # The upper left corner of the text box
91
+ attr_reader :at
92
+ # The line height of the last line printed
93
+ attr_reader :line_height
94
+ # The height of the ascender of the last line printed
95
+ attr_reader :ascender
96
+ # The height of the descender of the last line printed
97
+ attr_reader :descender
98
+ # The leading used during printing
99
+ attr_reader :leading
100
+
101
+ # See Prawn::Text#text_box for valid options
102
+ #
103
+ def initialize(text, options={})
104
+ @inked = false
105
+ Prawn.verify_options(valid_options, options)
106
+ options = options.dup
107
+ @overflow = options[:overflow] || :truncate
108
+ # we'll be messing with the strings encoding, don't change the user's
109
+ # original string
110
+ @text_to_print = text.dup
111
+ @text = nil
112
+
113
+ @document = options[:document]
114
+ @at = options[:at] ||
115
+ [@document.bounds.left, @document.bounds.top]
116
+ @width = options[:width] ||
117
+ @document.bounds.right - @at[0]
118
+ @height = options[:height] ||
119
+ @at[1] - @document.bounds.bottom
120
+ @center = [@at[0] + @width * 0.5, @at[1] + @height * 0.5]
121
+ @align = options[:align] || :left
122
+ @vertical_align = options[:valign] || :top
123
+ @leading = options[:leading] || 0
124
+
125
+ if @overflow == :expand
126
+ # if set to expand, then we simply set the bottom
127
+ # as the bottom of the document bounds, since that
128
+ # is the maximum we should expand to
129
+ @height = @at[1] - @document.bounds.bottom
130
+ @overflow = :truncate
131
+ end
132
+ @min_font_size = options[:min_font_size] || 5
133
+ @wrap_block = options [:wrap_block] || default_wrap_block
134
+ @options = @document.text_options.merge(:kerning => options[:kerning],
135
+ :size => options[:size],
136
+ :style => options[:style])
137
+ end
138
+
139
+ # Render text to the document based on the settings defined in initialize.
140
+ #
141
+ # In order to facilitate look-ahead calculations, <tt>render</tt> accepts
142
+ # a <tt>:dry_run => true</tt> option. If provided then everything is
143
+ # executed as if rendering, with the exception that nothing is drawn on
144
+ # the page. Useful for look-ahead computations of height, unprinted text,
145
+ # etc.
146
+ #
147
+ # Returns any text that did not print under the current settings
148
+ #
149
+ def render(flags={})
150
+ unprinted_text = ''
151
+ @document.save_font do
152
+ process_options
153
+
154
+ unless @document.skip_encoding
155
+ @document.font.normalize_encoding!(@text_to_print)
156
+ end
157
+
158
+ @document.font_size(@font_size) do
159
+ shrink_to_fit if @overflow == :shrink_to_fit
160
+ process_vertical_alignment
161
+ @inked = true unless flags[:dry_run]
162
+ unprinted_text = _render(@text_to_print)
163
+ @inked = false
164
+ end
165
+ end
166
+ unprinted_text
167
+ end
168
+
169
+ # The height actually used during the previous <tt>render</tt>
170
+ #
171
+ def height
172
+ return 0 if @baseline_y.nil? || @descender.nil?
173
+ # baseline is already pushed down one line below the current
174
+ # line, so we need to subtract line line_height and leading,
175
+ # but we need to add in the descender since baseline is
176
+ # above the descender
177
+ @baseline_y.abs + @descender - @line_height - @leading
178
+ end
179
+
180
+ private
181
+
182
+ def valid_options
183
+ Text::VALID_TEXT_OPTIONS.dup.concat([:at, :height, :width,
184
+ :align, :valign,
185
+ :overflow, :min_font_size,
186
+ :wrap_block,
187
+ :leading,
188
+ :document])
189
+ end
190
+
191
+ def process_vertical_alignment
192
+ return if @vertical_align == :top
193
+ _render(@text_to_print)
194
+ case @vertical_align
195
+ when :center
196
+ @at[1] = @at[1] - (@height - height) * 0.5
197
+ when :bottom
198
+ @at[1] = @at[1] - (@height - height)
199
+ end
200
+ @height = height
201
+ end
202
+
203
+ # Decrease the font size until the text fits or the min font
204
+ # size is reached
205
+ def shrink_to_fit
206
+ while (unprinted_text = _render(@text_to_print)).length > 0 &&
207
+ @font_size > @min_font_size
208
+ @font_size -= 0.5
209
+ @document.font_size = @font_size
210
+ end
211
+ end
212
+
213
+ def process_options
214
+ # must be performed within a save_font bock because
215
+ # document.process_text_options sets the font
216
+ @document.process_text_options(@options)
217
+ @font_size = @options[:size]
218
+ @kerning = @options[:kerning]
219
+ end
220
+
221
+ def _render(remaining_text)
222
+ @line_height = @document.font.height
223
+ @descender = @document.font.descender
224
+ @ascender = @document.font.ascender
225
+ @baseline_y = -@ascender
226
+
227
+ printed_text = []
228
+
229
+ while remaining_text &&
230
+ remaining_text.length > 0 &&
231
+ @baseline_y.abs + @descender <= @height
232
+ line_to_print = @wrap_block.call(remaining_text.first_line,
233
+ :document => @document,
234
+ :kerning => @kerning,
235
+ :size => @font_size,
236
+ :width => @width)
237
+ remaining_text = remaining_text.slice(line_to_print.length..
238
+ remaining_text.length)
239
+ print_ellipses = (@overflow == :ellipses && last_line? &&
240
+ remaining_text.length > 0)
241
+ printed_text << print_line(line_to_print, print_ellipses)
242
+ @baseline_y -= (@line_height + @leading)
243
+ end
244
+
245
+ @text = printed_text.join("\n") if @inked
246
+
247
+ remaining_text
248
+ end
249
+
250
+ def print_line(line_to_print, print_ellipses)
251
+ # strip so that trailing and preceding white space don't
252
+ # interfere with alignment
253
+ line_to_print.strip!
254
+
255
+ insert_ellipses(line_to_print) if print_ellipses
256
+
257
+ case(@align)
258
+ when :left
259
+ x = @center[0] - @width * 0.5
260
+ when :center
261
+ line_width = @document.width_of(line_to_print, :kerning => @kerning)
262
+ x = @center[0] - line_width * 0.5
263
+ when :right
264
+ line_width = @document.width_of(line_to_print, :kerning => @kerning)
265
+ x = @center[0] + @width * 0.5 - line_width
266
+ end
267
+
268
+ y = @at[1] + @baseline_y
269
+
270
+ if @inked
271
+ @document.text_at(line_to_print, :at => [x, y],
272
+ :size => @font_size, :kerning => @kerning)
273
+ end
274
+
275
+ line_to_print
276
+ end
277
+
278
+ def last_line?
279
+ @baseline_y.abs + @descender > @height - @line_height
280
+ end
281
+
282
+ def insert_ellipses(line_to_print)
283
+ if @document.width_of(line_to_print + "...",
284
+ :kerning => @kerning) < @width
285
+ line_to_print.insert(-1, "...")
286
+ else
287
+ line_to_print[-3..-1] = "..." if line_to_print.length > 3
288
+ end
289
+ end
290
+
291
+ def default_wrap_block
292
+ lambda do |line, options|
293
+ scan_pattern = /\S+|\s+/
294
+ output = ""
295
+ accumulated_width = 0
296
+ line.scan(scan_pattern).each do |segment|
297
+ segment_width = options[:document].width_of(segment,
298
+ :size => options[:size],
299
+ :kerning => options[:kerning])
300
+
301
+ if accumulated_width + segment_width <= options[:width]
302
+ accumulated_width += segment_width
303
+ output << segment
304
+ else
305
+ # if the line contains white space, don't split the
306
+ # final word that doesn't fit, just return what fits nicely
307
+ break if output =~ /\s/
308
+
309
+ # if there is no white space on the curren tline, then just
310
+ # print whatever part of the last segment that will fit on the
311
+ # line
312
+ begin
313
+ segment.unpack("U*").each do |char_int|
314
+ char = [char_int].pack("U")
315
+ accumulated_width += options[:document].width_of(char,
316
+ :size => options[:size],
317
+ :kerning => options[:kerning])
318
+ break if accumulated_width >= options[:width]
319
+ output << char
320
+ end
321
+ rescue
322
+ segment.each_char do |char|
323
+ accumulated_width += options[:document].width_of(char,
324
+ :size => options[:size],
325
+ :kerning => options[:kerning])
326
+ break if accumulated_width >= options[:width]
327
+ output << char
328
+ end
329
+ end
330
+ end
331
+ end
332
+ output
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
338
+
339
+
340
+ class String
341
+ def first_line
342
+ self.each_line { |line| return line }
343
+ end
344
+ end