giga-fast-kit 0.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/giga-fast-kit.gemspec +12 -0
- data/prawn-2.5.0/COPYING +2 -0
- data/prawn-2.5.0/GPLv2 +339 -0
- data/prawn-2.5.0/GPLv3 +674 -0
- data/prawn-2.5.0/LICENSE +56 -0
- data/prawn-2.5.0/data/fonts/Courier-Bold.afm +342 -0
- data/prawn-2.5.0/data/fonts/Courier-BoldOblique.afm +342 -0
- data/prawn-2.5.0/data/fonts/Courier-Oblique.afm +342 -0
- data/prawn-2.5.0/data/fonts/Courier.afm +342 -0
- data/prawn-2.5.0/data/fonts/Helvetica-Bold.afm +2827 -0
- data/prawn-2.5.0/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/prawn-2.5.0/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/prawn-2.5.0/data/fonts/Helvetica.afm +3051 -0
- data/prawn-2.5.0/data/fonts/MustRead.html +19 -0
- data/prawn-2.5.0/data/fonts/Symbol.afm +213 -0
- data/prawn-2.5.0/data/fonts/Times-Bold.afm +2588 -0
- data/prawn-2.5.0/data/fonts/Times-BoldItalic.afm +2384 -0
- data/prawn-2.5.0/data/fonts/Times-Italic.afm +2667 -0
- data/prawn-2.5.0/data/fonts/Times-Roman.afm +2419 -0
- data/prawn-2.5.0/data/fonts/ZapfDingbats.afm +225 -0
- data/prawn-2.5.0/lib/prawn/document/bounding_box.rb +625 -0
- data/prawn-2.5.0/lib/prawn/document/column_box.rb +177 -0
- data/prawn-2.5.0/lib/prawn/document/internals.rb +76 -0
- data/prawn-2.5.0/lib/prawn/document/span.rb +65 -0
- data/prawn-2.5.0/lib/prawn/document.rb +846 -0
- data/prawn-2.5.0/lib/prawn/encoding.rb +85 -0
- data/prawn-2.5.0/lib/prawn/errors.rb +73 -0
- data/prawn-2.5.0/lib/prawn/font.rb +567 -0
- data/prawn-2.5.0/lib/prawn/font_metric_cache.rb +50 -0
- data/prawn-2.5.0/lib/prawn/fonts/afm.rb +332 -0
- data/prawn-2.5.0/lib/prawn/fonts/dfont.rb +42 -0
- data/prawn-2.5.0/lib/prawn/fonts/otf.rb +14 -0
- data/prawn-2.5.0/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
- data/prawn-2.5.0/lib/prawn/fonts/ttc.rb +41 -0
- data/prawn-2.5.0/lib/prawn/fonts/ttf.rb +615 -0
- data/prawn-2.5.0/lib/prawn/fonts.rb +14 -0
- data/prawn-2.5.0/lib/prawn/graphics/blend_mode.rb +62 -0
- data/prawn-2.5.0/lib/prawn/graphics/cap_style.rb +44 -0
- data/prawn-2.5.0/lib/prawn/graphics/color.rb +257 -0
- data/prawn-2.5.0/lib/prawn/graphics/dash.rb +123 -0
- data/prawn-2.5.0/lib/prawn/graphics/join_style.rb +60 -0
- data/prawn-2.5.0/lib/prawn/graphics/patterns.rb +389 -0
- data/prawn-2.5.0/lib/prawn/graphics/transformation.rb +172 -0
- data/prawn-2.5.0/lib/prawn/graphics/transparency.rb +79 -0
- data/prawn-2.5.0/lib/prawn/graphics.rb +791 -0
- data/prawn-2.5.0/lib/prawn/grid.rb +449 -0
- data/prawn-2.5.0/lib/prawn/image_handler.rb +61 -0
- data/prawn-2.5.0/lib/prawn/images/image.rb +48 -0
- data/prawn-2.5.0/lib/prawn/images/jpg.rb +121 -0
- data/prawn-2.5.0/lib/prawn/images/png.rb +383 -0
- data/prawn-2.5.0/lib/prawn/images.rb +195 -0
- data/prawn-2.5.0/lib/prawn/measurement_extensions.rb +82 -0
- data/prawn-2.5.0/lib/prawn/measurements.rb +129 -0
- data/prawn-2.5.0/lib/prawn/outline.rb +316 -0
- data/prawn-2.5.0/lib/prawn/repeater.rb +140 -0
- data/prawn-2.5.0/lib/prawn/security/arcfour.rb +54 -0
- data/prawn-2.5.0/lib/prawn/security.rb +308 -0
- data/prawn-2.5.0/lib/prawn/soft_mask.rb +105 -0
- data/prawn-2.5.0/lib/prawn/stamp.rb +147 -0
- data/prawn-2.5.0/lib/prawn/text/box.rb +192 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/arranger.rb +396 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/box.rb +725 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/fragment.rb +373 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/line_wrap.rb +335 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/parser.rb +284 -0
- data/prawn-2.5.0/lib/prawn/text/formatted/wrap.rb +166 -0
- data/prawn-2.5.0/lib/prawn/text/formatted.rb +82 -0
- data/prawn-2.5.0/lib/prawn/text.rb +701 -0
- data/prawn-2.5.0/lib/prawn/transformation_stack.rb +63 -0
- data/prawn-2.5.0/lib/prawn/utilities.rb +34 -0
- data/prawn-2.5.0/lib/prawn/version.rb +6 -0
- data/prawn-2.5.0/lib/prawn/view.rb +116 -0
- data/prawn-2.5.0/lib/prawn.rb +83 -0
- metadata +114 -0
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
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
|
+
# Formatted text box.
|
|
14
|
+
#
|
|
15
|
+
# Generally, one would use the {Prawn::Text::Formatted#formatted_text_box}
|
|
16
|
+
# convenience method. However, using `Text::Formatted::Box.new` in
|
|
17
|
+
# conjunction with `#render(dry_run: true)` enables one to do calculations
|
|
18
|
+
# prior to placing text on the page, or to determine how much vertical
|
|
19
|
+
# space was consumed by the printed text
|
|
20
|
+
class Box
|
|
21
|
+
include Prawn::Text::Formatted::Wrap
|
|
22
|
+
|
|
23
|
+
# @group Experimental API
|
|
24
|
+
|
|
25
|
+
# The text that was successfully printed (or, if `:dry_run` was
|
|
26
|
+
# used, the text that would have been successfully printed).
|
|
27
|
+
# @return [Array<Hash>]
|
|
28
|
+
attr_reader :text
|
|
29
|
+
|
|
30
|
+
# True if nothing printed (or, if `:dry_run` was used, nothing would
|
|
31
|
+
# have been successfully printed).
|
|
32
|
+
#
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def nothing_printed?
|
|
35
|
+
@nothing_printed
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# True if everything printed (or, if `:dry_run` was used, everything
|
|
39
|
+
# would have been successfully printed).
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def everything_printed?
|
|
43
|
+
@everything_printed
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The upper left corner of the text box.
|
|
47
|
+
# @return [Array(Number, Number)]
|
|
48
|
+
attr_reader :at
|
|
49
|
+
|
|
50
|
+
# The line height of the last line printed.
|
|
51
|
+
# @return [Number]
|
|
52
|
+
attr_reader :line_height
|
|
53
|
+
|
|
54
|
+
# The height of the ascender of the last line printed.
|
|
55
|
+
# @return [Number]
|
|
56
|
+
attr_reader :ascender
|
|
57
|
+
|
|
58
|
+
# The height of the descender of the last line printed.
|
|
59
|
+
# @return [Number]
|
|
60
|
+
attr_reader :descender
|
|
61
|
+
|
|
62
|
+
# The leading used during printing.
|
|
63
|
+
# @return [Number]
|
|
64
|
+
attr_reader :leading
|
|
65
|
+
|
|
66
|
+
# Gap between adjacent lines of text.
|
|
67
|
+
#
|
|
68
|
+
# @return [Number]
|
|
69
|
+
def line_gap
|
|
70
|
+
line_height - (ascender + descender)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# See Prawn::Text#text_box for valid options
|
|
74
|
+
#
|
|
75
|
+
# @param formatted_text [Array<Hash{Symbol => any}>]
|
|
76
|
+
# Formatted text is an array of hashes, where each hash defines text
|
|
77
|
+
# and format information. The following hash options are supported:
|
|
78
|
+
#
|
|
79
|
+
# - `:text` --- the text to format according to the other hash
|
|
80
|
+
# options.
|
|
81
|
+
# - `:styles` --- an array of styles to apply to this text. Available
|
|
82
|
+
# styles include `:bold`, `:italic`, `:underline`, `:strikethrough`,
|
|
83
|
+
# `:subscript`, and `:superscript`.
|
|
84
|
+
# - `:size` ---a number denoting the font size to apply to this text.
|
|
85
|
+
# - `:character_spacing` --- a number denoting how much to increase or
|
|
86
|
+
# decrease the default spacing between characters.
|
|
87
|
+
# - `:font` --- the name of a font. The name must be an AFM font with
|
|
88
|
+
# the desired faces or must be a font that is already registered
|
|
89
|
+
# using {Prawn::Document#font_families}.
|
|
90
|
+
# - `:color` --- anything compatible with
|
|
91
|
+
# {Prawn::Graphics::Color#fill_color} and
|
|
92
|
+
# {Prawn::Graphics::Color#stroke_color}.
|
|
93
|
+
# - :link` --- a URL to which to create a link. A clickable link will
|
|
94
|
+
# be created to that URL. Note that you must explicitly underline
|
|
95
|
+
# and color using the appropriate tags if you which to draw
|
|
96
|
+
# attention to the link.
|
|
97
|
+
# - `:anchor` --- a destination that has already been or will be
|
|
98
|
+
# registered using `PDF::Core::Destinations#add_dest`. A clickable
|
|
99
|
+
# link will be created to that destination. Note that you must
|
|
100
|
+
# explicitly underline and color using the appropriate tags if you
|
|
101
|
+
# which to draw attention to the link.
|
|
102
|
+
# - `:local` --- a file or application to be opened locally.
|
|
103
|
+
# A clickable link will be created to the provided local file or
|
|
104
|
+
# application. If the file is another PDF, it will be opened in
|
|
105
|
+
# a new window. Note that you must explicitly underline and color
|
|
106
|
+
# using the appropriate options if you which to draw attention to
|
|
107
|
+
# the link.
|
|
108
|
+
# - `:draw_text_callback` --- if provided, this Proc will be called
|
|
109
|
+
# instead of {Prawn::Document#draw_text!} once per fragment for
|
|
110
|
+
# every low-level addition of text to the page.
|
|
111
|
+
# - `:callback` --- an object (or array of such objects) with two
|
|
112
|
+
# methods: `render_behind` and `render_in_front`, which are called
|
|
113
|
+
# immediately prior to and immediately after rendering the text
|
|
114
|
+
# fragment and which are passed the fragment as an argument.
|
|
115
|
+
# @param options [Hash{Symbol => any}]
|
|
116
|
+
# @option options :document [Prawn::Document] Owning document.
|
|
117
|
+
# @option options :kerning [Boolean]
|
|
118
|
+
# (value of document.default_kerning?)
|
|
119
|
+
# Whether or not to use kerning (if it is available with the current
|
|
120
|
+
# font).
|
|
121
|
+
# @option options :size [Number] (current font size)
|
|
122
|
+
# The font size to use.
|
|
123
|
+
# @option options :character_spacing [Number] (0)
|
|
124
|
+
# The amount of space to add to or remove from the default character
|
|
125
|
+
# spacing.
|
|
126
|
+
# @option options :disable_wrap_by_char [Boolean] (false)
|
|
127
|
+
# Whether or not to prevent mid-word breaks when text does not fit in
|
|
128
|
+
# box.
|
|
129
|
+
# @option options :mode [Symbol] (:fill)
|
|
130
|
+
# The text rendering mode. See documentation for
|
|
131
|
+
# {Prawn::Document#text_rendering_mode} for a list of valid options.
|
|
132
|
+
# @option option :style [Symbol] (current style)
|
|
133
|
+
# The style to use. The requested style must be part of the current
|
|
134
|
+
# font family.
|
|
135
|
+
# @option option :at [Array(Number, Number)] (bounds top left corner)
|
|
136
|
+
# The upper left corner of the box.
|
|
137
|
+
# @option options :width [Number] (bounds.right - at[0])
|
|
138
|
+
# The width of the box.
|
|
139
|
+
# @option options :height [Number] (default_height())
|
|
140
|
+
# The height of the box.
|
|
141
|
+
# @option options :direction [:ltr, :rtl]
|
|
142
|
+
# (value of document.text_direction)
|
|
143
|
+
# Direction of the text (left-to-right or right-to-left).
|
|
144
|
+
# @option options :fallback_fonts [Array<String>]
|
|
145
|
+
# An array of font names. Each name must be the name of an AFM font or
|
|
146
|
+
# the name that was used to register a family of external fonts (see
|
|
147
|
+
# {Prawn::Document#font_families}). If present, then each glyph will
|
|
148
|
+
# be rendered using the first font that includes the glyph, starting
|
|
149
|
+
# with the current font and then moving through `:fallback_fonts`.
|
|
150
|
+
# @option options :align [:left, :center, :right, :justify]
|
|
151
|
+
# (:left if direction is :ltr, :right if direction is :rtl)
|
|
152
|
+
# Alignment within the bounding box.
|
|
153
|
+
# @option options :valign [:top, :center, :bottom] (:top)
|
|
154
|
+
# Vertical alignment within the bounding box.
|
|
155
|
+
# @option options :rotate [Number]
|
|
156
|
+
# The angle to rotate the text.
|
|
157
|
+
# @option options :rotate_around
|
|
158
|
+
# [:center, :upper_left, :upper_right, :lower_right, :lower_left]
|
|
159
|
+
# (:upper_left)
|
|
160
|
+
# The point around which to rotate the text.
|
|
161
|
+
# @option options :leading [Number] (value of document.default_leading)
|
|
162
|
+
# Additional space between lines.
|
|
163
|
+
# @option options :single_line [Boolean] (false)
|
|
164
|
+
# If true, then only the first line will be drawn.
|
|
165
|
+
# @option options :overflow [:truncate, :shrink_to_fit, :expand]
|
|
166
|
+
# (:truncate)
|
|
167
|
+
# This controls the behavior when the amount of text exceeds the
|
|
168
|
+
# available space.
|
|
169
|
+
# @option options :min_font_size [Number] (5)
|
|
170
|
+
# The minimum font size to use when `:overflow` is set to
|
|
171
|
+
# `:shrink_to_fit` (that is the font size will not be reduced to less
|
|
172
|
+
# than this value, even if it means that some text will be cut off).
|
|
173
|
+
def initialize(formatted_text, options = {})
|
|
174
|
+
@inked = false
|
|
175
|
+
Prawn.verify_options(valid_options, options)
|
|
176
|
+
options = options.dup
|
|
177
|
+
|
|
178
|
+
self.class.extensions.reverse_each { |e| extend(e) }
|
|
179
|
+
|
|
180
|
+
@overflow = options[:overflow] || :truncate
|
|
181
|
+
@disable_wrap_by_char = options[:disable_wrap_by_char]
|
|
182
|
+
|
|
183
|
+
self.original_text = formatted_text
|
|
184
|
+
@text = nil
|
|
185
|
+
|
|
186
|
+
@document = options[:document]
|
|
187
|
+
@direction = options[:direction] || @document.text_direction
|
|
188
|
+
@fallback_fonts = options[:fallback_fonts] ||
|
|
189
|
+
@document.fallback_fonts
|
|
190
|
+
@at = (
|
|
191
|
+
options[:at] || [@document.bounds.left, @document.bounds.top]
|
|
192
|
+
).dup
|
|
193
|
+
@width = options[:width] ||
|
|
194
|
+
(@document.bounds.right - @at[0])
|
|
195
|
+
@height = options[:height] || default_height
|
|
196
|
+
@align = options[:align] ||
|
|
197
|
+
(@direction == :rtl ? :right : :left)
|
|
198
|
+
@vertical_align = options[:valign] || :top
|
|
199
|
+
@leading = options[:leading] || @document.default_leading
|
|
200
|
+
@character_spacing = options[:character_spacing] ||
|
|
201
|
+
@document.character_spacing
|
|
202
|
+
@mode = options[:mode] || @document.text_rendering_mode
|
|
203
|
+
@rotate = options[:rotate] || 0
|
|
204
|
+
@rotate_around = options[:rotate_around] || :upper_left
|
|
205
|
+
@single_line = options[:single_line]
|
|
206
|
+
@draw_text_callback = options[:draw_text_callback]
|
|
207
|
+
|
|
208
|
+
# if the text rendering mode is :unknown, force it back to :fill
|
|
209
|
+
if @mode == :unknown
|
|
210
|
+
@mode = :fill
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
if @overflow == :expand
|
|
214
|
+
# if set to expand, then we simply set the bottom
|
|
215
|
+
# as the bottom of the document bounds, since that
|
|
216
|
+
# is the maximum we should expand to
|
|
217
|
+
@height = default_height
|
|
218
|
+
@overflow = :truncate
|
|
219
|
+
end
|
|
220
|
+
@min_font_size = options[:min_font_size] || 5
|
|
221
|
+
if options[:kerning].nil?
|
|
222
|
+
options[:kerning] = @document.default_kerning?
|
|
223
|
+
end
|
|
224
|
+
@options = {
|
|
225
|
+
kerning: options[:kerning],
|
|
226
|
+
size: options[:size],
|
|
227
|
+
style: options[:style],
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
super(formatted_text, options)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Render text to the document based on the settings defined in
|
|
234
|
+
# constructor.
|
|
235
|
+
#
|
|
236
|
+
# In order to facilitate look-ahead calculations, this method accepts
|
|
237
|
+
# a `dry_run: true` option. If provided, then everything is executed as
|
|
238
|
+
# if rendering, with the exception that nothing is drawn on the page.
|
|
239
|
+
# Useful for look-ahead computations of height, unprinted text, etc.
|
|
240
|
+
#
|
|
241
|
+
# @param flags [Hash{Symbol => any}]
|
|
242
|
+
# @option flags :dry_run [Boolean] (false)
|
|
243
|
+
# Do not draw the text. Everything else is done.
|
|
244
|
+
# @return [Array<Hash>]
|
|
245
|
+
# A formatted text array representing any text that did not print
|
|
246
|
+
# under the current settings.
|
|
247
|
+
# @raise [Prawn::Text::Formatted::Arranger::BadFontFamily]
|
|
248
|
+
# If no font family is defined for the current font.
|
|
249
|
+
# @raise [Prawn::Errors::CannotFit]
|
|
250
|
+
# If not wide enough to print any text.
|
|
251
|
+
def render(flags = {})
|
|
252
|
+
unprinted_text = []
|
|
253
|
+
|
|
254
|
+
@document.save_font do
|
|
255
|
+
@document.character_spacing(@character_spacing) do
|
|
256
|
+
@document.text_rendering_mode(@mode) do
|
|
257
|
+
process_options
|
|
258
|
+
|
|
259
|
+
text = normalized_text(flags)
|
|
260
|
+
|
|
261
|
+
@document.font_size(@font_size) do
|
|
262
|
+
shrink_to_fit(text) if @overflow == :shrink_to_fit
|
|
263
|
+
process_vertical_alignment(text)
|
|
264
|
+
@inked = true unless flags[:dry_run]
|
|
265
|
+
unprinted_text =
|
|
266
|
+
if @rotate != 0 && @inked
|
|
267
|
+
render_rotated(text)
|
|
268
|
+
else
|
|
269
|
+
wrap(text)
|
|
270
|
+
end
|
|
271
|
+
@inked = false
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
unprinted_text.map do |e|
|
|
278
|
+
e.merge(text: @document.font.to_utf8(e[:text]))
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# The width available at this point in the box.
|
|
283
|
+
#
|
|
284
|
+
# @return [Number]
|
|
285
|
+
def available_width
|
|
286
|
+
@width
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# The height actually used during the previous {render}.
|
|
290
|
+
#
|
|
291
|
+
# @return [Number]
|
|
292
|
+
def height
|
|
293
|
+
return 0 if @baseline_y.nil? || @descender.nil?
|
|
294
|
+
|
|
295
|
+
(@baseline_y - @descender).abs
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# @private
|
|
299
|
+
# @param fragment [Prawn::Text::Formatted::Fragment]
|
|
300
|
+
# @param accumulated_width [Number]
|
|
301
|
+
# @param line_width [Number]
|
|
302
|
+
# @param word_spacing [Number]
|
|
303
|
+
# @return [void]
|
|
304
|
+
def draw_fragment(
|
|
305
|
+
fragment, accumulated_width = 0, line_width = 0, word_spacing = 0
|
|
306
|
+
)
|
|
307
|
+
case @align
|
|
308
|
+
when :left
|
|
309
|
+
x = @at[0]
|
|
310
|
+
when :center
|
|
311
|
+
x = @at[0] + (@width * 0.5) - (line_width * 0.5)
|
|
312
|
+
when :right
|
|
313
|
+
x = @at[0] + @width - line_width
|
|
314
|
+
when :justify
|
|
315
|
+
x =
|
|
316
|
+
if @direction == :ltr
|
|
317
|
+
@at[0]
|
|
318
|
+
else
|
|
319
|
+
@at[0] + @width - line_width
|
|
320
|
+
end
|
|
321
|
+
else
|
|
322
|
+
raise ArgumentError,
|
|
323
|
+
'align must be one of :left, :right, :center or :justify symbols'
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
x += accumulated_width
|
|
327
|
+
|
|
328
|
+
y = @at[1] + @baseline_y
|
|
329
|
+
|
|
330
|
+
y += fragment.y_offset
|
|
331
|
+
|
|
332
|
+
fragment.left = x
|
|
333
|
+
fragment.baseline = y
|
|
334
|
+
|
|
335
|
+
if @inked
|
|
336
|
+
draw_fragment_underlays(fragment)
|
|
337
|
+
|
|
338
|
+
@document.word_spacing(word_spacing) do
|
|
339
|
+
if @draw_text_callback
|
|
340
|
+
@draw_text_callback.call(
|
|
341
|
+
fragment.text,
|
|
342
|
+
at: [x, y],
|
|
343
|
+
kerning: @kerning,
|
|
344
|
+
)
|
|
345
|
+
else
|
|
346
|
+
@document.draw_text!(
|
|
347
|
+
fragment.text,
|
|
348
|
+
at: [x, y],
|
|
349
|
+
kerning: @kerning,
|
|
350
|
+
)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
draw_fragment_overlays(fragment)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# @group Extension API
|
|
359
|
+
|
|
360
|
+
# Text box extensions.
|
|
361
|
+
#
|
|
362
|
+
# Example:
|
|
363
|
+
#
|
|
364
|
+
# ```ruby
|
|
365
|
+
# module MyWrap
|
|
366
|
+
# def wrap(array)
|
|
367
|
+
# initialize_wrap([{ text: 'all your base are belong to us' }])
|
|
368
|
+
# @line_wrap.wrap_line(
|
|
369
|
+
# document: @document,
|
|
370
|
+
# kerning: @kerning,
|
|
371
|
+
# width: 10000,
|
|
372
|
+
# arranger: @arranger
|
|
373
|
+
# )
|
|
374
|
+
# fragment = @arranger.retrieve_fragment
|
|
375
|
+
# format_and_draw_fragment(fragment, 0, @line_wrap.width, 0)
|
|
376
|
+
# []
|
|
377
|
+
# end
|
|
378
|
+
# end
|
|
379
|
+
#
|
|
380
|
+
# Prawn::Text::Formatted::Box.extensions << MyWrap
|
|
381
|
+
#
|
|
382
|
+
# box = Prawn::Text::Formatted::Box.new('hello world')
|
|
383
|
+
# box.render("why can't I print anything other than" +
|
|
384
|
+
# '"all your base are belong to us"?')
|
|
385
|
+
# ```
|
|
386
|
+
#
|
|
387
|
+
# See {Prawn::Text::Formatted::Wrap} for what is required of the
|
|
388
|
+
# wrap method if you want to override the default wrapping algorithm.
|
|
389
|
+
#
|
|
390
|
+
# @return [Array<Module>]
|
|
391
|
+
def self.extensions
|
|
392
|
+
@extensions ||= []
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# @private
|
|
396
|
+
def self.inherited(base)
|
|
397
|
+
super
|
|
398
|
+
extensions.each { |e| base.extensions << e }
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# @private
|
|
402
|
+
def valid_options
|
|
403
|
+
PDF::Core::Text::VALID_OPTIONS + %i[
|
|
404
|
+
at
|
|
405
|
+
height width
|
|
406
|
+
align valign
|
|
407
|
+
rotate rotate_around
|
|
408
|
+
overflow min_font_size
|
|
409
|
+
disable_wrap_by_char
|
|
410
|
+
leading character_spacing
|
|
411
|
+
mode single_line
|
|
412
|
+
document
|
|
413
|
+
direction
|
|
414
|
+
fallback_fonts
|
|
415
|
+
draw_text_callback
|
|
416
|
+
]
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
private
|
|
420
|
+
|
|
421
|
+
def normalized_text(flags)
|
|
422
|
+
text = normalize_encoding
|
|
423
|
+
|
|
424
|
+
text.each { |t| t.delete(:color) } if flags[:dry_run]
|
|
425
|
+
|
|
426
|
+
text
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def original_text
|
|
430
|
+
@original_array.map(&:dup)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def original_text=(formatted_text)
|
|
434
|
+
@original_array = formatted_text
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def normalize_encoding
|
|
438
|
+
formatted_text = original_text
|
|
439
|
+
|
|
440
|
+
unless @fallback_fonts.empty?
|
|
441
|
+
formatted_text = process_fallback_fonts(formatted_text)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
formatted_text.each do |hash|
|
|
445
|
+
if hash[:font]
|
|
446
|
+
@document.font(hash[:font]) do
|
|
447
|
+
hash[:text] = @document.font.normalize_encoding(hash[:text])
|
|
448
|
+
end
|
|
449
|
+
else
|
|
450
|
+
hash[:text] = @document.font.normalize_encoding(hash[:text])
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
formatted_text
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def process_fallback_fonts(formatted_text)
|
|
458
|
+
modified_formatted_text = []
|
|
459
|
+
|
|
460
|
+
formatted_text.each do |hash|
|
|
461
|
+
fragments = analyze_glyphs_for_fallback_font_support(hash)
|
|
462
|
+
modified_formatted_text.concat(fragments)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
modified_formatted_text
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def analyze_glyphs_for_fallback_font_support(hash)
|
|
469
|
+
font_glyph_pairs = []
|
|
470
|
+
|
|
471
|
+
original_font = @document.font.family
|
|
472
|
+
fragment_font = hash[:font] || original_font
|
|
473
|
+
fragment_font_options =
|
|
474
|
+
(fragment_font_style = font_style(hash[:styles])) == :normal ? {} : { style: fragment_font_style }
|
|
475
|
+
|
|
476
|
+
fallback_fonts = @fallback_fonts.dup
|
|
477
|
+
# always default back to the current font if the glyph is missing from
|
|
478
|
+
# all fonts
|
|
479
|
+
fallback_fonts << fragment_font
|
|
480
|
+
|
|
481
|
+
@document.save_font do
|
|
482
|
+
hash[:text].each_char do |char|
|
|
483
|
+
font_glyph_pairs << [
|
|
484
|
+
find_font_for_this_glyph(
|
|
485
|
+
char,
|
|
486
|
+
fragment_font,
|
|
487
|
+
fallback_fonts.dup,
|
|
488
|
+
fragment_font_options,
|
|
489
|
+
),
|
|
490
|
+
char,
|
|
491
|
+
]
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Don't add a :font to fragments if it wasn't there originally
|
|
496
|
+
if hash[:font].nil?
|
|
497
|
+
font_glyph_pairs.each do |pair|
|
|
498
|
+
pair[0] = nil if pair[0] == original_font
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def font_style(styles)
|
|
506
|
+
if styles
|
|
507
|
+
if styles.include?(:bold)
|
|
508
|
+
styles.include?(:italic) ? :bold_italic : :bold
|
|
509
|
+
elsif styles.include?(:italic)
|
|
510
|
+
:italic
|
|
511
|
+
else
|
|
512
|
+
:normal
|
|
513
|
+
end
|
|
514
|
+
else
|
|
515
|
+
:normal
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def find_font_for_this_glyph(char, current_font, fallback_fonts, current_font_options = {})
|
|
520
|
+
@document.font(current_font, current_font_options)
|
|
521
|
+
if fallback_fonts.empty? || @document.font.glyph_present?(char)
|
|
522
|
+
current_font
|
|
523
|
+
else
|
|
524
|
+
find_font_for_this_glyph(char, fallback_fonts.shift, fallback_fonts, current_font_options)
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def form_fragments_from_like_font_glyph_pairs(font_glyph_pairs, hash)
|
|
529
|
+
fragments = []
|
|
530
|
+
fragment = nil
|
|
531
|
+
current_font = nil
|
|
532
|
+
|
|
533
|
+
font_glyph_pairs.each do |font, char|
|
|
534
|
+
if font != current_font || fragments.count.zero?
|
|
535
|
+
current_font = font
|
|
536
|
+
fragment = hash.dup
|
|
537
|
+
fragment[:text] = char
|
|
538
|
+
fragment[:font] = font unless font.nil?
|
|
539
|
+
fragments << fragment
|
|
540
|
+
else
|
|
541
|
+
fragment[:text] += char
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
fragments
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def move_baseline_down
|
|
549
|
+
if @baseline_y.zero?
|
|
550
|
+
@baseline_y = -@ascender
|
|
551
|
+
else
|
|
552
|
+
@baseline_y -= (@line_height + @leading)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Returns the default height to be used if none is provided or if the
|
|
557
|
+
# overflow option is set to :expand. If we are in a stretchy bounding
|
|
558
|
+
# box, assume we can stretch to the bottom of the innermost non-stretchy
|
|
559
|
+
# box.
|
|
560
|
+
#
|
|
561
|
+
def default_height
|
|
562
|
+
# Find the "frame", the innermost non-stretchy bbox.
|
|
563
|
+
frame = @document.bounds
|
|
564
|
+
frame = frame.parent while frame.stretchy? && frame.parent
|
|
565
|
+
|
|
566
|
+
@at[1] + @document.bounds.absolute_bottom - frame.absolute_bottom
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def process_vertical_alignment(text)
|
|
570
|
+
# The vertical alignment must only be done once per text box, but
|
|
571
|
+
# we need to wait until render() is called so that the fonts are set
|
|
572
|
+
# up properly for wrapping. So guard with a boolean to ensure this is
|
|
573
|
+
# only run once.
|
|
574
|
+
if defined?(@vertical_alignment_processed) &&
|
|
575
|
+
@vertical_alignment_processed
|
|
576
|
+
return
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
@vertical_alignment_processed = true
|
|
580
|
+
|
|
581
|
+
return if @vertical_align == :top
|
|
582
|
+
|
|
583
|
+
wrap(text)
|
|
584
|
+
|
|
585
|
+
case @vertical_align
|
|
586
|
+
when :center
|
|
587
|
+
@at[1] -= (@height - height + @descender) * 0.5
|
|
588
|
+
when :bottom
|
|
589
|
+
@at[1] -= (@height - height)
|
|
590
|
+
else
|
|
591
|
+
raise ArgumentError,
|
|
592
|
+
'valign must be one of :left, :right or :center symbols'
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
@height = height
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
# Decrease the font size until the text fits or the min font
|
|
599
|
+
# size is reached
|
|
600
|
+
def shrink_to_fit(text)
|
|
601
|
+
loop do
|
|
602
|
+
if @disable_wrap_by_char && @font_size > @min_font_size
|
|
603
|
+
begin
|
|
604
|
+
wrap(text)
|
|
605
|
+
rescue Errors::CannotFit
|
|
606
|
+
# Ignore errors while we can still attempt smaller
|
|
607
|
+
# font sizes.
|
|
608
|
+
end
|
|
609
|
+
else
|
|
610
|
+
wrap(text)
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
break if @everything_printed || @font_size <= @min_font_size
|
|
614
|
+
|
|
615
|
+
@font_size = [@font_size - 0.5, @min_font_size].max
|
|
616
|
+
@document.font_size = @font_size
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def process_options
|
|
621
|
+
# must be performed within a save_font block because
|
|
622
|
+
# document.process_text_options sets the font
|
|
623
|
+
@document.process_text_options(@options)
|
|
624
|
+
@font_size = @options[:size]
|
|
625
|
+
@kerning = @options[:kerning]
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def render_rotated(text)
|
|
629
|
+
unprinted_text = ''
|
|
630
|
+
|
|
631
|
+
case @rotate_around
|
|
632
|
+
when :center
|
|
633
|
+
x = @at[0] + (@width * 0.5)
|
|
634
|
+
y = @at[1] - (@height * 0.5)
|
|
635
|
+
when :upper_right
|
|
636
|
+
x = @at[0] + @width
|
|
637
|
+
y = @at[1]
|
|
638
|
+
when :lower_right
|
|
639
|
+
x = @at[0] + @width
|
|
640
|
+
y = @at[1] - @height
|
|
641
|
+
when :lower_left
|
|
642
|
+
x = @at[0]
|
|
643
|
+
y = @at[1] - @height
|
|
644
|
+
else
|
|
645
|
+
x = @at[0]
|
|
646
|
+
y = @at[1]
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
@document.rotate(@rotate, origin: [x, y]) do
|
|
650
|
+
unprinted_text = wrap(text)
|
|
651
|
+
end
|
|
652
|
+
unprinted_text
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def draw_fragment_underlays(fragment)
|
|
656
|
+
fragment.callback_objects.each do |obj|
|
|
657
|
+
obj.render_behind(fragment) if obj.respond_to?(:render_behind)
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
661
|
+
def draw_fragment_overlays(fragment)
|
|
662
|
+
draw_fragment_overlay_styles(fragment)
|
|
663
|
+
draw_fragment_overlay_link(fragment)
|
|
664
|
+
draw_fragment_overlay_anchor(fragment)
|
|
665
|
+
draw_fragment_overlay_local(fragment)
|
|
666
|
+
fragment.callback_objects.each do |obj|
|
|
667
|
+
obj.render_in_front(fragment) if obj.respond_to?(:render_in_front)
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def draw_fragment_overlay_link(fragment)
|
|
672
|
+
return unless fragment.link
|
|
673
|
+
|
|
674
|
+
box = fragment.absolute_bounding_box
|
|
675
|
+
@document.link_annotation(
|
|
676
|
+
box,
|
|
677
|
+
Border: [0, 0, 0],
|
|
678
|
+
A: {
|
|
679
|
+
Type: :Action,
|
|
680
|
+
S: :URI,
|
|
681
|
+
URI: PDF::Core::LiteralString.new(fragment.link),
|
|
682
|
+
},
|
|
683
|
+
)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def draw_fragment_overlay_anchor(fragment)
|
|
687
|
+
return unless fragment.anchor
|
|
688
|
+
|
|
689
|
+
box = fragment.absolute_bounding_box
|
|
690
|
+
@document.link_annotation(
|
|
691
|
+
box,
|
|
692
|
+
Border: [0, 0, 0],
|
|
693
|
+
Dest: fragment.anchor,
|
|
694
|
+
)
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def draw_fragment_overlay_local(fragment)
|
|
698
|
+
return unless fragment.local
|
|
699
|
+
|
|
700
|
+
box = fragment.absolute_bounding_box
|
|
701
|
+
@document.link_annotation(
|
|
702
|
+
box,
|
|
703
|
+
Border: [0, 0, 0],
|
|
704
|
+
A: {
|
|
705
|
+
Type: :Action,
|
|
706
|
+
S: :Launch,
|
|
707
|
+
F: PDF::Core::LiteralString.new(fragment.local),
|
|
708
|
+
NewWindow: true,
|
|
709
|
+
},
|
|
710
|
+
)
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
def draw_fragment_overlay_styles(fragment)
|
|
714
|
+
if fragment.styles.include?(:underline)
|
|
715
|
+
@document.stroke_line(fragment.underline_points)
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
if fragment.styles.include?(:strikethrough)
|
|
719
|
+
@document.stroke_line(fragment.strikethrough_points)
|
|
720
|
+
end
|
|
721
|
+
end
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
end
|