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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/giga-fast-kit.gemspec +12 -0
  3. data/prawn-2.5.0/COPYING +2 -0
  4. data/prawn-2.5.0/GPLv2 +339 -0
  5. data/prawn-2.5.0/GPLv3 +674 -0
  6. data/prawn-2.5.0/LICENSE +56 -0
  7. data/prawn-2.5.0/data/fonts/Courier-Bold.afm +342 -0
  8. data/prawn-2.5.0/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/prawn-2.5.0/data/fonts/Courier-Oblique.afm +342 -0
  10. data/prawn-2.5.0/data/fonts/Courier.afm +342 -0
  11. data/prawn-2.5.0/data/fonts/Helvetica-Bold.afm +2827 -0
  12. data/prawn-2.5.0/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  13. data/prawn-2.5.0/data/fonts/Helvetica-Oblique.afm +3051 -0
  14. data/prawn-2.5.0/data/fonts/Helvetica.afm +3051 -0
  15. data/prawn-2.5.0/data/fonts/MustRead.html +19 -0
  16. data/prawn-2.5.0/data/fonts/Symbol.afm +213 -0
  17. data/prawn-2.5.0/data/fonts/Times-Bold.afm +2588 -0
  18. data/prawn-2.5.0/data/fonts/Times-BoldItalic.afm +2384 -0
  19. data/prawn-2.5.0/data/fonts/Times-Italic.afm +2667 -0
  20. data/prawn-2.5.0/data/fonts/Times-Roman.afm +2419 -0
  21. data/prawn-2.5.0/data/fonts/ZapfDingbats.afm +225 -0
  22. data/prawn-2.5.0/lib/prawn/document/bounding_box.rb +625 -0
  23. data/prawn-2.5.0/lib/prawn/document/column_box.rb +177 -0
  24. data/prawn-2.5.0/lib/prawn/document/internals.rb +76 -0
  25. data/prawn-2.5.0/lib/prawn/document/span.rb +65 -0
  26. data/prawn-2.5.0/lib/prawn/document.rb +846 -0
  27. data/prawn-2.5.0/lib/prawn/encoding.rb +85 -0
  28. data/prawn-2.5.0/lib/prawn/errors.rb +73 -0
  29. data/prawn-2.5.0/lib/prawn/font.rb +567 -0
  30. data/prawn-2.5.0/lib/prawn/font_metric_cache.rb +50 -0
  31. data/prawn-2.5.0/lib/prawn/fonts/afm.rb +332 -0
  32. data/prawn-2.5.0/lib/prawn/fonts/dfont.rb +42 -0
  33. data/prawn-2.5.0/lib/prawn/fonts/otf.rb +14 -0
  34. data/prawn-2.5.0/lib/prawn/fonts/to_unicode_cmap.rb +151 -0
  35. data/prawn-2.5.0/lib/prawn/fonts/ttc.rb +41 -0
  36. data/prawn-2.5.0/lib/prawn/fonts/ttf.rb +615 -0
  37. data/prawn-2.5.0/lib/prawn/fonts.rb +14 -0
  38. data/prawn-2.5.0/lib/prawn/graphics/blend_mode.rb +62 -0
  39. data/prawn-2.5.0/lib/prawn/graphics/cap_style.rb +44 -0
  40. data/prawn-2.5.0/lib/prawn/graphics/color.rb +257 -0
  41. data/prawn-2.5.0/lib/prawn/graphics/dash.rb +123 -0
  42. data/prawn-2.5.0/lib/prawn/graphics/join_style.rb +60 -0
  43. data/prawn-2.5.0/lib/prawn/graphics/patterns.rb +389 -0
  44. data/prawn-2.5.0/lib/prawn/graphics/transformation.rb +172 -0
  45. data/prawn-2.5.0/lib/prawn/graphics/transparency.rb +79 -0
  46. data/prawn-2.5.0/lib/prawn/graphics.rb +791 -0
  47. data/prawn-2.5.0/lib/prawn/grid.rb +449 -0
  48. data/prawn-2.5.0/lib/prawn/image_handler.rb +61 -0
  49. data/prawn-2.5.0/lib/prawn/images/image.rb +48 -0
  50. data/prawn-2.5.0/lib/prawn/images/jpg.rb +121 -0
  51. data/prawn-2.5.0/lib/prawn/images/png.rb +383 -0
  52. data/prawn-2.5.0/lib/prawn/images.rb +195 -0
  53. data/prawn-2.5.0/lib/prawn/measurement_extensions.rb +82 -0
  54. data/prawn-2.5.0/lib/prawn/measurements.rb +129 -0
  55. data/prawn-2.5.0/lib/prawn/outline.rb +316 -0
  56. data/prawn-2.5.0/lib/prawn/repeater.rb +140 -0
  57. data/prawn-2.5.0/lib/prawn/security/arcfour.rb +54 -0
  58. data/prawn-2.5.0/lib/prawn/security.rb +308 -0
  59. data/prawn-2.5.0/lib/prawn/soft_mask.rb +105 -0
  60. data/prawn-2.5.0/lib/prawn/stamp.rb +147 -0
  61. data/prawn-2.5.0/lib/prawn/text/box.rb +192 -0
  62. data/prawn-2.5.0/lib/prawn/text/formatted/arranger.rb +396 -0
  63. data/prawn-2.5.0/lib/prawn/text/formatted/box.rb +725 -0
  64. data/prawn-2.5.0/lib/prawn/text/formatted/fragment.rb +373 -0
  65. data/prawn-2.5.0/lib/prawn/text/formatted/line_wrap.rb +335 -0
  66. data/prawn-2.5.0/lib/prawn/text/formatted/parser.rb +284 -0
  67. data/prawn-2.5.0/lib/prawn/text/formatted/wrap.rb +166 -0
  68. data/prawn-2.5.0/lib/prawn/text/formatted.rb +82 -0
  69. data/prawn-2.5.0/lib/prawn/text.rb +701 -0
  70. data/prawn-2.5.0/lib/prawn/transformation_stack.rb +63 -0
  71. data/prawn-2.5.0/lib/prawn/utilities.rb +34 -0
  72. data/prawn-2.5.0/lib/prawn/version.rb +6 -0
  73. data/prawn-2.5.0/lib/prawn/view.rb +116 -0
  74. data/prawn-2.5.0/lib/prawn.rb +83 -0
  75. 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