Class: HexaPDF::Layout::Style

Inherits:
Object
  • Object
show all
Defined in:
lib/hexapdf/layout/style.rb

Overview

A Style is a container for properties that describe the appearance of text or graphics.

Each property except #font has a default value, so only the desired properties need to be changed.

Each property has three associated methods:

property_name

Getter method.

property_name(*args) and property_name=

Setter method.

property_name?

Tester method to see if a value has been set or if the default value has already been used.

Defined Under Namespace

Classes: Border, Layers, LineSpacing, LinkLayer, Quad

Constant Summary collapse

UNSET =

:nodoc:

::Object.new
PROPERTIES =

:nodoc:

[
  [:font, "raise HexaPDF::Error, 'No font set'"],
  [:font_bold, false],
  [:font_italic, false],
  [:font_size, 10],
  [:line_height, nil],
  [:character_spacing, 0],
  [:word_spacing, 0],
  [:horizontal_scaling, 100],
  [:text_rise, 0],
  [:font_features, {}],
  [:text_rendering_mode, "Content::TextRenderingMode::FILL",
   {setter: "Content::TextRenderingMode.normalize(value)"}],
  [:subscript, false,
   {setter: "value; superscript(false) if value && superscript? && superscript",
    valid_values: [true, false]}],
  [:superscript, false,
   {setter: "value; subscript(false) if value && subscript? && subscript",
    valid_values: [true, false]}],
  [:underline, false, {valid_values: [true, false]}],
  [:strikeout, false, {valid_values: [true, false]}],
  [:fill_color, "default_color"],
  [:fill_alpha, 1],
  [:stroke_color, "default_color"],
  [:stroke_alpha, 1],
  [:stroke_width, 1],
  [:stroke_cap_style, "Content::LineCapStyle::BUTT_CAP",
   {setter: "Content::LineCapStyle.normalize(value)"}],
  [:stroke_join_style, "Content::LineJoinStyle::MITER_JOIN",
   {setter: "Content::LineJoinStyle.normalize(value)"}],
  [:stroke_miter_limit, 10.0],
  [:stroke_dash_pattern, "Content::LineDashPattern.new",
   {setter: "Content::LineDashPattern.normalize(value, phase)", extra_args: ", phase = 0"}],
  [:text_align, :left, {valid_values: [:left, :center, :right, :justify]}],
  [:text_valign, :top, {valid_values: [:top, :center, :bottom]}],
  [:text_indent, 0],
  [:line_spacing, "LineSpacing.new(type: :single)",
   {setter: "LineSpacing.new(**(value.kind_of?(Symbol) || value.kind_of?(Numeric) || " \
     "value.kind_of?(LineSpacing) ? {type: value, value: extra_arg} : value))",
    extra_args: ", extra_arg = nil"}],
  [:last_line_gap, false, {valid_values: [true, false]}],
  [:fill_horizontal, nil],
  [:background_color, nil],
  [:background_alpha, 1],
  [:padding, "Quad.new(0)",
   {setter: "value.kind_of?(Hash) && @name ? @name.set(value) : Quad.new(value)"}],
  [:margin, "Quad.new(0)",
   {setter: "value.kind_of?(Hash) && @name ? @name.set(value) : Quad.new(value)"}],
  [:border, "Border.new", {setter: "Border.new(**value)"}],
  [:overlays, "Layers.new", {setter: "Layers.new(value)"}],
  [:underlays, "Layers.new", {setter: "Layers.new(value)"}],
  [:position, :default],
  [:align, :left, {valid_values: [:left, :center, :right]}],
  [:valign, :top, {valid_values: [:top, :center, :bottom]}],
  [:mask_mode, :default, {valid_values: [:default, :none, :box, :fill_horizontal,
                                         :fill_frame_horizontal, :fill_vertical, :fill]}],
  [:overflow, :error],
  [:box_options, {}],
].each do |name, default, options = {}|
  default = default.inspect unless default.kind_of?(String)
  setter = options.delete(:setter) || "value"
  extra_args = options.delete(:extra_args) || ""
  valid_values = options.delete(:valid_values)
  raise ArgumentError, "Invalid keywords: #{options.keys.join(', ')}" unless options.empty?
  valid_values_const = "#{name}_valid_values".upcase
  const_set(valid_values_const, valid_values)
  module_eval("    def \#{name}(value = UNSET\#{extra_args})\n      if value == UNSET\n        @\#{name} ||= \#{default}\n      elsif \#{valid_values_const} && !\#{valid_values_const}.include?(value)\n        raise ArgumentError, \"\\\#{value.inspect} is not a valid \#{name} value \" \\\\\n          \"(\\\#{\#{valid_values_const}.map(&:inspect).join(', ')})\"\n      else\n        @\#{name} = \#{setter}\n        self\n      end\n    end\n    def \#{name}?\n      defined?(@\#{name})\n    end\n  EOF\n  alias_method(\"\#{name}=\", name)\nend.each_with_object({}) {|arr, hash| hash[:\"@\#{arr.first}\"] = arr.first }\n", __FILE__, __LINE__ + 1)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**properties) ⇒ Style

