class HexaPDF::Font::TrueType::Subsetter

Subsets a TrueType font in the context of PDF.

TrueType fonts can be embedded into PDF either as a simple font or as a composite font. This subsetter implements the functionality needed when embedding a TrueType subset for a composite font.

This means in particular that the resulting font file cannot be used outside of the PDF.

Public Class Methods

new(font) click to toggle source

Creates a new Subsetter for the given TrueType Font object.

# File lib/hexapdf/font/true_type/subsetter.rb, line 52
def initialize(font)
  @font = font
  @glyph_map = {0 => 0}
  @last_id = 0
end

Public Instance Methods

build_font() click to toggle source

Builds the subset font file and returns it as a binary string.

# File lib/hexapdf/font/true_type/subsetter.rb, line 85
def build_font
  glyf, locations = build_glyf_table
  loca = build_loca_table(locations)
  hmtx = build_hmtx_table
  head = build_head_table(modified: Time.now, loca_type: 1)
  hhea = build_hhea_table(@glyph_map.size)
  maxp = build_maxp_table(@glyph_map.size)

  tables = {
    'head' => head,
    'hhea' => hhea,
    'maxp' => maxp,
    'glyf' => glyf,
    'loca' => loca,
    'hmtx' => hmtx,
  }
  tables['cvt '] = @font[:"cvt "].raw_data if @font[:"cvt "]
  tables['fpgm'] = @font[:fpgm].raw_data if @font[:fpgm]
  tables['prep'] = @font[:prep].raw_data if @font[:prep]

  Builder.build(tables)
end
subset_glyph_id(glyph_id) click to toggle source

Returns the new subset glyph ID for the given glyph ID, or nil if the glyph isn't subset.

# File lib/hexapdf/font/true_type/subsetter.rb, line 80
def subset_glyph_id(glyph_id)
  @glyph_map[glyph_id]
end
use_glyph(glyph_id) click to toggle source

Includes the glyph with the given ID in the subset and returns the new subset glyph ID.

Can be called multiple times with the same glyph ID, always returning the correct new subset glyph ID.

# File lib/hexapdf/font/true_type/subsetter.rb, line 62
def use_glyph(glyph_id)
  return @glyph_map[glyph_id] if @glyph_map.key?(glyph_id)
  @last_id += 1
  # Handle codes for ASCII characters \r (13), (, ) (40, 41) and \ (92) specially so that
  # they never appear in the output (PDF serialization would need to escape them)
  if @last_id == 13 || @last_id == 40 || @last_id == 92
    @glyph_map[:"s#{@last_id}"] = @last_id
    if @last_id == 40
      @last_id += 1
      @glyph_map[:"s#{@last_id}"] = @last_id
    end
    @last_id += 1
  end
  @glyph_map[glyph_id] = @last_id
end

Private Instance Methods

add_glyph_components() click to toggle source

Adds the components of compound glyphs to the subset.

# File lib/hexapdf/font/true_type/subsetter.rb, line 176
def add_glyph_components
  glyf = @font[:glyf]
  @glyph_map.keys.each do |gid|
    next if gid.kind_of?(Symbol)
    glyf[gid].components&.each {|cgid| use_glyph(cgid) }
  end
end
build_glyf_table() click to toggle source

Builds the glyf table.

# File lib/hexapdf/font/true_type/subsetter.rb, line 111
def build_glyf_table
  add_glyph_components

  orig_glyf = @font[:glyf]
  table = ''.b
  locations = []

  @glyph_map.each_key do |old_gid|
    glyph = orig_glyf[old_gid.kind_of?(Symbol) ? 0 : old_gid]
    locations << table.size
    data = glyph.raw_data
    if glyph.compound?
      data = data.dup
      glyph.component_offsets.each_with_index do |offset, index|
        data[offset, 2] = [@glyph_map[glyph.components[index]]].pack('n')
      end
    end
    table << data
  end

  locations << table.size

  [table, locations]
end
build_head_table(modified:, loca_type:) click to toggle source

Builds the head table, adjusting the modification time and location table type.

# File lib/hexapdf/font/true_type/subsetter.rb, line 160
def build_head_table(modified:, loca_type:)
  data = @font[:head].raw_data
  data[8, 4] = "\0\0\0\0"
  data[28, 8] = [(modified - TrueType::Table::TIME_EPOCH).to_i].pack('q>')
  data[-4, 2] = [loca_type].pack('n')
  data
end
build_hhea_table(num_of_long_hor_metrics) click to toggle source

Builds the hhea table, adjusting the value of the number of horizontal metrics.

# File lib/hexapdf/font/true_type/subsetter.rb, line 153
def build_hhea_table(num_of_long_hor_metrics)
  data = @font[:hhea].raw_data
  data[-2, 2] = [num_of_long_hor_metrics].pack('n')
  data
end
build_hmtx_table() click to toggle source

Builds the hmtx table.

# File lib/hexapdf/font/true_type/subsetter.rb, line 142
def build_hmtx_table
  hmtx = @font[:hmtx]
  data = ''.b
  @glyph_map.each_key do |old_gid|
    metric = hmtx[old_gid.kind_of?(Symbol) ? 0 : old_gid]
    data << [metric.advance_width, metric.left_side_bearing].pack('n2')
  end
  data
end
build_loca_table(locations) click to toggle source

Builds the loca table given the locations.

# File lib/hexapdf/font/true_type/subsetter.rb, line 137
def build_loca_table(locations)
  locations.pack('N*')
end
build_maxp_table(nr_of_glyphs) click to toggle source

Builds the maxp table, adjusting the number of glyphs.

# File lib/hexapdf/font/true_type/subsetter.rb, line 169
def build_maxp_table(nr_of_glyphs)
  data = @font[:maxp].raw_data
  data[4, 2] = [nr_of_glyphs].pack('n')
  data
end