Class: HexaPDF::Layout::TextFragment
- Inherits:
-
Object
- Object
- HexaPDF::Layout::TextFragment
- Defined in:
- lib/hexapdf/layout/text_fragment.rb
Overview
A TextFragment describes an optionally kerned piece of text that shares the same font, font size and other properties.
Its items are either glyph objects of the font or numeric values describing kerning information. All returned measurement values are in text space units. If the items or the style are changed, the #clear_cache has to be called. Otherwise the measurements may not be correct!
The items of a text fragment may be frozen to indicate that the fragment is potentially used multiple times.
The rectangle with the bottom-left corner (#x_min, #y_min) and the top-right corner (#x_max, #y_max) describes the minimum bounding box of the whole text fragment and is usually not equal to the box (0, 0)-(#width, #height).
Note: This class should not be used directly but via HexaPDF::Document::Layout#text_fragments. This way the whole document layout infrastructure like font fallback and such is automatically used.
Constant Summary collapse
- PRECISION =
The precision used to determine whether two floats represent the same value.
0.000001
Instance Attribute Summary collapse
-
#items ⇒ Object
The items (glyphs and kerning values) of the text fragment.
-
#style ⇒ Object
readonly
The style to be applied.
Class Method Summary collapse
-
.create(text, style) ⇒ Object
Creates a new TextFragment object for the given text, shapes it and returns it.
-
.create_with_fallback_glyphs(text, style) ⇒ Object
:call-seq: TextFragment.create_with_fallback_glyphs(text, style) -> [frag] TextFragment.create_with_fallback_glyphs(text, style) {|codepoint| block } -> [frag1, frag2, …].
Instance Method Summary collapse
-
#attributes_hash ⇒ Object
Returns the value that should be used as hash key when only the fragment’s attributes - without the items - should play a role.
-
#clear_cache ⇒ Object
Clears all cached values.
-
#draw(canvas, x, y, ignore_text_properties: false) ⇒ Object
Draws the text onto the canvas at the given position.
-
#dup_attributes(items) ⇒ Object
Creates a new TextFragment with the same style and custom properties as this one but with the given
items. -
#exact_y_max ⇒ Object
The maximum y-coordinate of any item.
-
#exact_y_min ⇒ Object
The minimum y-coordinate of any item.
-
#fill_horizontal!(width) ⇒ Object
Creates a new text fragment that repeats this fragment’s items and applies the necessary spacing so that the returned text fragment fills the given
widthcompletely. -
#height ⇒ Object
The height of the text fragment.
-
#initialize(items, style, properties: nil) ⇒ TextFragment
constructor
Creates a new TextFragment object with the given items and style.
-
#inspect ⇒ Object
:nodoc:.
-
#properties ⇒ Object
Returns the custom properties hash for the text fragment.
-
#text ⇒ Object
Returns the text of the fragment.
-
#valign ⇒ Object
Returns the vertical alignment inside a line which is always :text for text fragments.
-
#width ⇒ Object
The width of the text fragment.
-
#x_max ⇒ Object
The maximum x-coordinate of the last glyph.
-
#x_min ⇒ Object
The minimum x-coordinate of the first glyph.
-
#y_max ⇒ Object
The maximum y-coordinate, calculated using the scaled ascender of the font.
-
#y_min ⇒ Object
The minimum y-coordinate, calculated using the scaled descender of the font.
Constructor Details
#initialize(items, style, properties: nil) ⇒ TextFragment
Creates a new TextFragment object with the given items and style.
The argument style can either be a Style object or a hash of style properties, see Style::create for details.
For internal use, see the note under TextFragment for details.
172 173 174 175 176 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 172 def initialize(items, style, properties: nil) @items = items @style = Style.create(style) @properties = properties end |
Instance Attribute Details
#items ⇒ Object
The items (glyphs and kerning values) of the text fragment.
136 137 138 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 136 def items @items end |
#style ⇒ Object (readonly)
The style to be applied.
Only the following properties are used:
-
Style#font
-
Style#font_size
-
Style#horizontal_scaling
-
Style#character_spacing
-
Style#word_spacing
-
Style#text_rise
-
Style#text_rendering_mode
-
Style#subscript
-
Style#superscript
-
Style#underline
-
Style#strikeout
-
Style#fill_color
-
Style#fill_alpha
-
Style#stroke_color
-
Style#stroke_alpha
-
Style#stroke_width
-
Style#stroke_cap_style
-
Style#stroke_join_style
-
Style#stroke_miter_limit
-
Style#stroke_dash_pattern
-
Style#underlays
-
Style#overlays
164 165 166 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 164 def style @style end |
Class Method Details
.create(text, style) ⇒ Object
Creates a new TextFragment object for the given text, shapes it and returns it.
The needed style of the text fragment is specified by the style argument (see Style::create for details). Note that the resulting style object needs at least the font set.
For internal use, see the note under TextFragment for details.
73 74 75 76 77 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 73 def self.create(text, style) style = Style.create(style) fragment = new(style.font.decode_utf8(text), style) TextShaper.new.shape_text(fragment) end |
.create_with_fallback_glyphs(text, style) ⇒ Object
:call-seq:
TextFragment.create_with_fallback_glyphs(text, style) -> [frag]
TextFragment.create_with_fallback_glyphs(text, style) {|codepoint| block } -> [frag1, frag2, ...]
Creates one or more TextFragment objects for the given text - possibly using glyphs from fallback fonts -, shapes them and returns them.
If no block is given, the method works like #create but returns the text fragment inside an array.
If a block is given, the text is split on codepoints for which there is no glyph in the style’s font. For the parts with valid glyphs TextFragment objects are created like with #create. Each codepoint without a valid glyph is yielded to the given block together with the associated HexaPDF::Font::InvalidGlyph object as arguments. The block needs to return an array of either HexaPDF::Font::Type1Wrapper::Glyph or HexaPDF::Font::TrueTypeWrapper::Glyph objects. This array is then used for creating a TextFragment object.
The needed style of the text fragments is specified by the style argument (see Style::create for details). Note that the resulting style object needs at least the font set.
For internal use, see the note under TextFragment for details.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 101 def self.create_with_fallback_glyphs(text, style) return [create(text, style)] if !block_given? || text.empty? style = Style.create(style) styles = Hash.new {|h, k| h[k] = style.dup.font(k) } styles[style.font] = style result = [] items = [] shaper = TextShaper.new font = style.font text.each_codepoint do |codepoint| glyph = font.decode_codepoint(codepoint) if glyph.valid? items << glyph else unless items.empty? result.append(*shaper.shape_text(new(items, style))) items = [] end if glyph.control_char? result.append(new([glyph], style)) else fallback = yield(codepoint, glyph) unless fallback.empty? result.append(*shaper.shape_text(new(fallback, styles[fallback.first.font_wrapper]))) end end end end result.append(*shaper.shape_text(new(items, style))) unless items.empty? result end |
Instance Method Details
#attributes_hash ⇒ Object
Returns the value that should be used as hash key when only the fragment’s attributes - without the items - should play a role.
198 199 200 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 198 def attributes_hash @style.hash ^ @properties.hash end |
#clear_cache ⇒ Object
Clears all cached values.
This method needs to be called if the fragment’s items or attributes are changed!
383 384 385 386 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 383 def clear_cache @x_min = @x_max = @exact_y_min = @exact_y_max = @width = @height = nil self end |
#draw(canvas, x, y, ignore_text_properties: false) ⇒ Object
Draws the text onto the canvas at the given position.
This method is the main styled text drawing facility and therefore some optimizations are done:
-
The text is drawn using HexaPDF::Content;:Canvas#show_glyphs_only which means that the text matrix is not updated. Therefore the caller must not rely on it!
-
All text style properties mentioned in the description of #style are set except if
ignore_text_propertiesis set totrue. Note that this only applies to style properties that directly affect text drawing, so, for example, underlays/overlays and underlining/strikeout is always done.The caller should set
ignore_text_propertiestotrueif the graphics state hasn’t been changed. This is the case, for example, if the last thing drawn was a text fragment with the same style. -
It is assumed that the text matrix is not rotated, skewed, etc. so that setting the text position can be done using the optimal method.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 224 def draw(canvas, x, y, ignore_text_properties: false) style.underlays.draw(canvas, x, y + y_min, self) if style.underlays? # Set general font related graphics state if necessary unless ignore_text_properties canvas.font(style.font, size: style.calculated_font_size). horizontal_scaling(style.horizontal_scaling). character_spacing(style.character_spacing). word_spacing(style.word_spacing). text_rise(style.calculated_text_rise). text_rendering_mode(style.text_rendering_mode) # Set fill and/or stroke related graphics state canvas.opacity(fill_alpha: style.fill_alpha, stroke_alpha: style.stroke_alpha) trm = canvas.text_rendering_mode if trm.value.even? # text is filled canvas.fill_color(style.fill_color) end if trm == :stroke || trm == :fill_stroke || trm == :stroke_clip || trm == :fill_stroke_clip canvas.stroke_color(style.stroke_color). line_width(style.stroke_width). line_cap_style(style.stroke_cap_style). line_join_style(style.stroke_join_style). miter_limit(style.stroke_miter_limit). line_dash_pattern(style.stroke_dash_pattern) end end in_text_object = (canvas.graphics_object == :text) canvas.begin_text tlm = canvas.graphics_state.tlm tx = x - tlm.e ty = y - tlm.f if tx.abs < PRECISION if ty.abs < PRECISION # do nothing elsif (ty + canvas.graphics_state.leading).abs < PRECISION canvas.move_text_cursor else canvas.move_text_cursor(offset: [0, ty], absolute: false) end elsif ty.abs < PRECISION canvas.move_text_cursor(offset: [tx, 0], absolute: false) else canvas.move_text_cursor(offset: [x, y], absolute: in_text_object) end canvas.show_glyphs_only(items) if style.underline? && style.underline y_offset = style.calculated_underline_position canvas.save_graphics_state do canvas.stroke_color(style.fill_color). line_width(style.calculated_underline_thickness). line_cap_style(:butt). line_dash_pattern(0). line(x, y + y_offset, x + width, y + y_offset). stroke end end if style.strikeout? && style.strikeout y_offset = style.calculated_strikeout_position canvas.save_graphics_state do canvas.stroke_color(style.fill_color). line_width(style.calculated_strikeout_thickness). line_cap_style(:butt). line_dash_pattern(0). line(x, y + y_offset, x + width, y + y_offset). stroke end end style..draw(canvas, x, y + y_min, self) if style. end |
#dup_attributes(items) ⇒ Object
Creates a new TextFragment with the same style and custom properties as this one but with the given items.
185 186 187 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 185 def dup_attributes(items) self.class.new(items, @style, properties: @properties.dup) end |
#exact_y_max ⇒ Object
The maximum y-coordinate of any item.
326 327 328 329 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 326 def exact_y_max @exact_y_max ||= (@items.max_by(&:y_max)&.y_max || 0) * style.calculated_font_size / 1000.0 + style.calculated_text_rise end |
#exact_y_min ⇒ Object
The minimum y-coordinate of any item.
320 321 322 323 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 320 def exact_y_min @exact_y_min ||= (@items.min_by(&:y_min)&.y_min || 0) * style.calculated_font_size / 1000.0 + style.calculated_text_rise end |
#fill_horizontal!(width) ⇒ Object
Creates a new text fragment that repeats this fragment’s items and applies the necessary spacing so that the returned text fragment fills the given width completely.
If the given width is less than the fragment’s width, self is returned.
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 362 def fill_horizontal!(width) return self if width < self.width factor, rest = width.divmod(self.width) items = @items * factor rest = @items.inject(rest) do |available_width, item| new_available_width = available_width - style.scaled_item_width(item) break available_width if new_available_width < 0 items << item new_available_width end spacing = rest / (items.size - 1) new_style = @style.dup.update(character_spacing: spacing) items << spacing / new_style.scaled_font_size # correct spacing after last item self.class.new(items, new_style, properties: @properties.dup) end |
#height ⇒ Object
The height of the text fragment.
It is calculated as the difference of the maximum of the y_max values and the minimum of the y_min values of the items. However, the text rise value is also taken into account so that the baseline is always inside the bounds. For example, if a large negative text rise value is used, the baseline will be equal to the top boundary; if a large positive value is used, it will be equal to the bottom boundary.
347 348 349 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 347 def height @height ||= [y_max, 0].max - [y_min, 0].min end |
#inspect ⇒ Object
:nodoc:
389 390 391 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 389 def inspect "#<#{self.class.name} #{text.inspect} #{items.inspect}>" end |
#properties ⇒ Object
Returns the custom properties hash for the text fragment.
See Box#properties for usage details.
192 193 194 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 192 def properties @properties ||= {} end |
#text ⇒ Object
Returns the text of the fragment.
179 180 181 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 179 def text items.reject {|i| i.kind_of?(Numeric) }.map(&:str).join end |
#valign ⇒ Object
Returns the vertical alignment inside a line which is always :text for text fragments.
See Line for details.
354 355 356 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 354 def valign :text end |
#width ⇒ Object
The width of the text fragment.
It is the sum of the widths of its items and is calculated by using the algorithm presented in PDF2.0 s9.4.4. By using kerning values as the first and/or last items, the text contained in the fragment may spill over the left and/or right boundary.
336 337 338 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 336 def width @width ||= @items.sum {|item| style.scaled_item_width(item) } end |
#x_max ⇒ Object
The maximum x-coordinate of the last glyph.
305 306 307 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 305 def x_max @x_max ||= calculate_x_max end |
#x_min ⇒ Object
The minimum x-coordinate of the first glyph.
300 301 302 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 300 def x_min @x_min ||= calculate_x_min end |
#y_max ⇒ Object
The maximum y-coordinate, calculated using the scaled ascender of the font.
315 316 317 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 315 def y_max style.scaled_y_max end |
#y_min ⇒ Object
The minimum y-coordinate, calculated using the scaled descender of the font.
310 311 312 |
# File 'lib/hexapdf/layout/text_fragment.rb', line 310 def y_min style.scaled_y_min end |