Creates a new Style object.

The properties hash may be used to set the initial values of properties by using keys equivalent to the property names.

Example:

Style.new(font_size: 15, text_align: :center, text_valign: center)


596
597
598
599
# File 'lib/hexapdf/layout/style.rb', line 596

def initialize(**properties)
  update(**properties)
  @scaled_item_widths = {}.compare_by_identity
end

Class Method Details

.create(style) ⇒ Object

:call-seq:

Style.create(style)     -> style
Style.create(properties_hash)   -> style

Creates a Style object based on the style argument and returns it:

  • If style is already a Style object, it is just returned.

  • If style is a hash, a new Style object with the style properties specified by the hash

  • is created.

  • If style is nil, a new Style object with only default values is created.



580
581
582
583
584
585
586
587
# File 'lib/hexapdf/layout/style.rb', line 580

def self.create(style)
  case style
  when self then style
  when Hash then new(**style)
  when nil then new
  else raise ArgumentError, "Invalid argument class #{style.class}"
  end
end

Instance Method Details

#calculated_font_sizeObject

The calculated font size, taking superscript and subscript into account.



1669
1670
1671
# File 'lib/hexapdf/layout/style.rb', line 1669

def calculated_font_size
  ((superscript? && superscript) || (subscript? && subscript) ? 0.583 : 1) * font_size
end

#calculated_strikeout_positionObject

Returns the correct offset from the baseline for the strikeout line.



1688
1689
1690
1691
1692
1693
# File 'lib/hexapdf/layout/style.rb', line 1688

def calculated_strikeout_position
  calculated_text_rise +
    font.wrapped_font.strikeout_position * font.scaling_factor *
    font.pdf_object.glyph_scaling_factor * calculated_font_size -
    calculated_strikeout_thickness / 2.0
end

#calculated_strikeout_thicknessObject

Returns the correct thickness for the strikeout line.



1696
1697
1698
1699
# File 'lib/hexapdf/layout/style.rb', line 1696

def calculated_strikeout_thickness
  font.wrapped_font.strikeout_thickness * font.scaling_factor *
    font.pdf_object.glyph_scaling_factor * calculated_font_size
end

#calculated_text_riseObject

The calculated text rise, taking superscript and subscript into account.



1658
1659
1660
1661
1662
1663
1664
1665
1666
# File 'lib/hexapdf/layout/style.rb', line 1658

def calculated_text_rise
  if superscript? && superscript
    text_rise + font_size * 0.33
  elsif subscript? && subscript
    text_rise - font_size * 0.20
  else
    text_rise
  end
end

#calculated_underline_positionObject

Returns the correct offset from the baseline for the underline.



1674
1675
1676
1677
1678
1679
# File 'lib/hexapdf/layout/style.rb', line 1674

def calculated_underline_position
  calculated_text_rise +
    font.wrapped_font.underline_position * font.scaling_factor *
    font.pdf_object.glyph_scaling_factor * calculated_font_size -
    calculated_underline_thickness / 2.0
end

#calculated_underline_thicknessObject

Returns the correct thickness for the underline.



1682
1683
1684
1685
# File 'lib/hexapdf/layout/style.rb', line 1682

def calculated_underline_thickness
  font.wrapped_font.underline_thickness * font.scaling_factor *
    font.pdf_object.glyph_scaling_factor * calculated_font_size
end

#clear_cacheObject

Clears all cached values.

This method needs to be called if the following style properties are changed and values were already cached: font, font_size, character_spacing, word_spacing, horizontal_scaling, ascender, descender.



1768
1769
1770
1771
1772
1773
# File 'lib/hexapdf/layout/style.rb', line 1768

def clear_cache
  @scaled_font_size = @scaled_character_spacing = @scaled_word_spacing = nil
  @scaled_horizontal_scaling = @scaled_font_ascender = @scaled_font_descender = nil
  @scaled_y_min = @scaled_y_max = nil
  @scaled_item_widths.clear
end

#each_propertyObject

Yields all set properties.



627
628
629
630
631
632
# File 'lib/hexapdf/layout/style.rb', line 627

def each_property # :yield: property, value
  return to_enum(__method__) unless block_given?
  instance_variables.each do |iv|
    (val = PROPERTIES[iv]) && yield(val, instance_variable_get(iv))
  end
end

#initialize_copy(other) ⇒ Object

Duplicates the complex properties that can be modified, as well as the cache.



602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/hexapdf/layout/style.rb', line 602

def initialize_copy(other)
  super
  @scaled_item_widths = {}.compare_by_identity
  clear_cache

  @font_features = @font_features.dup if defined?(@font_features)
  @padding = @padding.dup if defined?(@padding)
  @margin = @margin.dup if defined?(@margin)
  @border = @border.dup if defined?(@border)
  @overlays = @overlays.dup if defined?(@overlays)
  @underlays = @underlays.dup if defined?(@underlays)
end

#merge(other) ⇒ Object

:call-seq:

style.merge(other_style)   -> style

Merges the set properties of the other_style object into this one.

