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,373 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
module Text
|
|
5
|
+
module Formatted
|
|
6
|
+
# Prawn::Text::Formatted::Fragment is a state store for a formatted text
|
|
7
|
+
# fragment. It does not render anything.
|
|
8
|
+
#
|
|
9
|
+
# @private
|
|
10
|
+
class Fragment
|
|
11
|
+
attr_reader :format_state
|
|
12
|
+
attr_reader :text
|
|
13
|
+
attr_writer :width
|
|
14
|
+
attr_accessor :line_height
|
|
15
|
+
attr_accessor :descender
|
|
16
|
+
attr_accessor :ascender
|
|
17
|
+
attr_accessor :word_spacing
|
|
18
|
+
attr_accessor :left
|
|
19
|
+
attr_accessor :baseline
|
|
20
|
+
|
|
21
|
+
# @param text [String]
|
|
22
|
+
# @param format_state [Hash{Symbol => any}]
|
|
23
|
+
# @param document [Prawn::Documnt]
|
|
24
|
+
def initialize(text, format_state, document)
|
|
25
|
+
@format_state = format_state
|
|
26
|
+
@document = document
|
|
27
|
+
@word_spacing = 0
|
|
28
|
+
|
|
29
|
+
# keep the original value of "text", so we can reinitialize @text if
|
|
30
|
+
# formatting parameters like text direction are changed
|
|
31
|
+
@original_text = text
|
|
32
|
+
@text = process_text(@original_text)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Width of fragment.
|
|
36
|
+
#
|
|
37
|
+
# @return [Number]
|
|
38
|
+
def width
|
|
39
|
+
if @word_spacing.zero? then @width
|
|
40
|
+
else
|
|
41
|
+
@width + (@word_spacing * space_count)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Height of fragment.
|
|
46
|
+
#
|
|
47
|
+
# @return [Number]
|
|
48
|
+
def height
|
|
49
|
+
top - bottom
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Is this a subscript fragment?
|
|
53
|
+
#
|
|
54
|
+
# @return [Boolean]
|
|
55
|
+
def subscript?
|
|
56
|
+
styles.include?(:subscript)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Is this a superscript fragment?
|
|
60
|
+
#
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def superscript?
|
|
63
|
+
styles.include?(:superscript)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Vertical offset of the fragment.
|
|
67
|
+
#
|
|
68
|
+
# @return [Number]
|
|
69
|
+
def y_offset
|
|
70
|
+
if subscript? then -descender
|
|
71
|
+
elsif superscript? then 0.85 * ascender
|
|
72
|
+
else
|
|
73
|
+
0
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Fragment bounding box, relative to the containing bounding box.
|
|
78
|
+
#
|
|
79
|
+
# @return [Array(Number, Number, Number, Number)]
|
|
80
|
+
def bounding_box
|
|
81
|
+
[left, bottom, right, top]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Fragment bounding box, relative to the containing page.
|
|
85
|
+
#
|
|
86
|
+
# @return [Array(Number, Number, Number, Number)]
|
|
87
|
+
def absolute_bounding_box
|
|
88
|
+
box = bounding_box
|
|
89
|
+
box[0] += @document.bounds.absolute_left
|
|
90
|
+
box[2] += @document.bounds.absolute_left
|
|
91
|
+
box[1] += @document.bounds.absolute_bottom
|
|
92
|
+
box[3] += @document.bounds.absolute_bottom
|
|
93
|
+
box
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Underline endpoints.
|
|
97
|
+
#
|
|
98
|
+
# @return [Array(Array(Number, Number), Array(Number, Number))]
|
|
99
|
+
def underline_points
|
|
100
|
+
y = baseline - 1.25
|
|
101
|
+
[[left, y], [right, y]]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Strikethrough endpoints.
|
|
105
|
+
#
|
|
106
|
+
# @return [Array(Array(Number, Number), Array(Number, Number))]
|
|
107
|
+
def strikethrough_points
|
|
108
|
+
y = baseline + (ascender * 0.3)
|
|
109
|
+
[[left, y], [right, y]]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Fragment font styles.
|
|
113
|
+
#
|
|
114
|
+
# @return [Array<Symbol>]
|
|
115
|
+
def styles
|
|
116
|
+
@format_state[:styles] || []
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Fragment link.
|
|
120
|
+
#
|
|
121
|
+
# @return [String, nil]
|
|
122
|
+
def link
|
|
123
|
+
@format_state[:link]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Anchor.
|
|
127
|
+
#
|
|
128
|
+
# @return [PDF::Core::Reference, Array, Hash]
|
|
129
|
+
def anchor
|
|
130
|
+
@format_state[:anchor]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Local destination.
|
|
134
|
+
#
|
|
135
|
+
# @return [String]
|
|
136
|
+
def local
|
|
137
|
+
@format_state[:local]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Fragment color.
|
|
141
|
+
#
|
|
142
|
+
# @return [Color]
|
|
143
|
+
def color
|
|
144
|
+
@format_state[:color]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Fragment font name.
|
|
148
|
+
#
|
|
149
|
+
# @return [String]
|
|
150
|
+
def font
|
|
151
|
+
@format_state[:font]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Font size.
|
|
155
|
+
#
|
|
156
|
+
# @return [Number]
|
|
157
|
+
def size
|
|
158
|
+
@format_state[:size]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Character spacing.
|
|
162
|
+
#
|
|
163
|
+
# @return [Number]
|
|
164
|
+
def character_spacing
|
|
165
|
+
@format_state[:character_spacing] ||
|
|
166
|
+
@document.character_spacing
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Text direction.
|
|
170
|
+
#
|
|
171
|
+
# @return [:ltr, :rtl]
|
|
172
|
+
def direction
|
|
173
|
+
@format_state[:direction]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Set default text direction.
|
|
177
|
+
#
|
|
178
|
+
# @param direction [:ltr, :rtl]
|
|
179
|
+
# @return [void]
|
|
180
|
+
def default_direction=(direction)
|
|
181
|
+
unless @format_state[:direction]
|
|
182
|
+
@format_state[:direction] = direction
|
|
183
|
+
@text = process_text(@original_text)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Keep trailing spaces.
|
|
188
|
+
#
|
|
189
|
+
# @return [void]
|
|
190
|
+
def include_trailing_white_space!
|
|
191
|
+
@format_state.delete(:exclude_trailing_white_space)
|
|
192
|
+
@text = process_text(@original_text)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Number of spaces in the text.
|
|
196
|
+
#
|
|
197
|
+
# @return [Integer]
|
|
198
|
+
def space_count
|
|
199
|
+
@text.count(' ')
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Callbacks.
|
|
203
|
+
#
|
|
204
|
+
# @return [Array]
|
|
205
|
+
def callback_objects
|
|
206
|
+
callback = @format_state[:callback]
|
|
207
|
+
if callback.nil?
|
|
208
|
+
[]
|
|
209
|
+
elsif callback.is_a?(Array)
|
|
210
|
+
callback
|
|
211
|
+
else
|
|
212
|
+
[callback]
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Horizontal coordinate of the right side of the fragment.
|
|
217
|
+
#
|
|
218
|
+
# @return [Number]
|
|
219
|
+
def right
|
|
220
|
+
left + width
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Vertical coordinate of the top side of the fragment.
|
|
224
|
+
#
|
|
225
|
+
# @return [Number]
|
|
226
|
+
def top
|
|
227
|
+
baseline + ascender
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Vertical coordinate of the bottom side of the fragment.
|
|
231
|
+
#
|
|
232
|
+
# @return [Number]
|
|
233
|
+
def bottom
|
|
234
|
+
baseline - descender
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Coordinates of the top left corner of the fragment.
|
|
238
|
+
#
|
|
239
|
+
# @return [Array(Number, Number)]
|
|
240
|
+
def top_left
|
|
241
|
+
[left, top]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Coordinates of the top right corner of the fragment.
|
|
245
|
+
#
|
|
246
|
+
# @return [Array(Number, Number)]
|
|
247
|
+
def top_right
|
|
248
|
+
[right, top]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Coordinates of the bottom right corner of the fragment.
|
|
252
|
+
#
|
|
253
|
+
# @return [Array(Number, Number)]
|
|
254
|
+
def bottom_right
|
|
255
|
+
[right, bottom]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Coordinates of the bottom left corner of the fragment.
|
|
259
|
+
#
|
|
260
|
+
# @return [Array(Number, Number)]
|
|
261
|
+
def bottom_left
|
|
262
|
+
[left, bottom]
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Absolute horizontal coordinate of the left side of the fragment.
|
|
266
|
+
#
|
|
267
|
+
# @return [Number]
|
|
268
|
+
def absolute_left
|
|
269
|
+
absolute_bounding_box[0]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Absolute horizontal coordinate of the right side of the fragment.
|
|
273
|
+
#
|
|
274
|
+
# @return [Number]
|
|
275
|
+
def absolute_right
|
|
276
|
+
absolute_bounding_box[2]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Absolute vertical coordinate of the top side of the fragment.
|
|
280
|
+
#
|
|
281
|
+
# @return [Number]
|
|
282
|
+
def absolute_top
|
|
283
|
+
absolute_bounding_box[3]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Absolute vertical coordinate of the bottom side of the fragment.
|
|
287
|
+
#
|
|
288
|
+
# @return [Number]
|
|
289
|
+
def absolute_bottom
|
|
290
|
+
absolute_bounding_box[1]
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Absolute coordinates of the top left corner of the fragment.
|
|
294
|
+
#
|
|
295
|
+
# @return [Array(Number, Number)]
|
|
296
|
+
def absolute_top_left
|
|
297
|
+
[absolute_left, absolute_top]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Absolute coordinates of the top right corner of the fragment.
|
|
301
|
+
#
|
|
302
|
+
# @return [Array(Number, Number)]
|
|
303
|
+
def absolute_top_right
|
|
304
|
+
[absolute_right, absolute_top]
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Absolute coordinates of the bottom left corner of the fragment.
|
|
308
|
+
#
|
|
309
|
+
# @return [Array(Number, Number)]
|
|
310
|
+
def absolute_bottom_left
|
|
311
|
+
[absolute_left, absolute_bottom]
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Absolute coordinates of the bottom right corner of the fragment.
|
|
315
|
+
#
|
|
316
|
+
# @return [Array(Number, Number)]
|
|
317
|
+
def absolute_bottom_right
|
|
318
|
+
[absolute_right, absolute_bottom]
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
private
|
|
322
|
+
|
|
323
|
+
def process_text(text)
|
|
324
|
+
string = strip_zero_width_spaces(text)
|
|
325
|
+
|
|
326
|
+
if exclude_trailing_white_space?
|
|
327
|
+
string = string.rstrip
|
|
328
|
+
|
|
329
|
+
if soft_hyphens_need_processing?(string)
|
|
330
|
+
string = process_soft_hyphens(string[0..-2]) + string[-1..]
|
|
331
|
+
end
|
|
332
|
+
elsif soft_hyphens_need_processing?(string)
|
|
333
|
+
string = process_soft_hyphens(string)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
if direction == :rtl
|
|
337
|
+
string.reverse
|
|
338
|
+
else
|
|
339
|
+
string
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def exclude_trailing_white_space?
|
|
344
|
+
@format_state[:exclude_trailing_white_space]
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def soft_hyphens_need_processing?(string)
|
|
348
|
+
!string.empty? && normalized_soft_hyphen
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def normalized_soft_hyphen
|
|
352
|
+
@format_state[:normalized_soft_hyphen]
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def process_soft_hyphens(string)
|
|
356
|
+
if string.encoding != normalized_soft_hyphen.encoding
|
|
357
|
+
string.force_encoding(normalized_soft_hyphen.encoding)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
string.gsub(normalized_soft_hyphen, '')
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def strip_zero_width_spaces(string)
|
|
364
|
+
if string.encoding == ::Encoding::UTF_8
|
|
365
|
+
string.gsub(Prawn::Text::ZWSP, '')
|
|
366
|
+
else
|
|
367
|
+
string
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prawn
|
|
4
|
+
module Text
|
|
5
|
+
module Formatted
|
|
6
|
+
# Implements individual line wrapping of formatted text.
|
|
7
|
+
#
|
|
8
|
+
# @private
|
|
9
|
+
class LineWrap
|
|
10
|
+
# The width of the last wrapped line.
|
|
11
|
+
#
|
|
12
|
+
# @return [Number]
|
|
13
|
+
def width
|
|
14
|
+
@accumulated_width || 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The number of spaces in the last wrapped line.
|
|
18
|
+
# @return [Integer]
|
|
19
|
+
attr_reader :space_count
|
|
20
|
+
|
|
21
|
+
# Whether this line is the last line in the paragraph.
|
|
22
|
+
#
|
|
23
|
+
# @return [Boolean]
|
|
24
|
+
def paragraph_finished?
|
|
25
|
+
@newline_encountered || next_string_newline? || @arranger.finished?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Break the fragment into tokens.
|
|
29
|
+
#
|
|
30
|
+
# @param fragment [String]
|
|
31
|
+
# @return [Array<String>]
|
|
32
|
+
def tokenize(fragment)
|
|
33
|
+
fragment.scan(scan_pattern(fragment.encoding))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Work in conjunction with the {Prawn::Text::Formatted::Arranger}
|
|
37
|
+
# defined in the `:arranger` option to determine what formatted text
|
|
38
|
+
# will fit within the width defined by the `:width` option.
|
|
39
|
+
#
|
|
40
|
+
# @param options [Hash{Symbol => any}]
|
|
41
|
+
# @option options :document [Prawn::Document]
|
|
42
|
+
# @option options :kerning [Boolean]
|
|
43
|
+
# @option options :width [Number]
|
|
44
|
+
# @option options :disable_wrap_by_char [Boolean]
|
|
45
|
+
# @option options :arranger [Prawn::Text::Formatted::Arranger]
|
|
46
|
+
# @return [String]
|
|
47
|
+
def wrap_line(options)
|
|
48
|
+
initialize_line(options)
|
|
49
|
+
|
|
50
|
+
# rubocop: disable Lint/AssignmentInCondition
|
|
51
|
+
while fragment = @arranger.next_string
|
|
52
|
+
# rubocop: enable Lint/AssignmentInCondition
|
|
53
|
+
@fragment_output = +''
|
|
54
|
+
|
|
55
|
+
fragment.lstrip! if first_fragment_on_this_line?(fragment)
|
|
56
|
+
next if empty_line?(fragment)
|
|
57
|
+
|
|
58
|
+
unless apply_font_settings_and_add_fragment_to_line(fragment)
|
|
59
|
+
break
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
@arranger.finalize_line
|
|
63
|
+
@accumulated_width = @arranger.line_width
|
|
64
|
+
@space_count = @arranger.space_count
|
|
65
|
+
@arranger.line
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def first_fragment_on_this_line?(fragment)
|
|
71
|
+
line_empty? && fragment != "\n"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def empty_line?(fragment)
|
|
75
|
+
empty = line_empty? && fragment.empty? && next_string_newline?
|
|
76
|
+
if empty
|
|
77
|
+
@arranger.update_last_string('', '', soft_hyphen(fragment.encoding))
|
|
78
|
+
end
|
|
79
|
+
empty
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def next_string_newline?
|
|
83
|
+
@arranger.preview_next_string == "\n"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def apply_font_settings_and_add_fragment_to_line(fragment)
|
|
87
|
+
result = nil
|
|
88
|
+
@arranger.apply_font_settings do
|
|
89
|
+
result = add_fragment_to_line(fragment)
|
|
90
|
+
end
|
|
91
|
+
result
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# returns true if all text was printed without running into the end of
|
|
95
|
+
# the line
|
|
96
|
+
#
|
|
97
|
+
def add_fragment_to_line(fragment)
|
|
98
|
+
case fragment
|
|
99
|
+
when ''
|
|
100
|
+
true
|
|
101
|
+
when "\n"
|
|
102
|
+
@newline_encountered = true
|
|
103
|
+
false
|
|
104
|
+
else
|
|
105
|
+
tokenize(fragment).each do |segment|
|
|
106
|
+
segment_width =
|
|
107
|
+
if segment == zero_width_space(segment.encoding)
|
|
108
|
+
0
|
|
109
|
+
else
|
|
110
|
+
@document.width_of(segment, kerning: @kerning)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if @accumulated_width + segment_width <= @width
|
|
114
|
+
@accumulated_width += segment_width
|
|
115
|
+
shy = soft_hyphen(segment.encoding)
|
|
116
|
+
if segment[-1] == shy
|
|
117
|
+
sh_width = @document.width_of(shy, kerning: @kerning)
|
|
118
|
+
@accumulated_width -= sh_width
|
|
119
|
+
end
|
|
120
|
+
@fragment_output += segment
|
|
121
|
+
else
|
|
122
|
+
if @accumulated_width.zero? && @line_contains_more_than_one_word
|
|
123
|
+
@line_contains_more_than_one_word = false
|
|
124
|
+
end
|
|
125
|
+
end_of_the_line_reached(segment)
|
|
126
|
+
fragment_finished(fragment)
|
|
127
|
+
return false
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
fragment_finished(fragment)
|
|
132
|
+
true
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# The pattern used to determine chunks of text to place on a given line
|
|
137
|
+
#
|
|
138
|
+
def scan_pattern(encoding = ::Encoding::UTF_8)
|
|
139
|
+
ebc = break_chars(encoding)
|
|
140
|
+
eshy = soft_hyphen(encoding)
|
|
141
|
+
ehy = hyphen(encoding)
|
|
142
|
+
ews = whitespace(encoding)
|
|
143
|
+
|
|
144
|
+
patterns = [
|
|
145
|
+
"[^#{ebc}]+#{eshy}",
|
|
146
|
+
"[^#{ebc}]+#{ehy}+",
|
|
147
|
+
"[^#{ebc}]+",
|
|
148
|
+
"[#{ews}]+",
|
|
149
|
+
"#{ehy}+[^#{ebc}]*",
|
|
150
|
+
eshy.to_s,
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
pattern = patterns
|
|
154
|
+
.map { |p| p.encode(encoding) }
|
|
155
|
+
.join('|')
|
|
156
|
+
|
|
157
|
+
Regexp.new(pattern)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The pattern used to determine whether any word breaks exist on a
|
|
161
|
+
# current line, which in turn determines whether character level
|
|
162
|
+
# word breaking is needed
|
|
163
|
+
#
|
|
164
|
+
def word_division_scan_pattern(encoding = ::Encoding::UTF_8)
|
|
165
|
+
common_whitespaces =
|
|
166
|
+
["\t", "\n", "\v", "\r", ' '].map { |c|
|
|
167
|
+
c.encode(encoding)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Regexp.union(
|
|
171
|
+
common_whitespaces +
|
|
172
|
+
[
|
|
173
|
+
zero_width_space(encoding),
|
|
174
|
+
soft_hyphen(encoding),
|
|
175
|
+
hyphen(encoding),
|
|
176
|
+
].compact,
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def soft_hyphen(encoding = ::Encoding::UTF_8)
|
|
181
|
+
Prawn::Text::SHY.encode(encoding)
|
|
182
|
+
rescue ::Encoding::InvalidByteSequenceError,
|
|
183
|
+
::Encoding::UndefinedConversionError
|
|
184
|
+
nil
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def break_chars(encoding = ::Encoding::UTF_8)
|
|
188
|
+
[
|
|
189
|
+
whitespace(encoding),
|
|
190
|
+
soft_hyphen(encoding),
|
|
191
|
+
hyphen(encoding),
|
|
192
|
+
].join('')
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def zero_width_space(encoding = ::Encoding::UTF_8)
|
|
196
|
+
Prawn::Text::ZWSP.encode(encoding)
|
|
197
|
+
rescue ::Encoding::InvalidByteSequenceError,
|
|
198
|
+
::Encoding::UndefinedConversionError
|
|
199
|
+
nil
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def whitespace(encoding = ::Encoding::UTF_8)
|
|
203
|
+
"\s\t#{zero_width_space(encoding)}".encode(encoding)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def hyphen(_encoding = ::Encoding::UTF_8)
|
|
207
|
+
'-'
|
|
208
|
+
rescue ::Encoding::InvalidByteSequenceError,
|
|
209
|
+
::Encoding::UndefinedConversionError
|
|
210
|
+
nil
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def line_empty?
|
|
214
|
+
@line_empty && @accumulated_width.zero?
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def initialize_line(options)
|
|
218
|
+
@document = options[:document]
|
|
219
|
+
@kerning = options[:kerning]
|
|
220
|
+
@width = options[:width]
|
|
221
|
+
|
|
222
|
+
@disable_wrap_by_char = options[:disable_wrap_by_char]
|
|
223
|
+
|
|
224
|
+
@accumulated_width = 0
|
|
225
|
+
@line_empty = true
|
|
226
|
+
@line_contains_more_than_one_word = false
|
|
227
|
+
|
|
228
|
+
@arranger = options[:arranger]
|
|
229
|
+
@arranger.initialize_line
|
|
230
|
+
|
|
231
|
+
@newline_encountered = false
|
|
232
|
+
@line_full = false
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def fragment_finished(fragment)
|
|
236
|
+
if fragment == "\n"
|
|
237
|
+
@newline_encountered = true
|
|
238
|
+
@line_empty = false
|
|
239
|
+
else
|
|
240
|
+
update_output_based_on_last_fragment(
|
|
241
|
+
fragment,
|
|
242
|
+
soft_hyphen(fragment.encoding),
|
|
243
|
+
)
|
|
244
|
+
update_line_status_based_on_last_output
|
|
245
|
+
pull_preceding_fragment_to_join_this_one?(fragment)
|
|
246
|
+
end
|
|
247
|
+
remember_this_fragment_for_backward_looking_ops
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def update_output_based_on_last_fragment(
|
|
251
|
+
fragment, normalized_soft_hyphen = nil
|
|
252
|
+
)
|
|
253
|
+
remaining_text =
|
|
254
|
+
fragment.slice(@fragment_output.length..fragment.length)
|
|
255
|
+
if line_finished? && line_empty? && @fragment_output.empty? &&
|
|
256
|
+
!fragment.strip.empty?
|
|
257
|
+
raise Prawn::Errors::CannotFit
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
@arranger.update_last_string(
|
|
261
|
+
@fragment_output,
|
|
262
|
+
remaining_text,
|
|
263
|
+
normalized_soft_hyphen,
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def pull_preceding_fragment_to_join_this_one?(current_fragment)
|
|
268
|
+
if @fragment_output.empty? && !current_fragment.empty? &&
|
|
269
|
+
@line_contains_more_than_one_word &&
|
|
270
|
+
!(previous_fragment_ended_with_breakable? ||
|
|
271
|
+
fragment_begins_with_breakable?(current_fragment))
|
|
272
|
+
@fragment_output = @previous_fragment_output_without_last_word
|
|
273
|
+
update_output_based_on_last_fragment(@previous_fragment)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def remember_this_fragment_for_backward_looking_ops
|
|
278
|
+
@previous_fragment = @fragment_output.dup
|
|
279
|
+
pf = @previous_fragment
|
|
280
|
+
@previous_fragment_ended_with_breakable =
|
|
281
|
+
pf =~ /[#{break_chars(pf.encoding)}]$/
|
|
282
|
+
last_word = pf.slice(/[^#{break_chars(pf.encoding)}]*$/)
|
|
283
|
+
last_word_length = last_word.nil? ? 0 : last_word.length
|
|
284
|
+
@previous_fragment_output_without_last_word =
|
|
285
|
+
pf.slice(0, pf.length - last_word_length)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def previous_fragment_ended_with_breakable?
|
|
289
|
+
@previous_fragment_ended_with_breakable
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def fragment_begins_with_breakable?(fragment)
|
|
293
|
+
fragment =~ /^[#{break_chars(fragment.encoding)}]/
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def line_finished?
|
|
297
|
+
@line_full || paragraph_finished?
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def update_line_status_based_on_last_output
|
|
301
|
+
if @fragment_output&.match?(word_division_scan_pattern(@fragment_output.encoding))
|
|
302
|
+
@line_contains_more_than_one_word = true
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def end_of_the_line_reached(segment)
|
|
307
|
+
update_line_status_based_on_last_output
|
|
308
|
+
unless @disable_wrap_by_char || @line_contains_more_than_one_word
|
|
309
|
+
wrap_by_char(segment)
|
|
310
|
+
end
|
|
311
|
+
@line_full = true
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def wrap_by_char(segment)
|
|
315
|
+
segment.each_char do |char|
|
|
316
|
+
break unless append_char(char)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def append_char(char)
|
|
321
|
+
# kerning doesn't make sense in the context of a single character
|
|
322
|
+
char_width = @document.width_of(char)
|
|
323
|
+
|
|
324
|
+
if @accumulated_width + char_width <= @width
|
|
325
|
+
@accumulated_width += char_width
|
|
326
|
+
@fragment_output << char
|
|
327
|
+
true
|
|
328
|
+
else
|
|
329
|
+
false
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|