lib/ttfunk/subset/base.rb in ttfunk-1.5.1 vs lib/ttfunk/subset/base.rb in ttfunk-1.6.0
- old
+ new
@@ -1,5 +1,7 @@
+# frozen_string_literal: true
+
require_relative '../table/cmap'
require_relative '../table/glyf'
require_relative '../table/head'
require_relative '../table/hhea'
require_relative '../table/hmtx'
@@ -11,159 +13,98 @@
require_relative '../table/simple'
module TTFunk
module Subset
class Base
+ MICROSOFT_PLATFORM_ID = 3
+ MS_SYMBOL_ENCODING_ID = 0
+
attr_reader :original
def initialize(original)
@original = original
end
def unicode?
false
end
+ def microsoft_symbol?
+ new_cmap_table[:platform_id] == MICROSOFT_PLATFORM_ID &&
+ new_cmap_table[:encoding_id] == MS_SYMBOL_ENCODING_ID
+ end
+
def to_unicode_map
{}
end
def encode(options = {})
- cmap_table = new_cmap_table(options)
- glyphs = collect_glyphs(original_glyph_ids)
+ encoder_klass.new(original, self, options).encode
+ end
- old2new_glyph = cmap_table[:charmap]
- .each_with_object(0 => 0) do |(_, ids), map|
- map[ids[:old]] = ids[:new]
- end
- next_glyph_id = cmap_table[:max_glyph_id]
+ def encoder_klass
+ original.cff.exists? ? OTFEncoder : TTFEncoder
+ end
- glyphs.keys.each do |old_id|
- unless old2new_glyph.key?(old_id)
- old2new_glyph[old_id] = next_glyph_id
- next_glyph_id += 1
- end
- end
+ def unicode_cmap
+ @unicode_cmap ||= @original.cmap.unicode.first
+ end
- new2old_glyph = old2new_glyph.invert
+ def glyphs
+ @glyphs ||= collect_glyphs(original_glyph_ids)
+ end
- # "mandatory" tables. Every font should ("should") have these, including
- # the cmap table (encoded above).
- glyf_table = TTFunk::Table::Glyf.encode(
- glyphs, new2old_glyph, old2new_glyph
- )
- loca_table = TTFunk::Table::Loca.encode(glyf_table[:offsets])
- hmtx_table = TTFunk::Table::Hmtx.encode(
- original.horizontal_metrics, new2old_glyph
- )
- hhea_table = TTFunk::Table::Hhea.encode(
- original.horizontal_header, hmtx_table
- )
- maxp_table = TTFunk::Table::Maxp.encode(
- original.maximum_profile, old2new_glyph
- )
- post_table = TTFunk::Table::Post.encode(
- original.postscript, new2old_glyph
- )
- name_table = TTFunk::Table::Name.encode(
- original.name, glyf_table[:table]
- )
- head_table = TTFunk::Table::Head.encode(
- original.header, loca_table
- )
-
- # "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.
- os2_table = original.os2.raw
- cvt_table = TTFunk::Table::Simple.new(original, 'cvt ').raw
- fpgm_table = TTFunk::Table::Simple.new(original, 'fpgm').raw
- prep_table = TTFunk::Table::Simple.new(original, 'prep').raw
-
- # for PDF's, 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, old2new_glyph)
+ def collect_glyphs(glyph_ids)
+ collected = glyph_ids.each_with_object({}) do |id, h|
+ h[id] = glyph_for(id)
end
- 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 }
+ additional_ids = collected.values
+ .select { |g| g && g.compound? }
+ .map(&:glyph_ids)
+ .flatten
- tables.delete_if { |_tag, table| table.nil? }
+ collected.update(collect_glyphs(additional_ids)) if additional_ids.any?
- search_range = (Math.log(tables.length) / Math.log(2)).to_i * 16
- entry_selector = (Math.log(search_range) / Math.log(2)).to_i
- range_shift = tables.length * 16 - search_range
+ collected
+ end
- newfont = [
- original.directory.scaler_type,
- tables.length,
- search_range,
- entry_selector,
- range_shift
- ].pack('Nn*')
+ def old_to_new_glyph
+ @old_to_new_glyph ||= begin
+ charmap = new_cmap_table[:charmap]
+ old_to_new = charmap.each_with_object(0 => 0) do |(_, ids), map|
+ map[ids[:old]] = ids[:new]
+ end
- directory_size = tables.length * 16
- offset = newfont.length + directory_size
+ next_glyph_id = new_cmap_table[:max_glyph_id]
- table_data = ''
- head_offset = nil
- tables.each do |tag, data|
- newfont << [tag, checksum(data), offset, data.length].pack('A4N*')
- table_data << data
- head_offset = offset if tag == 'head'
- offset += data.length
- while offset % 4 != 0
- offset += 1
- table_data << "\0"
+ glyphs.keys.each do |old_id|
+ unless old_to_new.key?(old_id)
+ old_to_new[old_id] = next_glyph_id
+ next_glyph_id += 1
+ end
end
+
+ old_to_new
end
+ end
- newfont << table_data
- sum = checksum(newfont)
- adjustment = 0xB1B0AFBA - sum
- newfont[head_offset + 8, 4] = [adjustment].pack('N')
-
- newfont
+ def new_to_old_glyph
+ @new_to_old_glyph ||= old_to_new_glyph.invert
end
private
- def unicode_cmap
- @unicode_cmap ||= @original.cmap.unicode.first
- end
-
- def checksum(data)
- data += "\0" * (4 - data.length % 4) unless data.length % 4 == 0
- data.unpack('N*').reduce(0, :+) & 0xFFFF_FFFF
- end
-
- def collect_glyphs(glyph_ids)
- glyphs = glyph_ids.each_with_object({}) do |id, h|
- h[id] = original.glyph_outlines.for(id)
+ def glyph_for(glyph_id)
+ if original.cff.exists?
+ original
+ .cff
+ .top_index[0]
+ .charstrings_index[glyph_id]
+ .glyph
+ else
+ original.glyph_outlines.for(glyph_id)
end
- additional_ids = glyphs.values.select { |g| g && g.compound? }
- .map(&:glyph_ids).flatten
-
- glyphs.update(collect_glyphs(additional_ids)) if additional_ids.any?
-
- glyphs
end
end
end
end