Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 490 lines (408 sloc) 17.1 KB
#!/usr/bin/env python
#
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Dumps the OpenType Layout tables in a font in a readable format."""
__author__ = "roozbeh@google.com (Roozbeh Pournader)"
import sys
from fontTools import ttLib
def internal_font_name(font):
"""Returns the font name according to the data in the name table."""
name_table = font['name']
for record in name_table.names:
identifier = (record.nameID, record.platformID,
record.platEncID, record.langID)
if identifier in [(4, 3, 1, 0x409), (4, 3, 0, 0x409)]:
return unicode(record.string, 'UTF-16BE')
def print_indented(output_string, indents=1):
"""Prints a string indented with a specified number of spaces."""
print ' ' * indents + output_string
def printable_glyph_class(glyph_list, quote=False):
"""Returns a printable form for a class of glyphs."""
if quote:
suffix = "'"
else:
suffix = ''
if len(glyph_list) == 1:
return list(glyph_list)[0]+suffix
else:
return '[' + ' '.join(glyph_list) + ']'+suffix
def printable_glyph_list(glyph_list, quote=False):
"""Returns a printable form for a list of glyphs."""
if quote:
suffix = "'"
else:
suffix = ''
if len(glyph_list) == 1:
return glyph_list[0]+suffix
else:
glyph_list = [glyph+suffix for glyph in glyph_list]
return ' '.join(glyph_list)
def dump_lang_sys(script, lang, lang_sys):
"""Dumps a language system."""
print '%s %s:' % (script, lang),
assert lang_sys.LookupOrder is None
if lang_sys.ReqFeatureIndex != 65535:
print '<%s>' % lang_sys.ReqFeatureIndex,
print lang_sys.FeatureIndex
def extract_glyphs_from_coverage(coverage):
"""Return a list of glyphs from a coverage."""
if isinstance(coverage, str):
return [coverage]
else:
return coverage.glyphs
def print_contextual_substitution(backtrack, center, lookahead, subs,
reverse=False):
"""Prints out a contextual substitution rule."""
if reverse:
rule_name = 'rsub'
else:
rule_name = 'sub'
if not subs:
rule_name = 'ignore ' + rule_name
output_list = []
for coverage in center:
glyphs = extract_glyphs_from_coverage(coverage)
output_list.append(printable_glyph_class(glyphs, quote=True))
if subs:
if isinstance(subs[0], str):
output_list.append('by')
output_list.append(printable_glyph_class(subs))
else:
records_added = 0
for record in subs:
output_list.insert(
record.SequenceIndex + 1 + records_added,
'lookup %d' % record.LookupListIndex)
records_added += 1
for coverage in backtrack:
glyphs = extract_glyphs_from_coverage(coverage)
output_list.insert(0, printable_glyph_class(glyphs))
for coverage in lookahead:
glyphs = extract_glyphs_from_coverage(coverage)
output_list.append(printable_glyph_class(glyphs))
print_indented('%s %s;' % (rule_name, ' '.join(output_list)))
def dump_gsub_subtable(lookup_type, subtable):
"""Dumps a GSUB subtable."""
if lookup_type == 7:
# Extension. Decompile and pass down as another lookup type
lookup_type = subtable.ExtensionLookupType
print_indented('# (extension for %d)' % lookup_type)
ext_sub_table = subtable.ExtSubTable
subtable = ext_sub_table
# Proceed to the actual type
if lookup_type == 1:
for key in sorted(subtable.mapping.keys()):
print_indented('sub %s by %s;' % (key, subtable.mapping[key]))
elif lookup_type == 2:
for key, value in sorted(subtable.mapping.items()):
print_indented('sub %s by %s;' % (key, ' '.join(value)))
elif lookup_type == 3:
for key in sorted(subtable.alternates.keys()):
print_indented('sub %s from %s;' % (
key,
printable_glyph_class(subtable.alternates[key])))
elif lookup_type == 4:
for key in sorted(subtable.ligatures.keys()):
for ligature in subtable.ligatures[key]:
print_indented('sub %s by %s;' % (
printable_glyph_list([key]+ligature.Component),
ligature.LigGlyph))
elif lookup_type == 5 and subtable.Format == 1:
for index in range(subtable.SubRuleSetCount):
glyph = subtable.Coverage.glyphs[index]
for subrule in subtable.SubRuleSet[index].SubRule:
whole_input = [glyph] + subrule.Input
print_contextual_substitution(
[], whole_input, [], subrule.SubstLookupRecord)
elif lookup_type == 5 and subtable.Format == 2:
print_indented('type=%d format=%d not supported yet' % (
lookup_type, subtable.Format))
# assert (max(subtable.ClassDef.classDefs.values())
# <= subtable.SubClassSetCount)
# assert len(subtable.SubClassSet) == subtable.SubClassSetCount
# print subtable.Coverage.glyphs
# print subtable.ClassDef.classDefs
# for index in range(subtable.SubClassSetCount):
# sub_class = subtable.SubClassSet[index]
# if sub_class:
# print index, vars(sub_class)
# else:
# print index, sub_class
elif lookup_type == 5 and subtable.Format == 3:
print_contextual_substitution(
[], subtable.Coverage, [], subtable.SubstLookupRecord)
elif lookup_type in [6, 8]:
is_reverse = (lookup_type == 8)
if subtable.Format == 1:
print_contextual_substitution(
subtable.BacktrackCoverage,
[subtable.Coverage],
subtable.LookAheadCoverage,
subtable.Substitute,
reverse=is_reverse)
elif subtable.Format == 3:
print_contextual_substitution(
subtable.BacktrackCoverage,
subtable.InputCoverage,
subtable.LookAheadCoverage,
subtable.SubstLookupRecord,
reverse=is_reverse)
else:
print_indented('# type=%d format=%d not supported yet' % (
lookup_type, subtable.Format))
else:
print_indented('# type=%d format=%d not supported yet' % (
lookup_type, subtable.Format))
# print vars(subtable)
def printable_value_record(value_record):
"""Prints a GPOS ValueRecord."""
if value_record is None:
return '<NULL>'
if vars(value_record).keys() == ['XAdvance']:
return '%d' % value_record.XAdvance
output_list = []
for key in ['XPlacement', 'YPlacement', 'XAdvance', 'YAdvance']:
output_list.append(vars(value_record).get(key, 0))
return '<%d %d %d %d>' % tuple(output_list)
def printable_device(device):
"""Returns a printable form of a device record."""
if device is None:
return '<device NULL>'
output_list = []
assert device.StartSize + len(device.DeltaValue) - 1 == device.EndSize
for index in range(len(device.DeltaValue)):
output_list.append('%d %d' % (
device.StartSize + index,
device.DeltaValue[index]))
return '<device %s>' % (', '.join(output_list))
def printable_anchor(anchor):
"""Returns a printable form of an anchor."""
if anchor is None:
return '<anchor NULL>'
if anchor.Format == 1:
return '<anchor %d %d>' % (anchor.XCoordinate, anchor.YCoordinate)
elif anchor.Format == 2:
return '<anchor %d %d contourpoint %d>' % (
anchor.XCoordinate,
anchor.YCoordinate,
anchor.AnchorPoint)
elif anchor.Format == 3:
return '<anchor %d %d %s %s>' % (
anchor.XCoordinate,
anchor.YCoordinate,
printable_device(anchor.XDeviceTable),
printable_device(anchor.YDeviceTable))
else:
print vars(anchor)
assert False, "don't know about anchor format"
def dump_marks(glyphs, records):
"""Prints marks with their classes."""
index = 0
for glyph in glyphs:
record = records[index]
print_indented(
'%s: class=%d %s' % (
glyph, record.Class, printable_anchor(record.MarkAnchor)),
indents=2)
index += 1
def dump_bases(glyphs, records, printable_function):
"""Prints bases with their classes."""
index = 0
for glyph in glyphs:
record = records[index]
print_indented(
'%s: %s' % (glyph, printable_function(record)),
indents=2)
index += 1
def reverse_class_def(class_def_dict):
"""Reverses a ClassDef dictionary."""
reverse = {}
for key in class_def_dict:
value = class_def_dict[key]
try:
reverse[value].add(key)
except KeyError:
reverse[value] = {key}
return reverse
def dump_gpos_subtable(lookup_type, subtable):
"""Prints a GPOS subtable."""
if lookup_type == 9:
# Extension. Decompile and pass down as another lookup type
lookup_type = subtable.ExtensionLookupType
print_indented('# (extension for %d)' % lookup_type)
ext_sub_table = subtable.ExtSubTable
subtable = ext_sub_table
if lookup_type == 1 and subtable.Format == 1:
print_indented('position %s %s;' % (
printable_glyph_class(subtable.Coverage.glyphs),
printable_value_record(subtable.Value)))
elif lookup_type == 1 and subtable.Format == 2:
for index, value_record in enumerate(subtable.Value):
print_indented('position %s %s;' % (
subtable.Coverage.glyphs[index],
printable_value_record(value_record)))
elif lookup_type == 2 and subtable.Format == 1:
first_glyphs = subtable.Coverage.glyphs
for index, pair_set in enumerate(subtable.PairSet):
first_glyph = first_glyphs[index]
for pair_value_record in pair_set.PairValueRecord:
if pair_value_record.Value2 is None:
print_indented(
'position %s %s %s;' % (
first_glyph,
pair_value_record.SecondGlyph,
printable_value_record(pair_value_record.Value1)))
else:
print_indented(
'position %s %s %s %s;' % (
first_glyph,
printable_value_record(pair_value_record.Value1),
pair_value_record.SecondGlyph,
printable_value_record(pair_value_record.Value2)))
elif lookup_type == 2 and subtable.Format == 2:
class1_reverse = reverse_class_def(subtable.ClassDef1.classDefs)
class2_reverse = reverse_class_def(subtable.ClassDef2.classDefs)
for index1, class1_record in enumerate(subtable.Class1Record):
class1 = class1_reverse.get(index1)
if class1 is None:
continue
for index2, class2_record in enumerate(class1_record.Class2Record):
class2 = class2_reverse.get(index2)
if class2 is None:
continue
if class2_record.Value2 is None:
value_record1 = printable_value_record(class2_record.Value1)
if value_record1 == '0':
continue
print_indented(
'position %s %s %s;' % (
printable_glyph_class(class1),
printable_glyph_class(class2),
value_record1))
else:
print_indented(
'position %s %s %s %s;' % (
printable_glyph_class(class1),
printable_value_record(class2_record.Value1),
printable_glyph_class(class2),
printable_value_record(class2_record.Value2)))
elif lookup_type == 3 and subtable.Format == 1:
for index, entry_exit_record in enumerate(subtable.EntryExitRecord):
print_indented(
'position cursive %s %s %s;' % (
subtable.Coverage.glyphs[index],
printable_anchor(entry_exit_record.EntryAnchor),
printable_anchor(entry_exit_record.ExitAnchor)))
elif lookup_type == 4:
print_indented('Mark:')
dump_marks(subtable.MarkCoverage.glyphs, subtable.MarkArray.MarkRecord)
print_indented('Base:')
dump_bases(
subtable.BaseCoverage.glyphs,
subtable.BaseArray.BaseRecord,
lambda record: ' '.join([printable_anchor(anchor)
for anchor in record.BaseAnchor]))
elif lookup_type == 5:
print_indented('Mark:')
dump_marks(subtable.MarkCoverage.glyphs, subtable.MarkArray.MarkRecord)
def printable_ligature_attach(attach):
"""Output routine for LigatureAttach."""
output_list = []
for record in attach.ComponentRecord:
anchor_list = []
for anchor in record.LigatureAnchor:
anchor_list.append(printable_anchor(anchor))
output_list.append(' '.join(anchor_list))
return ', '.join(output_list)
print_indented('Liga:')
dump_bases(
subtable.LigatureCoverage.glyphs,
subtable.LigatureArray.LigatureAttach,
printable_ligature_attach)
elif lookup_type == 6:
print_indented('Mark1:')
dump_marks(subtable.Mark1Coverage.glyphs,
subtable.Mark1Array.MarkRecord)
print_indented('Mark2:')
dump_bases(
subtable.Mark2Coverage.glyphs,
subtable.Mark2Array.Mark2Record,
lambda record: ' '.join([printable_anchor(anchor)
for anchor in record.Mark2Anchor]))
elif lookup_type == 8 and subtable.Format == 3:
print_contextual_substitution(
subtable.BacktrackCoverage,
subtable.InputCoverage,
subtable.LookAheadCoverage,
subtable.PosLookupRecord)
else:
print_indented('type=%d format=%d not supported yet' % (
lookup_type, subtable.Format))
def dump_script_record(script_record):
"""Prints out scripts records."""
for script in script_record:
script_tag = script.ScriptTag
default_lang_sys = script.Script.DefaultLangSys
if default_lang_sys:
dump_lang_sys(script_tag, 'dflt', default_lang_sys)
for lang_sys_record in script.Script.LangSysRecord:
dump_lang_sys(script_tag, lang_sys_record.LangSysTag,
lang_sys_record.LangSys)
def dump_feature_record(feature_record):
"""Prints out feature records."""
for index in range(len(feature_record)):
record = feature_record[index]
tag = record.FeatureTag
feature = record.Feature
print index, tag, feature.LookupListIndex
if feature.FeatureParams is not None:
print_indented('# name <%s>;' % feature.FeatureParams.UINameID)
def dump_lookup_list(lookup_list, table_name):
"""Prints out a lookup list."""
for index in range(len(lookup_list)):
lookup = lookup_list[index]
print 'lookup %d { # type=%d flag=0x%X' % (
index, lookup.LookupType, lookup.LookupFlag)
for subtable in lookup.SubTable:
if table_name == 'GSUB':
dump_gsub_subtable(lookup.LookupType, subtable)
elif table_name == 'GPOS':
dump_gpos_subtable(lookup.LookupType, subtable)
print '}'
def dump_otl_table(font, table_name):
"""Prints out an OpenType Layout table."""
if table_name not in font:
print 'no %s table' % table_name
print
return
else:
print '%s' % table_name
print '----'
table = font[table_name].table
dump_script_record(table.ScriptList.ScriptRecord)
print
dump_feature_record(table.FeatureList.FeatureRecord)
print
dump_lookup_list(table.LookupList.Lookup, table_name)
print
def main():
"""Dump the OpenType Layout tables for all input arguments."""
for font_file_name in sys.argv[1:]:
font = ttLib.TTFont(font_file_name)
print '%s: %s' % (font_file_name, internal_font_name(font))
dump_otl_table(font, 'GPOS')
dump_otl_table(font, 'GSUB')
if __name__ == '__main__':
main()