class TTFunk::TTFEncoder

Encodes a TrueType font subset to its binary representation.

Constants

OPTIMAL_TABLE_ORDER

Optimal table order according to TrueType specification.

Attributes

options[R]

Encoding options. @return [Hash]

original[R]

Original font. @return [TTFunk::File]

subset[R]

Subset to encode. @return [TTFunk::Subset]

Public Class Methods

new(original, subset, options = {}) click to toggle source

@param original [TTFunk::File] @param subset [TTFunk::Subset] @param options [Hash] @option options :kerning [Boolean] whether to encode Kerning (‘kern`)

table.
# File lib/ttfunk/ttf_encoder.rb, line 30
def initialize(original, subset, options = {})
  @original = original
  @subset = subset
  @options = options
end

Public Instance Methods

encode() click to toggle source

Encode the font subset.

@return [String]

# File lib/ttfunk/ttf_encoder.rb, line 39
def encode
  # https://www.microsoft.com/typography/otspec/otff.htm#offsetTable
  search_range = (2**Math.log2(tables.length).floor) * 16
  entry_selector = Integer(Math.log2(2**Math.log2(tables.length).floor))
  range_shift = (tables.length * 16) - search_range
  range_shift = 0 if range_shift.negative?

  newfont = EncodedString.new

  newfont << [
    original.directory.scaler_type,
    tables.length,
    search_range,
    entry_selector,
    range_shift,
  ].pack('Nn*')

  # Tables are supposed to be listed in ascending order whereas there is a
  # known optimal order for table data.
  tables.keys.sort.each do |tag|
    newfont << [tag, checksum(tables[tag])].pack('A4N')
    newfont << Placeholder.new(tag, length: 4)
    newfont << [tables[tag].length].pack('N')
  end

  optimal_table_order.each do |optimal_tag|
    next unless tables.include?(optimal_tag)

    newfont.resolve_placeholder(optimal_tag, [newfont.length].pack('N'))
    newfont << tables[optimal_tag]
    newfont.align!(4)
  end

  sum = checksum(newfont)
  adjustment = 0xB1B0AFBA - sum
  newfont.resolve_placeholder(:checksum, [adjustment].pack('N'))

  newfont.string
end

Private Instance Methods

align(data, width) click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 210
def align(data, width)
  if (data.length % width).positive?
    data + ("\0" * (width - (data.length % width)))
  else
    data
  end
end
checksum(data) click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 202
def checksum(data)
  align(raw(data), 4).unpack('N*').sum & 0xFFFF_FFFF
end
cmap_table() click to toggle source

“mandatory” tables. Every font should (“should”) have these

# File lib/ttfunk/ttf_encoder.rb, line 89
def cmap_table
  @cmap_table ||= subset.new_cmap_table
end
cvt_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 133
def cvt_table
  @cvt_table ||= TTFunk::Table::Simple.new(original, 'cvt ').raw
end
dsig_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 164
def dsig_table
  @dsig_table ||= TTFunk::Table::Dsig.encode(original.digital_signature)
end
fpgm_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 137
def fpgm_table
  @fpgm_table ||= TTFunk::Table::Simple.new(original, 'fpgm').raw
end
gasp_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 145
def gasp_table
  @gasp_table ||= TTFunk::Table::Simple.new(original, 'gasp').raw
end
glyf_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 93
def glyf_table
  @glyf_table ||= TTFunk::Table::Glyf.encode(glyphs, new_to_old_glyph, old_to_new_glyph)
end
glyphs() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 190
def glyphs
  subset.glyphs
end
head_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 121
def head_table
  @head_table ||= TTFunk::Table::Head.encode(original.header, loca_table, new_to_old_glyph)
end
hhea_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 105
def hhea_table
  @hhea_table = TTFunk::Table::Hhea.encode(original.horizontal_header, hmtx_table, original, new_to_old_glyph)
end
hmtx_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 101
def hmtx_table
  @hmtx_table ||= TTFunk::Table::Hmtx.encode(original.horizontal_metrics, new_to_old_glyph)
end
kern_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 149
def kern_table
  # for PDFs, the kerning info is all included in the PDF as the text is
  # drawn. Thus, the PDF readers do not actually use the kerning info in
  # embedded fonts. If the library is used for something else, the
  # generated subfont may need a kerning table... in that case, you need
  # to opt into it.
  if options[:kerning]
    @kern_table ||= TTFunk::Table::Kern.encode(original.kerning, old_to_new_glyph)
  end
end
loca_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 97
def loca_table
  @loca_table ||= TTFunk::Table::Loca.encode(glyf_table[:offsets])
end
maxp_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 109
def maxp_table
  @maxp_table ||= TTFunk::Table::Maxp.encode(original.maximum_profile, old_to_new_glyph)
end
name_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 117
def name_table
  @name_table ||= TTFunk::Table::Name.encode(original.name, glyf_table.fetch(:table, ''))
end
new_to_old_glyph() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 194
def new_to_old_glyph
  subset.new_to_old_glyph
end
old_to_new_glyph() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 198
def old_to_new_glyph
  subset.old_to_new_glyph
end
optimal_table_order() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 81
def optimal_table_order
  OPTIMAL_TABLE_ORDER +
    (tables.keys - ['DSIG'] - OPTIMAL_TABLE_ORDER) +
    ['DSIG']
end
os2_table() click to toggle source

“optional” tables. Fonts may omit these if they do not need them. Because they apply globally, we can simply copy them over, without modification, if they exist.

# File lib/ttfunk/ttf_encoder.rb, line 129
def os2_table
  @os2_table ||= TTFunk::Table::OS2.encode(original.os2, subset)
end
post_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 113
def post_table
  @post_table ||= TTFunk::Table::Post.encode(original.postscript, new_to_old_glyph)
end
prep_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 141
def prep_table
  @prep_table ||= TTFunk::Table::Simple.new(original, 'prep').raw
end
raw(data) click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 206
def raw(data)
  data.respond_to?(:unresolved_string) ? data.unresolved_string : data
end
tables() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 168
def tables
  @tables ||= {
    'cmap' => cmap_table[:table],
    'glyf' => glyf_table[:table],
    'loca' => loca_table[:table],
    'kern' => kern_table,
    'hmtx' => hmtx_table[:table],
    'hhea' => hhea_table,
    'maxp' => maxp_table,
    'OS/2' => os2_table,
    'post' => post_table,
    'name' => name_table,
    'head' => head_table,
    'prep' => prep_table,
    'fpgm' => fpgm_table,
    'cvt ' => cvt_table,
    'VORG' => vorg_table,
    'DSIG' => dsig_table,
    'gasp' => gasp_table,
  }.compact
end
vorg_table() click to toggle source
# File lib/ttfunk/ttf_encoder.rb, line 160
def vorg_table
  @vorg_table ||= TTFunk::Table::Vorg.encode(original.vertical_origins)
end