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.
- checksums.yaml +7 -0
- data/.yardopts +10 -0
- data/COPYING +2 -0
- data/GPLv2 +340 -0
- data/GPLv3 +674 -0
- data/Gemfile +11 -0
- data/LICENSE +56 -0
- data/Rakefile +55 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/data/images/16bit.alpha +0 -0
- data/data/images/16bit.color +0 -0
- data/data/images/16bit.png +0 -0
- data/data/images/arrow.png +0 -0
- data/data/images/arrow2.png +0 -0
- data/data/images/dice.alpha +0 -0
- data/data/images/dice.color +0 -0
- data/data/images/dice.png +0 -0
- data/data/images/dice_interlaced.png +0 -0
- data/data/images/fractal.jpg +0 -0
- data/data/images/indexed_color.dat +0 -0
- data/data/images/indexed_color.png +0 -0
- data/data/images/letterhead.jpg +0 -0
- data/data/images/license.md +8 -0
- data/data/images/page_white_text.alpha +0 -0
- data/data/images/page_white_text.color +0 -0
- data/data/images/page_white_text.png +0 -0
- data/data/images/pal_bk.png +0 -0
- data/data/images/pigs.jpg +0 -0
- data/data/images/prawn.png +0 -0
- data/data/images/ruport.png +0 -0
- data/data/images/ruport_data.dat +0 -0
- data/data/images/ruport_transparent.png +0 -0
- data/data/images/ruport_type0.png +0 -0
- data/data/images/stef.jpg +0 -0
- data/data/images/tru256.bmp +0 -0
- data/data/images/web-links.dat +1 -0
- data/data/images/web-links.png +0 -0
- data/data/pdfs/complex_template.pdf +0 -0
- data/data/pdfs/contains_ttf_font.pdf +0 -0
- data/data/pdfs/encrypted.pdf +0 -0
- data/data/pdfs/form.pdf +820 -0
- data/data/pdfs/hexagon.pdf +61 -0
- data/data/pdfs/indirect_reference.pdf +86 -0
- data/data/pdfs/multipage_template.pdf +127 -0
- data/data/pdfs/nested_pages.pdf +118 -0
- data/data/pdfs/page_without_mediabox.pdf +193 -0
- data/data/pdfs/resources_as_indirect_object.pdf +83 -0
- data/data/pdfs/two_hexagons.pdf +90 -0
- data/data/pdfs/version_1_6.pdf +61 -0
- data/data/shift_jis_text.txt +1 -0
- data/lib/prawn.rb +89 -0
- data/lib/prawn/document.rb +706 -0
- data/lib/prawn/document/bounding_box.rb +539 -0
- data/lib/prawn/document/column_box.rb +144 -0
- data/lib/prawn/document/internals.rb +58 -0
- data/lib/prawn/document/span.rb +57 -0
- data/lib/prawn/encoding.rb +87 -0
- data/lib/prawn/errors.rb +80 -0
- data/lib/prawn/font.rb +413 -0
- data/lib/prawn/font/afm.rb +256 -0
- data/lib/prawn/font/dfont.rb +43 -0
- data/lib/prawn/font/ttf.rb +355 -0
- data/lib/prawn/font_metric_cache.rb +46 -0
- data/lib/prawn/graphics.rb +646 -0
- data/lib/prawn/graphics/cap_style.rb +47 -0
- data/lib/prawn/graphics/color.rb +232 -0
- data/lib/prawn/graphics/dash.rb +109 -0
- data/lib/prawn/graphics/join_style.rb +49 -0
- data/lib/prawn/graphics/patterns.rb +126 -0
- data/lib/prawn/graphics/transformation.rb +157 -0
- data/lib/prawn/graphics/transparency.rb +101 -0
- data/lib/prawn/grid.rb +279 -0
- data/lib/prawn/image_handler.rb +44 -0
- data/lib/prawn/images.rb +199 -0
- data/lib/prawn/images/image.rb +49 -0
- data/lib/prawn/images/jpg.rb +91 -0
- data/lib/prawn/images/png.rb +290 -0
- data/lib/prawn/measurement_extensions.rb +50 -0
- data/lib/prawn/measurements.rb +77 -0
- data/lib/prawn/outline.rb +289 -0
- data/lib/prawn/repeater.rb +124 -0
- data/lib/prawn/security.rb +288 -0
- data/lib/prawn/security/arcfour.rb +54 -0
- data/lib/prawn/soft_mask.rb +94 -0
- data/lib/prawn/stamp.rb +136 -0
- data/lib/prawn/text.rb +437 -0
- data/lib/prawn/text/box.rb +141 -0
- data/lib/prawn/text/formatted.rb +7 -0
- data/lib/prawn/text/formatted/arranger.rb +290 -0
- data/lib/prawn/text/formatted/box.rb +614 -0
- data/lib/prawn/text/formatted/fragment.rb +264 -0
- data/lib/prawn/text/formatted/line_wrap.rb +277 -0
- data/lib/prawn/text/formatted/parser.rb +224 -0
- data/lib/prawn/text/formatted/wrap.rb +160 -0
- data/lib/prawn/utilities.rb +46 -0
- data/lib/prawn/version.rb +5 -0
- data/lib/prawn/view.rb +91 -0
- data/manual/absolute_position.pdf +0 -0
- data/manual/basic_concepts/adding_pages.rb +27 -0
- data/manual/basic_concepts/basic_concepts.rb +36 -0
- data/manual/basic_concepts/creation.rb +39 -0
- data/manual/basic_concepts/cursor.rb +33 -0
- data/manual/basic_concepts/measurement.rb +25 -0
- data/manual/basic_concepts/origin.rb +38 -0
- data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
- data/manual/basic_concepts/view.rb +42 -0
- data/manual/bounding_box/bounding_box.rb +39 -0
- data/manual/bounding_box/bounds.rb +49 -0
- data/manual/bounding_box/canvas.rb +24 -0
- data/manual/bounding_box/creation.rb +23 -0
- data/manual/bounding_box/indentation.rb +46 -0
- data/manual/bounding_box/nesting.rb +45 -0
- data/manual/bounding_box/russian_boxes.rb +40 -0
- data/manual/bounding_box/stretchy.rb +31 -0
- data/manual/contents.rb +29 -0
- data/manual/cover.rb +39 -0
- data/manual/document_and_page_options/background.rb +27 -0
- data/manual/document_and_page_options/document_and_page_options.rb +32 -0
- data/manual/document_and_page_options/metadata.rb +23 -0
- data/manual/document_and_page_options/page_margins.rb +38 -0
- data/manual/document_and_page_options/page_size.rb +34 -0
- data/manual/document_and_page_options/print_scaling.rb +20 -0
- data/manual/example_helper.rb +7 -0
- data/manual/graphics/circle_and_ellipse.rb +22 -0
- data/manual/graphics/color.rb +24 -0
- data/manual/graphics/common_lines.rb +30 -0
- data/manual/graphics/fill_and_stroke.rb +42 -0
- data/manual/graphics/fill_rules.rb +37 -0
- data/manual/graphics/gradients.rb +37 -0
- data/manual/graphics/graphics.rb +58 -0
- data/manual/graphics/helper.rb +24 -0
- data/manual/graphics/line_width.rb +35 -0
- data/manual/graphics/lines_and_curves.rb +41 -0
- data/manual/graphics/polygon.rb +29 -0
- data/manual/graphics/rectangle.rb +21 -0
- data/manual/graphics/rotate.rb +28 -0
- data/manual/graphics/scale.rb +41 -0
- data/manual/graphics/soft_masks.rb +46 -0
- data/manual/graphics/stroke_cap.rb +31 -0
- data/manual/graphics/stroke_dash.rb +48 -0
- data/manual/graphics/stroke_join.rb +30 -0
- data/manual/graphics/translate.rb +29 -0
- data/manual/graphics/transparency.rb +35 -0
- data/manual/how_to_read_this_manual.rb +40 -0
- data/manual/images/absolute_position.rb +23 -0
- data/manual/images/fit.rb +21 -0
- data/manual/images/horizontal.rb +25 -0
- data/manual/images/images.rb +40 -0
- data/manual/images/plain_image.rb +18 -0
- data/manual/images/scale.rb +22 -0
- data/manual/images/vertical.rb +28 -0
- data/manual/images/width_and_height.rb +25 -0
- data/manual/layout/boxes.rb +27 -0
- data/manual/layout/content.rb +25 -0
- data/manual/layout/layout.rb +28 -0
- data/manual/layout/simple_grid.rb +23 -0
- data/manual/outline/add_subsection_to.rb +61 -0
- data/manual/outline/insert_section_after.rb +47 -0
- data/manual/outline/outline.rb +32 -0
- data/manual/outline/sections_and_pages.rb +67 -0
- data/manual/repeatable_content/alternate_page_numbering.rb +32 -0
- data/manual/repeatable_content/page_numbering.rb +54 -0
- data/manual/repeatable_content/repeatable_content.rb +32 -0
- data/manual/repeatable_content/repeater.rb +55 -0
- data/manual/repeatable_content/stamp.rb +41 -0
- data/manual/security/encryption.rb +31 -0
- data/manual/security/permissions.rb +38 -0
- data/manual/security/security.rb +28 -0
- data/manual/table.rb +16 -0
- data/manual/text/alignment.rb +44 -0
- data/manual/text/color.rb +24 -0
- data/manual/text/column_box.rb +32 -0
- data/manual/text/fallback_fonts.rb +37 -0
- data/manual/text/font.rb +41 -0
- data/manual/text/font_size.rb +45 -0
- data/manual/text/font_style.rb +23 -0
- data/manual/text/formatted_callbacks.rb +60 -0
- data/manual/text/formatted_text.rb +50 -0
- data/manual/text/free_flowing_text.rb +51 -0
- data/manual/text/inline.rb +41 -0
- data/manual/text/kerning_and_character_spacing.rb +39 -0
- data/manual/text/leading.rb +25 -0
- data/manual/text/line_wrapping.rb +41 -0
- data/manual/text/paragraph_indentation.rb +34 -0
- data/manual/text/positioned_text.rb +38 -0
- data/manual/text/registering_families.rb +48 -0
- data/manual/text/rendering_and_color.rb +37 -0
- data/manual/text/right_to_left_text.rb +47 -0
- data/manual/text/rotation.rb +43 -0
- data/manual/text/single_usage.rb +37 -0
- data/manual/text/text.rb +73 -0
- data/manual/text/text_box_excess.rb +32 -0
- data/manual/text/text_box_extensions.rb +45 -0
- data/manual/text/text_box_overflow.rb +48 -0
- data/manual/text/utf8.rb +28 -0
- data/manual/text/win_ansi_charset.rb +60 -0
- data/prawn.gemspec +45 -0
- data/spec/acceptance/png.rb +25 -0
- data/spec/annotations_spec.rb +74 -0
- data/spec/bounding_box_spec.rb +510 -0
- data/spec/column_box_spec.rb +65 -0
- data/spec/data/curves.pdf +66 -0
- data/spec/destinations_spec.rb +15 -0
- data/spec/document_spec.rb +748 -0
- data/spec/extensions/encoding_helpers.rb +11 -0
- data/spec/extensions/mocha.rb +46 -0
- data/spec/font_metric_cache_spec.rb +52 -0
- data/spec/font_spec.rb +474 -0
- data/spec/formatted_text_arranger_spec.rb +421 -0
- data/spec/formatted_text_box_spec.rb +705 -0
- data/spec/formatted_text_fragment_spec.rb +298 -0
- data/spec/graphics_spec.rb +683 -0
- data/spec/grid_spec.rb +96 -0
- data/spec/image_handler_spec.rb +54 -0
- data/spec/images_spec.rb +153 -0
- data/spec/inline_formatted_text_parser_spec.rb +564 -0
- data/spec/jpg_spec.rb +25 -0
- data/spec/line_wrap_spec.rb +367 -0
- data/spec/measurement_units_spec.rb +25 -0
- data/spec/outline_spec.rb +430 -0
- data/spec/png_spec.rb +245 -0
- data/spec/reference_spec.rb +25 -0
- data/spec/repeater_spec.rb +160 -0
- data/spec/security_spec.rb +158 -0
- data/spec/soft_mask_spec.rb +79 -0
- data/spec/span_spec.rb +44 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/stamp_spec.rb +160 -0
- data/spec/stroke_styles_spec.rb +211 -0
- data/spec/text_at_spec.rb +143 -0
- data/spec/text_box_spec.rb +1043 -0
- data/spec/text_rendering_mode_spec.rb +45 -0
- data/spec/text_spacing_spec.rb +93 -0
- data/spec/text_spec.rb +557 -0
- data/spec/text_with_inline_formatting_spec.rb +35 -0
- data/spec/transparency_spec.rb +91 -0
- data/spec/view_spec.rb +43 -0
- metadata +509 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# core/text/formatted/arranger.rb : Implements a data structure for 2-stage
|
4
|
+
# processing of lines of 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
|
+
module Prawn
|
11
|
+
module Text
|
12
|
+
module Formatted #:nodoc:
|
13
|
+
|
14
|
+
# @private
|
15
|
+
|
16
|
+
class Arranger #:nodoc:
|
17
|
+
attr_reader :max_line_height
|
18
|
+
attr_reader :max_descender
|
19
|
+
attr_reader :max_ascender
|
20
|
+
attr_accessor :consumed
|
21
|
+
|
22
|
+
# The following present only for testing purposes
|
23
|
+
attr_reader :unconsumed
|
24
|
+
attr_reader :fragments
|
25
|
+
attr_reader :current_format_state
|
26
|
+
|
27
|
+
def initialize(document, options={})
|
28
|
+
@document = document
|
29
|
+
@fragments = []
|
30
|
+
@unconsumed = []
|
31
|
+
@kerning = options[:kerning]
|
32
|
+
end
|
33
|
+
|
34
|
+
def space_count
|
35
|
+
if @unfinalized_line
|
36
|
+
raise "Lines must be finalized before calling #space_count"
|
37
|
+
end
|
38
|
+
@fragments.inject(0) do |sum, fragment|
|
39
|
+
sum + fragment.space_count
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def line_width
|
44
|
+
if @unfinalized_line
|
45
|
+
raise "Lines must be finalized before calling #line_width"
|
46
|
+
end
|
47
|
+
@fragments.inject(0) do |sum, fragment|
|
48
|
+
sum + fragment.width
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def line
|
53
|
+
if @unfinalized_line
|
54
|
+
raise "Lines must be finalized before calling #line"
|
55
|
+
end
|
56
|
+
@fragments.collect do |fragment|
|
57
|
+
fragment.text.dup.force_encoding(::Encoding::UTF_8)
|
58
|
+
end.join
|
59
|
+
end
|
60
|
+
|
61
|
+
def finalize_line
|
62
|
+
@unfinalized_line = false
|
63
|
+
omit_trailing_whitespace_from_line_width
|
64
|
+
@fragments = []
|
65
|
+
@consumed.each do |hash|
|
66
|
+
text = hash[:text]
|
67
|
+
format_state = hash.dup
|
68
|
+
format_state.delete(:text)
|
69
|
+
fragment = Prawn::Text::Formatted::Fragment.new(text,
|
70
|
+
format_state,
|
71
|
+
@document)
|
72
|
+
@fragments << fragment
|
73
|
+
set_fragment_measurements(fragment)
|
74
|
+
set_line_measurement_maximums(fragment)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def format_array=(array)
|
79
|
+
initialize_line
|
80
|
+
@unconsumed = []
|
81
|
+
array.each do |hash|
|
82
|
+
hash[:text].scan(/[^\n]+|\n/) do |line|
|
83
|
+
@unconsumed << hash.merge(:text => line)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize_line
|
89
|
+
@unfinalized_line = true
|
90
|
+
@max_line_height = 0
|
91
|
+
@max_descender = 0
|
92
|
+
@max_ascender = 0
|
93
|
+
|
94
|
+
@consumed = []
|
95
|
+
@fragments = []
|
96
|
+
end
|
97
|
+
|
98
|
+
def finished?
|
99
|
+
@unconsumed.length == 0
|
100
|
+
end
|
101
|
+
|
102
|
+
def next_string
|
103
|
+
unless @unfinalized_line
|
104
|
+
raise "Lines must not be finalized when calling #next_string"
|
105
|
+
end
|
106
|
+
hash = @unconsumed.shift
|
107
|
+
if hash.nil?
|
108
|
+
nil
|
109
|
+
else
|
110
|
+
@consumed << hash.dup
|
111
|
+
@current_format_state = hash.dup
|
112
|
+
@current_format_state.delete(:text)
|
113
|
+
hash[:text]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def preview_next_string
|
118
|
+
hash = @unconsumed.first
|
119
|
+
if hash.nil? then nil
|
120
|
+
else hash[:text]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def apply_color_and_font_settings(fragment, &block)
|
125
|
+
if fragment.color
|
126
|
+
original_fill_color = @document.fill_color
|
127
|
+
original_stroke_color = @document.stroke_color
|
128
|
+
@document.fill_color(*fragment.color)
|
129
|
+
@document.stroke_color(*fragment.color)
|
130
|
+
apply_font_settings(fragment, &block)
|
131
|
+
@document.stroke_color = original_stroke_color
|
132
|
+
@document.fill_color = original_fill_color
|
133
|
+
else
|
134
|
+
apply_font_settings(fragment, &block)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def apply_font_settings(fragment=nil, &block)
|
139
|
+
if fragment.nil?
|
140
|
+
font = current_format_state[:font]
|
141
|
+
size = current_format_state[:size]
|
142
|
+
character_spacing = current_format_state[:character_spacing] ||
|
143
|
+
@document.character_spacing
|
144
|
+
styles = current_format_state[:styles]
|
145
|
+
font_style = font_style(styles)
|
146
|
+
else
|
147
|
+
font = fragment.font
|
148
|
+
size = fragment.size
|
149
|
+
character_spacing = fragment.character_spacing
|
150
|
+
styles = fragment.styles
|
151
|
+
font_style = font_style(styles)
|
152
|
+
end
|
153
|
+
|
154
|
+
@document.character_spacing(character_spacing) do
|
155
|
+
if font || font_style != :normal
|
156
|
+
raise "Bad font family" unless @document.font.family
|
157
|
+
@document.font(font || @document.font.family, :style => font_style) do
|
158
|
+
apply_font_size(size, styles, &block)
|
159
|
+
end
|
160
|
+
else
|
161
|
+
apply_font_size(size, styles, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def update_last_string(printed, unprinted, normalized_soft_hyphen=nil)
|
167
|
+
return if printed.nil?
|
168
|
+
if printed.empty?
|
169
|
+
@consumed.pop
|
170
|
+
else
|
171
|
+
@consumed.last[:text] = printed
|
172
|
+
if normalized_soft_hyphen
|
173
|
+
@consumed.last[:normalized_soft_hyphen] = normalized_soft_hyphen
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
unless unprinted.empty?
|
178
|
+
@unconsumed.unshift(@current_format_state.merge(:text => unprinted))
|
179
|
+
end
|
180
|
+
|
181
|
+
load_previous_format_state if printed.empty?
|
182
|
+
end
|
183
|
+
|
184
|
+
def retrieve_fragment
|
185
|
+
if @unfinalized_line
|
186
|
+
raise "Lines must be finalized before fragments can be retrieved"
|
187
|
+
end
|
188
|
+
@fragments.shift
|
189
|
+
end
|
190
|
+
|
191
|
+
def repack_unretrieved
|
192
|
+
new_unconsumed = []
|
193
|
+
while fragment = retrieve_fragment
|
194
|
+
fragment.include_trailing_white_space!
|
195
|
+
new_unconsumed << fragment.format_state.merge(:text => fragment.text)
|
196
|
+
end
|
197
|
+
@unconsumed = new_unconsumed.concat(@unconsumed)
|
198
|
+
end
|
199
|
+
|
200
|
+
def font_style(styles)
|
201
|
+
if styles.nil?
|
202
|
+
:normal
|
203
|
+
elsif styles.include?(:bold) && styles.include?(:italic)
|
204
|
+
:bold_italic
|
205
|
+
elsif styles.include?(:bold)
|
206
|
+
:bold
|
207
|
+
elsif styles.include?(:italic)
|
208
|
+
:italic
|
209
|
+
else
|
210
|
+
:normal
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
private
|
215
|
+
|
216
|
+
def load_previous_format_state
|
217
|
+
if @consumed.empty?
|
218
|
+
@current_format_state = {}
|
219
|
+
else
|
220
|
+
hash = @consumed.last
|
221
|
+
@current_format_state = hash.dup
|
222
|
+
@current_format_state.delete(:text)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def apply_font_size(size, styles)
|
227
|
+
if subscript?(styles) || superscript?(styles)
|
228
|
+
relative_size = 0.583
|
229
|
+
if size.nil?
|
230
|
+
size = @document.font_size * relative_size
|
231
|
+
else
|
232
|
+
size = size * relative_size
|
233
|
+
end
|
234
|
+
end
|
235
|
+
if size.nil?
|
236
|
+
yield
|
237
|
+
else
|
238
|
+
@document.font_size(size) { yield }
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def subscript?(styles)
|
243
|
+
if styles.nil? then false
|
244
|
+
else styles.include?(:subscript)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def superscript?(styles)
|
249
|
+
if styles.nil? then false
|
250
|
+
else styles.include?(:superscript)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def omit_trailing_whitespace_from_line_width
|
255
|
+
@consumed.reverse_each do |hash|
|
256
|
+
if hash[:text] == "\n"
|
257
|
+
break
|
258
|
+
elsif hash[:text].strip.empty? && @consumed.length > 1
|
259
|
+
# this entire fragment is trailing white space
|
260
|
+
hash[:exclude_trailing_white_space] = true
|
261
|
+
else
|
262
|
+
# this fragment contains the first non-white space we have
|
263
|
+
# encountered since the end of the line
|
264
|
+
hash[:exclude_trailing_white_space] = true
|
265
|
+
break
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def set_fragment_measurements(fragment)
|
271
|
+
apply_font_settings(fragment) do
|
272
|
+
fragment.width = @document.width_of(fragment.text,
|
273
|
+
:kerning => @kerning)
|
274
|
+
fragment.line_height = @document.font.height
|
275
|
+
fragment.descender = @document.font.descender
|
276
|
+
fragment.ascender = @document.font.ascender
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def set_line_measurement_maximums(fragment)
|
281
|
+
@max_line_height = [defined?(@max_line_height) && @max_line_height, fragment.line_height].compact.max
|
282
|
+
@max_descender = [defined?(@max_descender) && @max_descender, fragment.descender].compact.max
|
283
|
+
@max_ascender = [defined?(@max_ascender) && @max_ascender, fragment.ascender].compact.max
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,614 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# text/formatted/rectangle.rb : Implements text boxes with formatted text
|
4
|
+
#
|
5
|
+
# Copyright February 2010, Daniel Nelson. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Prawn
|
11
|
+
module Text
|
12
|
+
module Formatted
|
13
|
+
# @group Stable API
|
14
|
+
|
15
|
+
# Draws the requested formatted text into a box. When the text overflows
|
16
|
+
# the rectangle shrink to fit or truncate the text. Text boxes are
|
17
|
+
# independent of the document y position.
|
18
|
+
#
|
19
|
+
# == Formatted Text Array
|
20
|
+
#
|
21
|
+
# Formatted text is comprised of an array of hashes, where each hash
|
22
|
+
# defines text and format information. As of the time of writing, the
|
23
|
+
# following hash options are supported:
|
24
|
+
#
|
25
|
+
# <tt>:text</tt>::
|
26
|
+
# the text to format according to the other hash options
|
27
|
+
# <tt>:styles</tt>::
|
28
|
+
# an array of styles to apply to this text. Available styles include
|
29
|
+
# :bold, :italic, :underline, :strikethrough, :subscript, and
|
30
|
+
# :superscript
|
31
|
+
# <tt>:size</tt>::
|
32
|
+
# a number denoting the font size to apply to this text
|
33
|
+
# <tt>:character_spacing</tt>::
|
34
|
+
# a number denoting how much to increase or decrease the default
|
35
|
+
# spacing between characters
|
36
|
+
# <tt>:font</tt>::
|
37
|
+
# the name of a font. The name must be an AFM font with the desired
|
38
|
+
# faces or must be a font that is already registered using
|
39
|
+
# Prawn::Document#font_families
|
40
|
+
# <tt>:color</tt>::
|
41
|
+
# anything compatible with Prawn::Graphics::Color#fill_color and
|
42
|
+
# Prawn::Graphics::Color#stroke_color
|
43
|
+
# <tt>:link</tt>::
|
44
|
+
# a URL to which to create a link. A clickable link will be created
|
45
|
+
# to that URL. Note that you must explicitly underline and color using
|
46
|
+
# the appropriate tags if you which to draw attention to the link
|
47
|
+
# <tt>:anchor</tt>::
|
48
|
+
# a destination that has already been or will be registered using
|
49
|
+
# PDF::Core::Destinations#add_dest. A clickable link will be
|
50
|
+
# created to that destination. Note that you must explicitly underline
|
51
|
+
# and color using the appropriate tags if you which to draw attention
|
52
|
+
# to the link
|
53
|
+
# <tt>:local</tt>::
|
54
|
+
# a file or application to be opened locally. A clickable link will be
|
55
|
+
# created to the provided local file or application. If the file is
|
56
|
+
# another PDF, it will be opened in a new window. Note that you must
|
57
|
+
# explicitly underline and color using the appropriate tags if you which
|
58
|
+
# to draw attention to the link
|
59
|
+
# <tt>:draw_text_callback</tt>:
|
60
|
+
# if provided, this Proc will be called instead of #draw_text! once
|
61
|
+
# per fragment for every low-level addition of text to the page.
|
62
|
+
# <tt>:callback</tt>::
|
63
|
+
# an object (or array of such objects) with two methods:
|
64
|
+
# #render_behind and #render_in_front, which are called immediately
|
65
|
+
# prior to and immediately after rendring the text fragment and which
|
66
|
+
# are passed the fragment as an argument
|
67
|
+
#
|
68
|
+
# == Example
|
69
|
+
#
|
70
|
+
# formatted_text_box([{ :text => "hello" },
|
71
|
+
# { :text => "world",
|
72
|
+
# :size => 24,
|
73
|
+
# :styles => [:bold, :italic] }])
|
74
|
+
#
|
75
|
+
# == Options
|
76
|
+
#
|
77
|
+
# Accepts the same options as Text::Box with the below exceptions
|
78
|
+
#
|
79
|
+
# == Returns
|
80
|
+
#
|
81
|
+
# Returns a formatted text array representing any text that did not print
|
82
|
+
# under the current settings.
|
83
|
+
#
|
84
|
+
# == Exceptions
|
85
|
+
#
|
86
|
+
# Raises "Bad font family" if no font family is defined for the current font
|
87
|
+
#
|
88
|
+
# Raises <tt>Prawn::Errors::CannotFit</tt> if not wide enough to print
|
89
|
+
# any text
|
90
|
+
#
|
91
|
+
def formatted_text_box(array, options={})
|
92
|
+
Text::Formatted::Box.new(array, options.merge(:document => self)).render
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generally, one would use the Prawn::Text::Formatted#formatted_text_box
|
96
|
+
# convenience method. However, using Text::Formatted::Box.new in
|
97
|
+
# conjunction with #render(:dry_run => true) enables one to do look-ahead
|
98
|
+
# calculations prior to placing text on the page, or to determine how much
|
99
|
+
# vertical space was consumed by the printed text
|
100
|
+
#
|
101
|
+
class Box
|
102
|
+
include Prawn::Text::Formatted::Wrap
|
103
|
+
|
104
|
+
# @group Experimental API
|
105
|
+
|
106
|
+
# The text that was successfully printed (or, if <tt>dry_run</tt> was
|
107
|
+
# used, the text that would have been successfully printed)
|
108
|
+
attr_reader :text
|
109
|
+
|
110
|
+
# True if nothing printed (or, if <tt>dry_run</tt> was
|
111
|
+
# used, nothing would have been successfully printed)
|
112
|
+
def nothing_printed?
|
113
|
+
@nothing_printed
|
114
|
+
end
|
115
|
+
|
116
|
+
# True if everything printed (or, if <tt>dry_run</tt> was
|
117
|
+
# used, everything would have been successfully printed)
|
118
|
+
def everything_printed?
|
119
|
+
@everything_printed
|
120
|
+
end
|
121
|
+
|
122
|
+
# The upper left corner of the text box
|
123
|
+
attr_reader :at
|
124
|
+
# The line height of the last line printed
|
125
|
+
attr_reader :line_height
|
126
|
+
# The height of the ascender of the last line printed
|
127
|
+
attr_reader :ascender
|
128
|
+
# The height of the descender of the last line printed
|
129
|
+
attr_reader :descender
|
130
|
+
# The leading used during printing
|
131
|
+
attr_reader :leading
|
132
|
+
|
133
|
+
def line_gap
|
134
|
+
line_height - (ascender + descender)
|
135
|
+
end
|
136
|
+
|
137
|
+
# See Prawn::Text#text_box for valid options
|
138
|
+
#
|
139
|
+
def initialize(formatted_text, options={})
|
140
|
+
@inked = false
|
141
|
+
Prawn.verify_options(valid_options, options)
|
142
|
+
options = options.dup
|
143
|
+
|
144
|
+
self.class.extensions.reverse_each { |e| extend e }
|
145
|
+
|
146
|
+
@overflow = options[:overflow] || :truncate
|
147
|
+
@disable_wrap_by_char = options[:disable_wrap_by_char]
|
148
|
+
|
149
|
+
self.original_text = formatted_text
|
150
|
+
@text = nil
|
151
|
+
|
152
|
+
@document = options[:document]
|
153
|
+
@direction = options[:direction] || @document.text_direction
|
154
|
+
@fallback_fonts = options[:fallback_fonts] ||
|
155
|
+
@document.fallback_fonts
|
156
|
+
@at = (options[:at] ||
|
157
|
+
[@document.bounds.left, @document.bounds.top]).dup
|
158
|
+
@width = options[:width] ||
|
159
|
+
@document.bounds.right - @at[0]
|
160
|
+
@height = options[:height] || default_height
|
161
|
+
@align = options[:align] ||
|
162
|
+
(@direction == :rtl ? :right : :left)
|
163
|
+
@vertical_align = options[:valign] || :top
|
164
|
+
@leading = options[:leading] || @document.default_leading
|
165
|
+
@character_spacing = options[:character_spacing] ||
|
166
|
+
@document.character_spacing
|
167
|
+
@mode = options[:mode] || @document.text_rendering_mode
|
168
|
+
@rotate = options[:rotate] || 0
|
169
|
+
@rotate_around = options[:rotate_around] || :upper_left
|
170
|
+
@single_line = options[:single_line]
|
171
|
+
@draw_text_callback = options[:draw_text_callback]
|
172
|
+
|
173
|
+
# if the text rendering mode is :unknown, force it back to :fill
|
174
|
+
if @mode == :unknown
|
175
|
+
@mode = :fill
|
176
|
+
end
|
177
|
+
|
178
|
+
if @overflow == :expand
|
179
|
+
# if set to expand, then we simply set the bottom
|
180
|
+
# as the bottom of the document bounds, since that
|
181
|
+
# is the maximum we should expand to
|
182
|
+
@height = default_height
|
183
|
+
@overflow = :truncate
|
184
|
+
end
|
185
|
+
@min_font_size = options[:min_font_size] || 5
|
186
|
+
if options[:kerning].nil? then
|
187
|
+
options[:kerning] = @document.default_kerning?
|
188
|
+
end
|
189
|
+
@options = { :kerning => options[:kerning],
|
190
|
+
:size => options[:size],
|
191
|
+
:style => options[:style] }
|
192
|
+
|
193
|
+
super(formatted_text, options)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Render text to the document based on the settings defined in initialize.
|
197
|
+
#
|
198
|
+
# In order to facilitate look-ahead calculations, <tt>render</tt> accepts
|
199
|
+
# a <tt>:dry_run => true</tt> option. If provided, then everything is
|
200
|
+
# executed as if rendering, with the exception that nothing is drawn on
|
201
|
+
# the page. Useful for look-ahead computations of height, unprinted text,
|
202
|
+
# etc.
|
203
|
+
#
|
204
|
+
# Returns any text that did not print under the current settings
|
205
|
+
#
|
206
|
+
def render(flags={})
|
207
|
+
unprinted_text = []
|
208
|
+
|
209
|
+
@document.save_font do
|
210
|
+
@document.character_spacing(@character_spacing) do
|
211
|
+
@document.text_rendering_mode(@mode) do
|
212
|
+
process_options
|
213
|
+
|
214
|
+
text = normalized_text(flags)
|
215
|
+
|
216
|
+
@document.font_size(@font_size) do
|
217
|
+
shrink_to_fit(text) if @overflow == :shrink_to_fit
|
218
|
+
process_vertical_alignment(text)
|
219
|
+
@inked = true unless flags[:dry_run]
|
220
|
+
if @rotate != 0 && @inked
|
221
|
+
unprinted_text = render_rotated(text)
|
222
|
+
else
|
223
|
+
unprinted_text = wrap(text)
|
224
|
+
end
|
225
|
+
@inked = false
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
unprinted_text.map do |e|
|
232
|
+
e.merge(:text => @document.font.to_utf8(e[:text]))
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# The width available at this point in the box
|
237
|
+
#
|
238
|
+
def available_width
|
239
|
+
@width
|
240
|
+
end
|
241
|
+
|
242
|
+
# The height actually used during the previous <tt>render</tt>
|
243
|
+
#
|
244
|
+
def height
|
245
|
+
return 0 if @baseline_y.nil? || @descender.nil?
|
246
|
+
(@baseline_y - @descender).abs
|
247
|
+
end
|
248
|
+
|
249
|
+
# <tt>fragment</tt> is a Prawn::Text::Formatted::Fragment object
|
250
|
+
#
|
251
|
+
def draw_fragment(fragment, accumulated_width=0, line_width=0, word_spacing=0) #:nodoc:
|
252
|
+
case(@align)
|
253
|
+
when :left
|
254
|
+
x = @at[0]
|
255
|
+
when :center
|
256
|
+
x = @at[0] + @width * 0.5 - line_width * 0.5
|
257
|
+
when :right
|
258
|
+
x = @at[0] + @width - line_width
|
259
|
+
when :justify
|
260
|
+
if @direction == :ltr
|
261
|
+
x = @at[0]
|
262
|
+
else
|
263
|
+
x = @at[0] + @width - line_width
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
x += accumulated_width
|
268
|
+
|
269
|
+
y = @at[1] + @baseline_y
|
270
|
+
|
271
|
+
y += fragment.y_offset
|
272
|
+
|
273
|
+
fragment.left = x
|
274
|
+
fragment.baseline = y
|
275
|
+
|
276
|
+
if @inked
|
277
|
+
draw_fragment_underlays(fragment)
|
278
|
+
|
279
|
+
@document.word_spacing(word_spacing) {
|
280
|
+
if @draw_text_callback
|
281
|
+
@draw_text_callback.call(fragment.text, :at => [x, y],
|
282
|
+
:kerning => @kerning)
|
283
|
+
else
|
284
|
+
@document.draw_text!(fragment.text, :at => [x, y],
|
285
|
+
:kerning => @kerning)
|
286
|
+
end
|
287
|
+
}
|
288
|
+
|
289
|
+
draw_fragment_overlays(fragment)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# @group Extension API
|
294
|
+
|
295
|
+
# Example (see Prawn::Text::Core::Formatted::Wrap for what is required
|
296
|
+
# of the wrap method if you want to override the default wrapping
|
297
|
+
# algorithm):
|
298
|
+
#
|
299
|
+
#
|
300
|
+
# module MyWrap
|
301
|
+
#
|
302
|
+
# def wrap(array)
|
303
|
+
# initialize_wrap([{ :text => 'all your base are belong to us' }])
|
304
|
+
# @line_wrap.wrap_line(:document => @document,
|
305
|
+
# :kerning => @kerning,
|
306
|
+
# :width => 10000,
|
307
|
+
# :arranger => @arranger)
|
308
|
+
# fragment = @arranger.retrieve_fragment
|
309
|
+
# format_and_draw_fragment(fragment, 0, @line_wrap.width, 0)
|
310
|
+
# []
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
# Prawn::Text::Formatted::Box.extensions << MyWrap
|
316
|
+
#
|
317
|
+
# box = Prawn::Text::Formatted::Box.new('hello world')
|
318
|
+
# box.render('why can't I print anything other than' +
|
319
|
+
# '"all your base are belong to us"?')
|
320
|
+
#
|
321
|
+
#
|
322
|
+
def self.extensions
|
323
|
+
@extensions ||= []
|
324
|
+
end
|
325
|
+
|
326
|
+
# @private
|
327
|
+
def self.inherited(base)
|
328
|
+
extensions.each { |e| base.extensions << e }
|
329
|
+
end
|
330
|
+
|
331
|
+
def valid_options
|
332
|
+
PDF::Core::Text::VALID_OPTIONS + [:at, :height, :width,
|
333
|
+
:align, :valign,
|
334
|
+
:rotate, :rotate_around,
|
335
|
+
:overflow, :min_font_size,
|
336
|
+
:disable_wrap_by_char,
|
337
|
+
:leading, :character_spacing,
|
338
|
+
:mode, :single_line,
|
339
|
+
:document,
|
340
|
+
:direction,
|
341
|
+
:fallback_fonts,
|
342
|
+
:draw_text_callback]
|
343
|
+
end
|
344
|
+
|
345
|
+
private
|
346
|
+
|
347
|
+
def normalized_text(flags)
|
348
|
+
text = normalize_encoding
|
349
|
+
|
350
|
+
text.each { |t| t.delete(:color) } if flags[:dry_run]
|
351
|
+
|
352
|
+
text
|
353
|
+
end
|
354
|
+
|
355
|
+
def original_text
|
356
|
+
@original_array.collect { |hash| hash.dup }
|
357
|
+
end
|
358
|
+
|
359
|
+
def original_text=(formatted_text)
|
360
|
+
@original_array = formatted_text
|
361
|
+
end
|
362
|
+
|
363
|
+
def normalize_encoding
|
364
|
+
formatted_text = original_text
|
365
|
+
|
366
|
+
unless @fallback_fonts.empty?
|
367
|
+
formatted_text = process_fallback_fonts(formatted_text)
|
368
|
+
end
|
369
|
+
|
370
|
+
formatted_text.each do |hash|
|
371
|
+
if hash[:font]
|
372
|
+
@document.font(hash[:font]) do
|
373
|
+
hash[:text] = @document.font.normalize_encoding(hash[:text])
|
374
|
+
end
|
375
|
+
else
|
376
|
+
hash[:text] = @document.font.normalize_encoding(hash[:text])
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
formatted_text
|
381
|
+
end
|
382
|
+
|
383
|
+
def process_fallback_fonts(formatted_text)
|
384
|
+
modified_formatted_text = []
|
385
|
+
|
386
|
+
formatted_text.each do |hash|
|
387
|
+
fragments = analyze_glyphs_for_fallback_font_support(hash)
|
388
|
+
modified_formatted_text.concat(fragments)
|
389
|
+
end
|
390
|
+
|
391
|
+
modified_formatted_text
|
392
|
+
end
|
393
|
+
|
394
|
+
def analyze_glyphs_for_fallback_font_support(hash)
|
395
|
+
font_glyph_pairs = []
|
396
|
+
|
397
|
+
original_font = @document.font.family
|
398
|
+
fragment_font = hash[:font] || original_font
|
399
|
+
|
400
|
+
fallback_fonts = @fallback_fonts.dup
|
401
|
+
# always default back to the current font if the glyph is missing from
|
402
|
+
# all fonts
|
403
|
+
fallback_fonts << fragment_font
|
404
|
+
|
405
|
+
@document.save_font do
|
406
|
+
hash[:text].each_char do |char|
|
407
|
+
font_glyph_pairs << [find_font_for_this_glyph(char,
|
408
|
+
fragment_font,
|
409
|
+
fallback_fonts.dup),
|
410
|
+
char]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Don't add a :font to fragments if it wasn't there originally
|
415
|
+
if hash[:font].nil?
|
416
|
+
font_glyph_pairs.each do |pair|
|
417
|
+
pair[0] = nil if pair[0] == original_font
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
|
422
|
+
end
|
423
|
+
|
424
|
+
def find_font_for_this_glyph(char, current_font, fallback_fonts)
|
425
|
+
@document.font(current_font)
|
426
|
+
if fallback_fonts.length == 0 || @document.font.glyph_present?(char)
|
427
|
+
current_font
|
428
|
+
else
|
429
|
+
find_font_for_this_glyph(char, fallback_fonts.shift, fallback_fonts)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
|
434
|
+
fragments = []
|
435
|
+
fragment = nil
|
436
|
+
current_font = nil
|
437
|
+
|
438
|
+
font_glyph_pairs.each do |font, char|
|
439
|
+
if font != current_font || fragments.count == 0
|
440
|
+
current_font = font
|
441
|
+
fragment = hash.dup
|
442
|
+
fragment[:text] = char
|
443
|
+
fragment[:font] = font unless font.nil?
|
444
|
+
fragments << fragment
|
445
|
+
else
|
446
|
+
fragment[:text] += char
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
fragments
|
451
|
+
end
|
452
|
+
|
453
|
+
def move_baseline_down
|
454
|
+
if @baseline_y == 0
|
455
|
+
@baseline_y = -@ascender
|
456
|
+
else
|
457
|
+
@baseline_y -= (@line_height + @leading)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# Returns the default height to be used if none is provided or if the
|
462
|
+
# overflow option is set to :expand. If we are in a stretchy bounding
|
463
|
+
# box, assume we can stretch to the bottom of the innermost non-stretchy
|
464
|
+
# box.
|
465
|
+
#
|
466
|
+
def default_height
|
467
|
+
# Find the "frame", the innermost non-stretchy bbox.
|
468
|
+
frame = @document.bounds
|
469
|
+
frame = frame.parent while frame.stretchy? && frame.parent
|
470
|
+
|
471
|
+
@at[1] + @document.bounds.absolute_bottom - frame.absolute_bottom
|
472
|
+
end
|
473
|
+
|
474
|
+
def process_vertical_alignment(text)
|
475
|
+
# The vertical alignment must only be done once per text box, but
|
476
|
+
# we need to wait until render() is called so that the fonts are set
|
477
|
+
# up properly for wrapping. So guard with a boolean to ensure this is
|
478
|
+
# only run once.
|
479
|
+
return if defined?(@vertical_alignment_processed) && @vertical_alignment_processed
|
480
|
+
@vertical_alignment_processed = true
|
481
|
+
|
482
|
+
return if @vertical_align == :top
|
483
|
+
|
484
|
+
wrap(text)
|
485
|
+
|
486
|
+
case @vertical_align
|
487
|
+
when :center
|
488
|
+
@at[1] -= (@height - height + @descender) * 0.5
|
489
|
+
when :bottom
|
490
|
+
@at[1] -= (@height - height)
|
491
|
+
end
|
492
|
+
|
493
|
+
@height = height
|
494
|
+
end
|
495
|
+
|
496
|
+
# Decrease the font size until the text fits or the min font
|
497
|
+
# size is reached
|
498
|
+
def shrink_to_fit(text)
|
499
|
+
loop do
|
500
|
+
if @disable_wrap_by_char && @font_size > @min_font_size
|
501
|
+
begin
|
502
|
+
wrap(text)
|
503
|
+
rescue Errors::CannotFit
|
504
|
+
# Ignore errors while we can still attempt smaller
|
505
|
+
# font sizes.
|
506
|
+
end
|
507
|
+
else
|
508
|
+
wrap(text)
|
509
|
+
end
|
510
|
+
|
511
|
+
break if @everything_printed || @font_size <= @min_font_size
|
512
|
+
|
513
|
+
@font_size = [@font_size - 0.5, @min_font_size].max
|
514
|
+
@document.font_size = @font_size
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
def process_options
|
519
|
+
# must be performed within a save_font block because
|
520
|
+
# document.process_text_options sets the font
|
521
|
+
@document.process_text_options(@options)
|
522
|
+
@font_size = @options[:size]
|
523
|
+
@kerning = @options[:kerning]
|
524
|
+
end
|
525
|
+
|
526
|
+
def render_rotated(text)
|
527
|
+
unprinted_text = ''
|
528
|
+
|
529
|
+
case @rotate_around
|
530
|
+
when :center
|
531
|
+
x = @at[0] + @width * 0.5
|
532
|
+
y = @at[1] - @height * 0.5
|
533
|
+
when :upper_right
|
534
|
+
x = @at[0] + @width
|
535
|
+
y = @at[1]
|
536
|
+
when :lower_right
|
537
|
+
x = @at[0] + @width
|
538
|
+
y = @at[1] - @height
|
539
|
+
when :lower_left
|
540
|
+
x = @at[0]
|
541
|
+
y = @at[1] - @height
|
542
|
+
else
|
543
|
+
x = @at[0]
|
544
|
+
y = @at[1]
|
545
|
+
end
|
546
|
+
|
547
|
+
@document.rotate(@rotate, :origin => [x, y]) do
|
548
|
+
unprinted_text = wrap(text)
|
549
|
+
end
|
550
|
+
unprinted_text
|
551
|
+
end
|
552
|
+
|
553
|
+
def draw_fragment_underlays(fragment)
|
554
|
+
fragment.callback_objects.each do |obj|
|
555
|
+
obj.render_behind(fragment) if obj.respond_to?(:render_behind)
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def draw_fragment_overlays(fragment)
|
560
|
+
draw_fragment_overlay_styles(fragment)
|
561
|
+
draw_fragment_overlay_link(fragment)
|
562
|
+
draw_fragment_overlay_anchor(fragment)
|
563
|
+
draw_fragment_overlay_local(fragment)
|
564
|
+
fragment.callback_objects.each do |obj|
|
565
|
+
obj.render_in_front(fragment) if obj.respond_to?(:render_in_front)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
def draw_fragment_overlay_link(fragment)
|
570
|
+
return unless fragment.link
|
571
|
+
box = fragment.absolute_bounding_box
|
572
|
+
@document.link_annotation(box,
|
573
|
+
:Border => [0, 0, 0],
|
574
|
+
:A => { :Type => :Action,
|
575
|
+
:S => :URI,
|
576
|
+
:URI => PDF::Core::LiteralString.new(fragment.link) })
|
577
|
+
end
|
578
|
+
|
579
|
+
def draw_fragment_overlay_anchor(fragment)
|
580
|
+
return unless fragment.anchor
|
581
|
+
box = fragment.absolute_bounding_box
|
582
|
+
@document.link_annotation(box,
|
583
|
+
:Border => [0, 0, 0],
|
584
|
+
:Dest => fragment.anchor)
|
585
|
+
end
|
586
|
+
|
587
|
+
def draw_fragment_overlay_local(fragment)
|
588
|
+
return unless fragment.local
|
589
|
+
box = fragment.absolute_bounding_box
|
590
|
+
@document.link_annotation(box,
|
591
|
+
:Border => [0, 0, 0],
|
592
|
+
:A => { :Type => :Action,
|
593
|
+
:S => :Launch,
|
594
|
+
:F => PDF::Core::LiteralString.new(fragment.local),
|
595
|
+
:NewWindow => true })
|
596
|
+
end
|
597
|
+
|
598
|
+
def draw_fragment_overlay_styles(fragment)
|
599
|
+
underline = fragment.styles.include?(:underline)
|
600
|
+
if underline
|
601
|
+
@document.stroke_line(fragment.underline_points)
|
602
|
+
end
|
603
|
+
|
604
|
+
strikethrough = fragment.styles.include?(:strikethrough)
|
605
|
+
if strikethrough
|
606
|
+
@document.stroke_line(fragment.strikethrough_points)
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
end
|
611
|
+
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|