Note that merging is done on a per-property basis. So if a complex property is set on other_style and also on self, the other_style value completely overwrites the one from self.

Also see: #update



644
645
646
647
# File 'lib/hexapdf/layout/style.rb', line 644

def merge(other)
  other.each_property {|property, value| send(property, value) }
  self
end

#nameObject

:method: text_line_wrapping_algorithm :call-seq:

text_line_wrapping_algorithm(algorithm = nil) {|items, width_block| block }

The line wrapping algorithm that should be used, defaults to TextLayouter::SimpleLineWrapping.

When setting the algorithm, either an object that responds to #call or a block can be used. See TextLayouter::SimpleLineWrapping#call for the needed method signature.



1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
# File 'lib/hexapdf/layout/style.rb', line 1636

[
  [:text_segmentation_algorithm, 'TextLayouter::SimpleTextSegmentation'],
  [:text_line_wrapping_algorithm, 'TextLayouter::SimpleLineWrapping'],
].each do |name, default|
  default = default.inspect unless default.kind_of?(String)
  module_eval("    def \#{name}(value = UNSET, &block)\n      if value == UNSET && !block\n        @\#{name} ||= \#{default}\n      else\n        @\#{name} = (value != UNSET ? value : block)\n        self\n      end\n    end\n    def \#{name}?\n      defined?(@\#{name})\n    end\n  EOF\n  alias_method(\"\#{name}=\", name)\nend\n", __FILE__, __LINE__ + 1)

#scaled_character_spacingObject

The character spacing scaled appropriately.



1708
1709
1710
# File 'lib/hexapdf/layout/style.rb', line 1708

def scaled_character_spacing
  @scaled_character_spacing ||= character_spacing * scaled_horizontal_scaling
end

#scaled_font_ascenderObject

The ascender of the font scaled appropriately.



1723
1724
1725
1726
# File 'lib/hexapdf/layout/style.rb', line 1723

def scaled_font_ascender
  @scaled_font_ascender ||= font.wrapped_font.ascender * font.scaling_factor *
    font.pdf_object.glyph_scaling_factor * font_size
end

#scaled_font_descenderObject

The descender of the font scaled appropriately.



1729
1730
1731
1732
# File 'lib/hexapdf/layout/style.rb', line 1729

def scaled_font_descender
  @scaled_font_descender ||= font.wrapped_font.descender * font.scaling_factor *
    font.pdf_object.glyph_scaling_factor * font_size
end

#scaled_font_sizeObject

The font size scaled appropriately.



1702
1703
1704
1705
# File 'lib/hexapdf/layout/style.rb', line 1702

def scaled_font_size
  @scaled_font_size ||= calculated_font_size * font.pdf_object.glyph_scaling_factor *
    scaled_horizontal_scaling
end

#scaled_horizontal_scalingObject

The horizontal scaling scaled appropriately.



1718
1719
1720
# File 'lib/hexapdf/layout/style.rb', line 1718

def scaled_horizontal_scaling
  @scaled_horizontal_scaling ||= horizontal_scaling / 100.0
end

#scaled_item_width(item) ⇒ Object

Returns the width of the item scaled appropriately (by taking font size, characters spacing, word spacing and horizontal scaling into account).

The item may be a (singleton) glyph object or an integer/float, i.e. items that can appear inside a TextFragment.



1753
1754
1755
1756
1757
1758
1759
1760
1761
# File 'lib/hexapdf/layout/style.rb', line 1753

def scaled_item_width(item)
  @scaled_item_widths[item] ||=
    if item.kind_of?(Numeric)
      -item * scaled_font_size
    else
      item.width * scaled_font_size + scaled_character_spacing +
        (item.apply_word_spacing? ? scaled_word_spacing : 0)
    end
end

#scaled_word_spacingObject

The word spacing scaled appropriately.



1713
1714
1715
# File 'lib/hexapdf/layout/style.rb', line 1713

def scaled_word_spacing
  @scaled_word_spacing ||= word_spacing * scaled_horizontal_scaling
end

#scaled_y_maxObject

The maximum y-coordinate, calculated using the scaled ascender of the font and the line height or font size.



1743
1744
1745
1746
# File 'lib/hexapdf/layout/style.rb', line 1743

def scaled_y_max
  @scaled_y_max ||= scaled_font_ascender * (line_height || font_size) / font_size.to_f +
    calculated_text_rise
end

#scaled_y_minObject

The minimum y-coordinate, calculated using the scaled descender of the font and the line height or font size.



1736
1737
1738
1739
# File 'lib/hexapdf/layout/style.rb', line 1736

def scaled_y_min
  @scaled_y_min ||= scaled_font_descender * (line_height || font_size) / font_size.to_f +
    calculated_text_rise
end

#update(**properties) ⇒ Object

:call-seq:

style.update(**properties)    -> style

Updates the style’s properties using the key-value pairs specified by the properties hash.

Also see: #merge



621
622
623
624
# File 'lib/hexapdf/layout/style.rb', line 621

def update(**properties)
  properties.each {|key, value| send(key, value) }
  self
end