1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 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 134 135 136 137 138 139 140 141
|
require 'ttfunk/table/cmap'
require 'ttfunk/table/glyf'
require 'ttfunk/table/head'
require 'ttfunk/table/hhea'
require 'ttfunk/table/hmtx'
require 'ttfunk/table/kern'
require 'ttfunk/table/loca'
require 'ttfunk/table/maxp'
require 'ttfunk/table/name'
require 'ttfunk/table/post'
require 'ttfunk/table/simple'
module TTFunk
module Subset
class Base
attr_reader :original
def initialize(original)
@original = original
end
def unicode?
false
end
def to_unicode_map
{}
end
def encode(options={})
cmap_table = new_cmap_table(options)
glyphs = collect_glyphs(original_glyph_ids)
old2new_glyph = cmap_table[:charmap].inject({ 0 => 0 }) { |map, (code, ids)| map[ids[:old]] = ids[:new]; map }
next_glyph_id = cmap_table[:max_glyph_id]
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
new2old_glyph = old2new_glyph.invert
# "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)
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)
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 }
tables.delete_if { |tag, table| table.nil? }
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
newfont = [original.directory.scaler_type, tables.length, search_range, entry_selector, range_shift].pack("Nn*")
directory_size = tables.length * 16
offset = newfont.length + directory_size
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"
end
end
newfont << table_data
sum = checksum(newfont)
adjustment = 0xB1B0AFBA - sum
newfont[head_offset+8,4] = [adjustment].pack("N")
return newfont
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*").inject(0) { |sum, dword| sum + dword } & 0xFFFF_FFFF
end
def collect_glyphs(glyph_ids)
glyphs = glyph_ids.inject({}) { |h, id| h[id] = original.glyph_outlines.for(id); h }
additional_ids = glyphs.values.select { |g| g && g.compound? }.map { |g| g.glyph_ids }.flatten
glyphs.update(collect_glyphs(additional_ids)) if additional_ids.any?
return glyphs
end
end
end
end
|