prawnx 0.1.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 (160) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -0
  3. data/GPLv2 +339 -0
  4. data/GPLv3 +674 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-prawn-svg +21 -0
  7. data/README.md +107 -0
  8. data/data/fonts/Courier-Bold.afm +342 -0
  9. data/data/fonts/Courier-BoldOblique.afm +342 -0
  10. data/data/fonts/Courier-Oblique.afm +342 -0
  11. data/data/fonts/Courier.afm +342 -0
  12. data/data/fonts/Helvetica-Bold.afm +2827 -0
  13. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  14. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  15. data/data/fonts/Helvetica.afm +3051 -0
  16. data/data/fonts/MustRead.html +19 -0
  17. data/data/fonts/Symbol.afm +213 -0
  18. data/data/fonts/Times-Bold.afm +2588 -0
  19. data/data/fonts/Times-BoldItalic.afm +2384 -0
  20. data/data/fonts/Times-Italic.afm +2667 -0
  21. data/data/fonts/Times-Roman.afm +2419 -0
  22. data/data/fonts/ZapfDingbats.afm +225 -0
  23. data/lib/pdf/core.rb +4 -0
  24. data/lib/prawn/measurement_extensions.rb +4 -0
  25. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  26. data/lib/prawn/svg/attributes/mask.rb +16 -0
  27. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  28. data/lib/prawn/svg/attributes/space.rb +10 -0
  29. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  30. data/lib/prawn/svg/attributes/transform.rb +10 -0
  31. data/lib/prawn/svg/attributes.rb +6 -0
  32. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  33. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  34. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  35. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  36. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  37. data/lib/prawn/svg/color.rb +240 -0
  38. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  39. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  40. data/lib/prawn/svg/css/font_parser.rb +46 -0
  41. data/lib/prawn/svg/css/selector_parser.rb +178 -0
  42. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  43. data/lib/prawn/svg/css/values_parser.rb +68 -0
  44. data/lib/prawn/svg/document.rb +170 -0
  45. data/lib/prawn/svg/elements/anchor.rb +10 -0
  46. data/lib/prawn/svg/elements/base.rb +312 -0
  47. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  48. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  49. data/lib/prawn/svg/elements/circle.rb +25 -0
  50. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  51. data/lib/prawn/svg/elements/container.rb +10 -0
  52. data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
  53. data/lib/prawn/svg/elements/ellipse.rb +22 -0
  54. data/lib/prawn/svg/elements/gradient.rb +184 -0
  55. data/lib/prawn/svg/elements/ignored.rb +5 -0
  56. data/lib/prawn/svg/elements/image.rb +177 -0
  57. data/lib/prawn/svg/elements/line.rb +27 -0
  58. data/lib/prawn/svg/elements/marker.rb +72 -0
  59. data/lib/prawn/svg/elements/mask.rb +65 -0
  60. data/lib/prawn/svg/elements/path.rb +294 -0
  61. data/lib/prawn/svg/elements/pattern.rb +150 -0
  62. data/lib/prawn/svg/elements/polygon.rb +25 -0
  63. data/lib/prawn/svg/elements/polyline.rb +23 -0
  64. data/lib/prawn/svg/elements/rect.rb +64 -0
  65. data/lib/prawn/svg/elements/root.rb +22 -0
  66. data/lib/prawn/svg/elements/switch.rb +92 -0
  67. data/lib/prawn/svg/elements/text.rb +99 -0
  68. data/lib/prawn/svg/elements/text_component.rb +346 -0
  69. data/lib/prawn/svg/elements/text_node.rb +308 -0
  70. data/lib/prawn/svg/elements/text_path.rb +153 -0
  71. data/lib/prawn/svg/elements/use.rb +142 -0
  72. data/lib/prawn/svg/elements/viewport.rb +29 -0
  73. data/lib/prawn/svg/elements.rb +46 -0
  74. data/lib/prawn/svg/extension.rb +25 -0
  75. data/lib/prawn/svg/font.rb +20 -0
  76. data/lib/prawn/svg/font_metrics.rb +144 -0
  77. data/lib/prawn/svg/font_registry.rb +315 -0
  78. data/lib/prawn/svg/funciri.rb +14 -0
  79. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  80. data/lib/prawn/svg/gradients.rb +53 -0
  81. data/lib/prawn/svg/interface.rb +72 -0
  82. data/lib/prawn/svg/length.rb +43 -0
  83. data/lib/prawn/svg/link_renderer.rb +40 -0
  84. data/lib/prawn/svg/loaders/data.rb +17 -0
  85. data/lib/prawn/svg/loaders/file.rb +111 -0
  86. data/lib/prawn/svg/loaders/web.rb +24 -0
  87. data/lib/prawn/svg/paint.rb +67 -0
  88. data/lib/prawn/svg/pathable.rb +153 -0
  89. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  90. data/lib/prawn/svg/pdfmatrix.rb +37 -0
  91. data/lib/prawn/svg/percentage.rb +24 -0
  92. data/lib/prawn/svg/properties.rb +329 -0
  93. data/lib/prawn/svg/renderer.rb +249 -0
  94. data/lib/prawn/svg/state.rb +24 -0
  95. data/lib/prawn/svg/transform_parser.rb +78 -0
  96. data/lib/prawn/svg/ttc.rb +35 -0
  97. data/lib/prawn/svg/ttf.rb +95 -0
  98. data/lib/prawn/svg/url_loader.rb +46 -0
  99. data/lib/prawn/svg/version.rb +5 -0
  100. data/lib/prawn/table.rb +4 -0
  101. data/lib/prawn-svg.rb +53 -0
  102. data/lib/prawn.rb +10 -0
  103. data/lib/prawnx/annotations.rb +21 -0
  104. data/lib/prawnx/document/bounding_box.rb +193 -0
  105. data/lib/prawnx/document/column_box.rb +91 -0
  106. data/lib/prawnx/document/internals.rb +144 -0
  107. data/lib/prawnx/document/span.rb +25 -0
  108. data/lib/prawnx/document.rb +253 -0
  109. data/lib/prawnx/encoding.rb +29 -0
  110. data/lib/prawnx/errors.rb +37 -0
  111. data/lib/prawnx/font.rb +214 -0
  112. data/lib/prawnx/fonts/afm.rb +171 -0
  113. data/lib/prawnx/fonts/otf.rb +50 -0
  114. data/lib/prawnx/fonts/ttf.rb +410 -0
  115. data/lib/prawnx/graphic_state.rb +41 -0
  116. data/lib/prawnx/graphics/color.rb +148 -0
  117. data/lib/prawnx/graphics/patterns.rb +100 -0
  118. data/lib/prawnx/graphics/style.rb +171 -0
  119. data/lib/prawnx/graphics.rb +208 -0
  120. data/lib/prawnx/grid.rb +216 -0
  121. data/lib/prawnx/images/image.rb +244 -0
  122. data/lib/prawnx/images/png_decoder.rb +65 -0
  123. data/lib/prawnx/images.rb +115 -0
  124. data/lib/prawnx/measurement_extensions.rb +40 -0
  125. data/lib/prawnx/measurements.rb +62 -0
  126. data/lib/prawnx/outline.rb +146 -0
  127. data/lib/prawnx/page.rb +125 -0
  128. data/lib/prawnx/page_geometry.rb +27 -0
  129. data/lib/prawnx/pdf/outline_node.rb +53 -0
  130. data/lib/prawnx/pdf/reference.rb +31 -0
  131. data/lib/prawnx/pdf/writer.rb +105 -0
  132. data/lib/prawnx/pdf.rb +66 -0
  133. data/lib/prawnx/pdf_a/srgb_profile.rb +61 -0
  134. data/lib/prawnx/pdf_a.rb +130 -0
  135. data/lib/prawnx/pdf_core.rb +25 -0
  136. data/lib/prawnx/repeater.rb +79 -0
  137. data/lib/prawnx/security.rb +108 -0
  138. data/lib/prawnx/stamp.rb +74 -0
  139. data/lib/prawnx/table/cell/kinds.rb +246 -0
  140. data/lib/prawnx/table/cell.rb +331 -0
  141. data/lib/prawnx/table/cells.rb +208 -0
  142. data/lib/prawnx/table.rb +296 -0
  143. data/lib/prawnx/text/box.rb +16 -0
  144. data/lib/prawnx/text/formatted/arranger.rb +217 -0
  145. data/lib/prawnx/text/formatted/box.rb +312 -0
  146. data/lib/prawnx/text/formatted/fragment.rb +205 -0
  147. data/lib/prawnx/text/formatted/line_wrap.rb +204 -0
  148. data/lib/prawnx/text/formatted/parser.rb +149 -0
  149. data/lib/prawnx/text/formatted/wrap.rb +103 -0
  150. data/lib/prawnx/text.rb +281 -0
  151. data/lib/prawnx/transformation_stack.rb +43 -0
  152. data/lib/prawnx/true_type/cff_subset.rb +157 -0
  153. data/lib/prawnx/true_type/file.rb +371 -0
  154. data/lib/prawnx/true_type/open_type_layout.rb +189 -0
  155. data/lib/prawnx/true_type/simple_subsets.rb +94 -0
  156. data/lib/prawnx/true_type/subset.rb +187 -0
  157. data/lib/prawnx/version.rb +7 -0
  158. data/lib/prawnx/view.rb +28 -0
  159. data/lib/prawnx.rb +102 -0
  160. metadata +251 -0
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnX
4
+ module Text
5
+ module Formatted
6
+ # Formatted text laid out in a rectangle: wrapped, aligned, shrunk or truncated to fit, possibly rotated.
7
+ class Box
8
+ include Wrap
9
+
10
+ VALID_OPTIONS = (Text::VALID_OPTIONS + %i[
11
+ at height width align valign rotate rotate_around overflow min_font_size disable_wrap_by_char leading
12
+ character_spacing mode single_line document direction fallback_fonts draw_text_callback
13
+ ]).freeze
14
+ DEFAULT_MINIMUM_FONT_SIZE = 5
15
+ SHRINK_STEP = 0.5
16
+
17
+ attr_reader :text, :at, :line_height, :ascender, :descender, :leading
18
+
19
+ # Modules every new box of this class extends itself with: a subclass starts with its parent's.
20
+ def self.extensions
21
+ @extensions ||= []
22
+ end
23
+
24
+ def self.inherited(subclass)
25
+ super
26
+ subclass.extensions.concat(extensions)
27
+ end
28
+
29
+ def initialize(formatted_text, options = {})
30
+ @inked = false
31
+ self.class.extensions.reverse_each { extend(it) }
32
+ PrawnX.verify_options(valid_options, options)
33
+ options = options.dup
34
+ @overflow = options[:overflow] || :truncate
35
+ @disable_wrap_by_char = options[:disable_wrap_by_char]
36
+ @original_array = formatted_text
37
+ @text = nil
38
+ @document = options[:document]
39
+ @direction = options[:direction] || @document.text_direction
40
+ @fallback_fonts = options[:fallback_fonts] || @document.fallback_fonts
41
+ @at = (options[:at] || [@document.bounds.left, @document.bounds.top]).dup
42
+ @width = options[:width] || (@document.bounds.right - @at[0])
43
+ @height = options[:height] || default_height
44
+ @align = options[:align] || (@direction == :rtl ? :right : :left)
45
+ @vertical_align = options[:valign] || :top
46
+ @leading = options[:leading] || @document.default_leading
47
+ @character_spacing = options[:character_spacing] || @document.character_spacing
48
+ @mode = options[:mode] || @document.text_rendering_mode
49
+ @mode = :fill if @mode == :unknown
50
+ @rotate = options[:rotate] || 0
51
+ @rotate_around = options[:rotate_around] || :upper_left
52
+ @single_line = options[:single_line]
53
+ @draw_text_callback = options[:draw_text_callback]
54
+ if @overflow == :expand
55
+ @height = default_height
56
+ @overflow = :truncate
57
+ end
58
+ @min_font_size = options[:min_font_size] || DEFAULT_MINIMUM_FONT_SIZE
59
+ options[:kerning] = @document.default_kerning? if options[:kerning].nil?
60
+ @options = {kerning: options[:kerning], size: options[:size], style: options[:style]}
61
+ super(formatted_text, options)
62
+ end
63
+
64
+ def nothing_printed?
65
+ @nothing_printed
66
+ end
67
+
68
+ def everything_printed?
69
+ @everything_printed
70
+ end
71
+
72
+ def line_gap
73
+ line_height - (ascender + descender)
74
+ end
75
+
76
+ def render(flags = {})
77
+ unprinted_text = []
78
+ @laid_out_lines = flags[:keep_layout] ? [] : nil
79
+ with_text_settings do
80
+ text = normalized_text(flags)
81
+ shrink_to_fit(text) if @overflow == :shrink_to_fit
82
+ process_vertical_alignment(text)
83
+ @inked = true unless flags[:dry_run]
84
+ unprinted_text = @rotate != 0 && @inked ? render_rotated(text) : wrap(text)
85
+ @inked = false
86
+ end
87
+ unprinted_text.map { it.merge(text: @document.font.to_utf8(it[:text])) }
88
+ end
89
+
90
+ # Whether the lines of a `render(dry_run: true, keep_layout: true)` can be drawn again as they are.
91
+ def layout_reusable?
92
+ !@laid_out_lines.nil? && @everything_printed && @rotate.zero? && @vertical_align == :top &&
93
+ @overflow != :shrink_to_fit && self.class.extensions.empty? && @original_array.none? { it[:color] }
94
+ end
95
+
96
+ # Draws, with its top left corner at `at`, the lines the last kept layout wrapped, without wrapping them again.
97
+ def render_laid_out(at)
98
+ @at = at.dup
99
+ with_text_settings do
100
+ @inked = true
101
+ @laid_out_lines.each do |baseline_y, drawn_fragments|
102
+ @baseline_y = baseline_y
103
+ drawn_fragments.each { format_and_draw_fragment(*it) }
104
+ end
105
+ @inked = false
106
+ end
107
+ end
108
+
109
+ def available_width
110
+ @width
111
+ end
112
+
113
+ def height
114
+ return 0 if @baseline_y.nil? || @descender.nil?
115
+
116
+ (@baseline_y - @descender).abs
117
+ end
118
+
119
+ def draw_fragment(fragment, accumulated_width = 0, line_width = 0, word_spacing = 0)
120
+ x =
121
+ case @align
122
+ when :left then @at[0]
123
+ when :center then @at[0] + (@width * 0.5) - (line_width * 0.5)
124
+ when :right then @at[0] + @width - line_width
125
+ when :justify then @direction == :ltr ? @at[0] : @at[0] + @width - line_width
126
+ else raise ArgumentError, "align must be one of :left, :right, :center or :justify symbols"
127
+ end
128
+ x += accumulated_width
129
+ y = @at[1] + @baseline_y + fragment.y_offset
130
+ fragment.left = x
131
+ fragment.baseline = y
132
+ return unless @inked
133
+
134
+ fragment.callback_objects.each { it.render_behind(fragment) if it.respond_to?(:render_behind) }
135
+ @document.word_spacing(word_spacing) do
136
+ if @draw_text_callback
137
+ @draw_text_callback.call(fragment.text, at: [x, y], kerning: @kerning)
138
+ else
139
+ @document.draw_text!(fragment.text, at: [x, y], kerning: @kerning)
140
+ end
141
+ end
142
+ draw_fragment_overlays(fragment)
143
+ end
144
+
145
+ def valid_options
146
+ VALID_OPTIONS
147
+ end
148
+
149
+ private
150
+
151
+ def with_text_settings(&)
152
+ @document.save_font do
153
+ @document.character_spacing(@character_spacing) do
154
+ @document.text_rendering_mode(@mode) do
155
+ process_options
156
+ @document.font_size(@font_size, &)
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def normalized_text(flags)
163
+ text = normalize_encoding
164
+ text.each { it.delete(:color) } if flags[:dry_run]
165
+ text
166
+ end
167
+
168
+ def original_text
169
+ @original_array.map(&:dup)
170
+ end
171
+
172
+ def normalize_encoding
173
+ formatted_text = original_text
174
+ formatted_text = formatted_text.flat_map { fragments_by_fallback_font(it) } unless @fallback_fonts.empty?
175
+ formatted_text.each do |hash|
176
+ if hash[:font]
177
+ @document.font(hash[:font]) { hash[:text] = @document.font.normalize_encoding(hash[:text]) }
178
+ else
179
+ hash[:text] = @document.font.normalize_encoding(hash[:text])
180
+ end
181
+ end
182
+ formatted_text
183
+ end
184
+
185
+ def fragments_by_fallback_font(hash)
186
+ original_font = @document.font.family
187
+ fragment_font = hash[:font] || original_font
188
+ fragment_font_style = @arranger.font_style(hash[:styles])
189
+ fragment_font_options = fragment_font_style == :normal ? {} : {style: fragment_font_style}
190
+ fallback_fonts = [*@fallback_fonts, fragment_font]
191
+
192
+ font_glyph_pairs = []
193
+ @document.save_font do
194
+ hash[:text].each_char do |character|
195
+ font_glyph_pairs << [font_for_glyph(character, fragment_font, fallback_fonts.dup, fragment_font_options), character]
196
+ end
197
+ end
198
+ font_glyph_pairs.each { it[0] = nil if it[0] == original_font } if hash[:font].nil?
199
+
200
+ fragments = []
201
+ current_font = nil
202
+ font_glyph_pairs.each do |font, character|
203
+ if font != current_font || fragments.empty?
204
+ current_font = font
205
+ fragment = hash.merge(text: character)
206
+ fragment[:font] = font unless font.nil?
207
+ fragments << fragment
208
+ else
209
+ fragments.last[:text] += character
210
+ end
211
+ end
212
+ fragments
213
+ end
214
+
215
+ def font_for_glyph(character, current_font, fallback_fonts, current_font_options = {})
216
+ loop do
217
+ @document.font(current_font, current_font_options)
218
+ return current_font if fallback_fonts.empty? || @document.font.glyph_present?(character)
219
+
220
+ current_font = fallback_fonts.shift
221
+ end
222
+ end
223
+
224
+ def move_baseline_down
225
+ if @baseline_y.zero?
226
+ @baseline_y = -@ascender
227
+ else
228
+ @baseline_y -= @line_height + @leading
229
+ end
230
+ end
231
+
232
+ def default_height
233
+ frame = @document.bounds
234
+ frame = frame.parent while frame.stretchy? && frame.parent
235
+ @at[1] + @document.bounds.absolute_bottom - frame.absolute_bottom
236
+ end
237
+
238
+ def process_vertical_alignment(text)
239
+ return if @vertical_alignment_processed
240
+
241
+ @vertical_alignment_processed = true
242
+ return if @vertical_align == :top
243
+
244
+ wrap(text)
245
+ case @vertical_align
246
+ when :center then @at[1] -= (@height - height + @descender) * 0.5
247
+ when :bottom then @at[1] -= @height - height
248
+ else raise ArgumentError, "valign must be one of :left, :right or :center symbols"
249
+ end
250
+ @height = height
251
+ end
252
+
253
+ def shrink_to_fit(text)
254
+ loop do
255
+ if @disable_wrap_by_char && @font_size > @min_font_size
256
+ begin
257
+ wrap(text)
258
+ rescue Errors::CannotFit
259
+ # Too big for a word to fit whole: the next, smaller size may make it fit.
260
+ end
261
+ else
262
+ wrap(text)
263
+ end
264
+ break if @everything_printed || @font_size <= @min_font_size
265
+
266
+ @font_size = [@font_size - SHRINK_STEP, @min_font_size].max
267
+ @document.font_size = @font_size
268
+ end
269
+ end
270
+
271
+ def process_options
272
+ @document.process_text_options(@options)
273
+ @font_size = @options[:size]
274
+ @kerning = @options[:kerning]
275
+ end
276
+
277
+ def render_rotated(text)
278
+ x, y =
279
+ case @rotate_around
280
+ when :center then [@at[0] + (@width * 0.5), @at[1] - (@height * 0.5)]
281
+ when :upper_right then [@at[0] + @width, @at[1]]
282
+ when :lower_right then [@at[0] + @width, @at[1] - @height]
283
+ when :lower_left then [@at[0], @at[1] - @height]
284
+ else [@at[0], @at[1]]
285
+ end
286
+ unprinted_text = ""
287
+ @document.rotate(@rotate, origin: [x, y]) { unprinted_text = wrap(text) }
288
+ unprinted_text
289
+ end
290
+
291
+ def draw_fragment_overlays(fragment)
292
+ @document.stroke_line(fragment.underline_points) if fragment.styles.include?(:underline)
293
+ @document.stroke_line(fragment.strikethrough_points) if fragment.styles.include?(:strikethrough)
294
+ draw_link_annotations(fragment)
295
+ fragment.callback_objects.each { it.render_in_front(fragment) if it.respond_to?(:render_in_front) }
296
+ end
297
+
298
+ def draw_link_annotations(fragment)
299
+ if fragment.link
300
+ @document.link_annotation(fragment.absolute_bounding_box, Border: [0, 0, 0],
301
+ A: {Type: :Action, S: :URI, URI: PDF::LiteralString.new(fragment.link)})
302
+ end
303
+ @document.link_annotation(fragment.absolute_bounding_box, Border: [0, 0, 0], Dest: fragment.anchor) if fragment.anchor
304
+ return unless fragment.local
305
+
306
+ @document.link_annotation(fragment.absolute_bounding_box, Border: [0, 0, 0],
307
+ A: {Type: :Action, S: :Launch, F: PDF::LiteralString.new(fragment.local), NewWindow: true})
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrawnX
4
+ module Text
5
+ module Formatted
6
+ # A run of text sharing one format on one line, with its measurements and, once drawn, its position.
7
+ class Fragment
8
+ attr_reader :format_state, :text
9
+ attr_writer :width
10
+ attr_accessor :line_height, :descender, :ascender, :word_spacing, :left, :baseline
11
+
12
+ def initialize(text, format_state, document)
13
+ @format_state = format_state
14
+ @document = document
15
+ @word_spacing = 0
16
+ @original_text = text
17
+ @text = process_text(@original_text)
18
+ end
19
+
20
+ def width
21
+ @word_spacing.zero? ? @width : @width + (@word_spacing * space_count)
22
+ end
23
+
24
+ def height
25
+ top - bottom
26
+ end
27
+
28
+ def subscript?
29
+ styles.include?(:subscript)
30
+ end
31
+
32
+ def superscript?
33
+ styles.include?(:superscript)
34
+ end
35
+
36
+ def y_offset
37
+ if subscript? then -descender
38
+ elsif superscript? then 0.85 * ascender
39
+ else 0
40
+ end
41
+ end
42
+
43
+ def bounding_box
44
+ [left, bottom, right, top]
45
+ end
46
+
47
+ def absolute_bounding_box
48
+ left, bottom, right, top = bounding_box
49
+ bounds = @document.bounds
50
+ [left + bounds.absolute_left, bottom + bounds.absolute_bottom, right + bounds.absolute_left, top + bounds.absolute_bottom]
51
+ end
52
+
53
+ def underline_points
54
+ y = baseline - 1.25
55
+ [[left, y], [right, y]]
56
+ end
57
+
58
+ def strikethrough_points
59
+ y = baseline + (ascender * 0.3)
60
+ [[left, y], [right, y]]
61
+ end
62
+
63
+ def styles
64
+ @format_state[:styles] || []
65
+ end
66
+
67
+ def link
68
+ @format_state[:link]
69
+ end
70
+
71
+ def anchor
72
+ @format_state[:anchor]
73
+ end
74
+
75
+ def local
76
+ @format_state[:local]
77
+ end
78
+
79
+ def color
80
+ @format_state[:color]
81
+ end
82
+
83
+ def font
84
+ @format_state[:font]
85
+ end
86
+
87
+ def size
88
+ @format_state[:size]
89
+ end
90
+
91
+ def character_spacing
92
+ @format_state[:character_spacing] || @document.character_spacing
93
+ end
94
+
95
+ def direction
96
+ @format_state[:direction]
97
+ end
98
+
99
+ def default_direction=(direction)
100
+ return if @format_state[:direction]
101
+
102
+ @format_state[:direction] = direction
103
+ @text = process_text(@original_text)
104
+ end
105
+
106
+ def include_trailing_white_space!
107
+ @format_state.delete(:exclude_trailing_white_space)
108
+ @text = process_text(@original_text)
109
+ end
110
+
111
+ def space_count
112
+ @text.count(" ")
113
+ end
114
+
115
+ def callback_objects
116
+ callback = @format_state[:callback]
117
+ callback.nil? ? [] : Array(callback)
118
+ end
119
+
120
+ def right
121
+ left + width
122
+ end
123
+
124
+ def top
125
+ baseline + ascender
126
+ end
127
+
128
+ def bottom
129
+ baseline - descender
130
+ end
131
+
132
+ def top_left
133
+ [left, top]
134
+ end
135
+
136
+ def top_right
137
+ [right, top]
138
+ end
139
+
140
+ def bottom_right
141
+ [right, bottom]
142
+ end
143
+
144
+ def bottom_left
145
+ [left, bottom]
146
+ end
147
+
148
+ def absolute_left
149
+ absolute_bounding_box[0]
150
+ end
151
+
152
+ def absolute_right
153
+ absolute_bounding_box[2]
154
+ end
155
+
156
+ def absolute_top
157
+ absolute_bounding_box[3]
158
+ end
159
+
160
+ def absolute_bottom
161
+ absolute_bounding_box[1]
162
+ end
163
+
164
+ def absolute_top_left
165
+ [absolute_left, absolute_top]
166
+ end
167
+
168
+ def absolute_top_right
169
+ [absolute_right, absolute_top]
170
+ end
171
+
172
+ def absolute_bottom_left
173
+ [absolute_left, absolute_bottom]
174
+ end
175
+
176
+ def absolute_bottom_right
177
+ [absolute_right, absolute_bottom]
178
+ end
179
+
180
+ private
181
+
182
+ def process_text(text)
183
+ string = text.encoding == ::Encoding::UTF_8 ? text.gsub(ZWSP, "") : text
184
+ if @format_state[:exclude_trailing_white_space]
185
+ string = string.rstrip
186
+ string = process_soft_hyphens(string[0..-2]) + string[-1..] if soft_hyphens_need_processing?(string)
187
+ elsif soft_hyphens_need_processing?(string)
188
+ string = process_soft_hyphens(string)
189
+ end
190
+ direction == :rtl ? string.reverse : string
191
+ end
192
+
193
+ def soft_hyphens_need_processing?(string)
194
+ !string.empty? && @format_state[:normalized_soft_hyphen]
195
+ end
196
+
197
+ def process_soft_hyphens(string)
198
+ soft_hyphen = @format_state[:normalized_soft_hyphen]
199
+ string = string.dup.force_encoding(soft_hyphen.encoding) if string.encoding != soft_hyphen.encoding
200
+ string.gsub(soft_hyphen, "")
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end