subset.py revision 9953685818344a9093ee0f14a842332e880eda6d
1# Copyright 2013 Google, Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0(the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# 15# Google Author(s): Behdad Esfahbod 16 17"""Python OpenType Layout Subsetter. 18 19Later grown into full OpenType subsetter, supporting all standard tables. 20""" 21 22import sys 23import struct 24import time 25import array 26 27import fontTools.ttLib 28import fontTools.ttLib.tables 29import fontTools.ttLib.tables.otTables 30import fontTools.cffLib 31import fontTools.misc.psCharStrings 32import fontTools.pens.basePen 33 34 35def _add_method(*clazzes): 36 """Returns a decorator function that adds a new method to one or 37 more classes.""" 38 def wrapper(method): 39 for clazz in clazzes: 40 assert clazz.__name__ != 'DefaultTable', 'Oops, table class not found.' 41 assert not hasattr(clazz, method.func_name), \ 42 "Oops, class '%s' has method '%s'." % (clazz.__name__, 43 method.func_name) 44 setattr(clazz, method.func_name, method) 45 return None 46 return wrapper 47 48def _uniq_sort(l): 49 return sorted(set(l)) 50 51def _set_update(s, *others): 52 # Jython's set.update only takes one other argument. 53 # Emulate real set.update... 54 for other in others: 55 s.update(other) 56 57 58@_add_method(fontTools.ttLib.tables.otTables.Coverage) 59def intersect(self, glyphs): 60 "Returns ascending list of matching coverage values." 61 return [i for i,g in enumerate(self.glyphs) if g in glyphs] 62 63@_add_method(fontTools.ttLib.tables.otTables.Coverage) 64def intersect_glyphs(self, glyphs): 65 "Returns set of intersecting glyphs." 66 return set(g for g in self.glyphs if g in glyphs) 67 68@_add_method(fontTools.ttLib.tables.otTables.Coverage) 69def subset(self, glyphs): 70 "Returns ascending list of remaining coverage values." 71 indices = self.intersect(glyphs) 72 self.glyphs = [g for g in self.glyphs if g in glyphs] 73 return indices 74 75@_add_method(fontTools.ttLib.tables.otTables.Coverage) 76def remap(self, coverage_map): 77 "Remaps coverage." 78 self.glyphs = [self.glyphs[i] for i in coverage_map] 79 80@_add_method(fontTools.ttLib.tables.otTables.ClassDef) 81def intersect(self, glyphs): 82 "Returns ascending list of matching class values." 83 return _uniq_sort( 84 ([0] if any(g not in self.classDefs for g in glyphs) else []) + 85 [v for g,v in self.classDefs.iteritems() if g in glyphs]) 86 87@_add_method(fontTools.ttLib.tables.otTables.ClassDef) 88def intersect_class(self, glyphs, klass): 89 "Returns set of glyphs matching class." 90 if klass == 0: 91 return set(g for g in glyphs if g not in self.classDefs) 92 return set(g for g,v in self.classDefs.iteritems() 93 if v == klass and g in glyphs) 94 95@_add_method(fontTools.ttLib.tables.otTables.ClassDef) 96def subset(self, glyphs, remap=False): 97 "Returns ascending list of remaining classes." 98 self.classDefs = dict((g,v) for g,v in self.classDefs.iteritems() if g in glyphs) 99 # Note: while class 0 has the special meaning of "not matched", 100 # if no glyph will ever /not match/, we can optimize class 0 out too. 101 indices = _uniq_sort( 102 ([0] if any(g not in self.classDefs for g in glyphs) else []) + 103 self.classDefs.values()) 104 if remap: 105 self.remap(indices) 106 return indices 107 108@_add_method(fontTools.ttLib.tables.otTables.ClassDef) 109def remap(self, class_map): 110 "Remaps classes." 111 self.classDefs = dict((g,class_map.index(v)) 112 for g,v in self.classDefs.iteritems()) 113 114@_add_method(fontTools.ttLib.tables.otTables.SingleSubst) 115def closure_glyphs(self, s, cur_glyphs=None): 116 if cur_glyphs == None: cur_glyphs = s.glyphs 117 if self.Format in [1, 2]: 118 s.glyphs.update(v for g,v in self.mapping.iteritems() if g in cur_glyphs) 119 else: 120 assert 0, "unknown format: %s" % self.Format 121 122@_add_method(fontTools.ttLib.tables.otTables.SingleSubst) 123def subset_glyphs(self, s): 124 if self.Format in [1, 2]: 125 self.mapping = dict((g,v) for g,v in self.mapping.iteritems() 126 if g in s.glyphs and v in s.glyphs) 127 return bool(self.mapping) 128 else: 129 assert 0, "unknown format: %s" % self.Format 130 131@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst) 132def closure_glyphs(self, s, cur_glyphs=None): 133 if cur_glyphs == None: cur_glyphs = s.glyphs 134 if self.Format == 1: 135 indices = self.Coverage.intersect(cur_glyphs) 136 _set_update(s.glyphs, *(self.Sequence[i].Substitute for i in indices)) 137 else: 138 assert 0, "unknown format: %s" % self.Format 139 140@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst) 141def subset_glyphs(self, s): 142 if self.Format == 1: 143 indices = self.Coverage.subset(s.glyphs) 144 self.Sequence = [self.Sequence[i] for i in indices] 145 # Now drop rules generating glyphs we don't want 146 indices = [i for i,seq in enumerate(self.Sequence) 147 if all(sub in s.glyphs for sub in seq.Substitute)] 148 self.Sequence = [self.Sequence[i] for i in indices] 149 self.Coverage.remap(indices) 150 self.SequenceCount = len(self.Sequence) 151 return bool(self.SequenceCount) 152 else: 153 assert 0, "unknown format: %s" % self.Format 154 155@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst) 156def closure_glyphs(self, s, cur_glyphs=None): 157 if cur_glyphs == None: cur_glyphs = s.glyphs 158 if self.Format == 1: 159 _set_update(s.glyphs, *(vlist for g,vlist in self.alternates.iteritems() 160 if g in cur_glyphs)) 161 else: 162 assert 0, "unknown format: %s" % self.Format 163 164@_add_method(fontTools.ttLib.tables.otTables.AlternateSubst) 165def subset_glyphs(self, s): 166 if self.Format == 1: 167 self.alternates = dict((g,vlist) 168 for g,vlist in self.alternates.iteritems() 169 if g in s.glyphs and 170 all(v in s.glyphs for v in vlist)) 171 return bool(self.alternates) 172 else: 173 assert 0, "unknown format: %s" % self.Format 174 175@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst) 176def closure_glyphs(self, s, cur_glyphs=None): 177 if cur_glyphs == None: cur_glyphs = s.glyphs 178 if self.Format == 1: 179 _set_update(s.glyphs, *([seq.LigGlyph for seq in seqs 180 if all(c in s.glyphs for c in seq.Component)] 181 for g,seqs in self.ligatures.iteritems() 182 if g in cur_glyphs)) 183 else: 184 assert 0, "unknown format: %s" % self.Format 185 186@_add_method(fontTools.ttLib.tables.otTables.LigatureSubst) 187def subset_glyphs(self, s): 188 if self.Format == 1: 189 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() 190 if g in s.glyphs) 191 self.ligatures = dict((g,[seq for seq in seqs 192 if seq.LigGlyph in s.glyphs and 193 all(c in s.glyphs for c in seq.Component)]) 194 for g,seqs in self.ligatures.iteritems()) 195 self.ligatures = dict((g,v) for g,v in self.ligatures.iteritems() if v) 196 return bool(self.ligatures) 197 else: 198 assert 0, "unknown format: %s" % self.Format 199 200@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst) 201def closure_glyphs(self, s, cur_glyphs=None): 202 if cur_glyphs == None: cur_glyphs = s.glyphs 203 if self.Format == 1: 204 indices = self.Coverage.intersect(cur_glyphs) 205 if(not indices or 206 not all(c.intersect(s.glyphs) 207 for c in self.LookAheadCoverage + self.BacktrackCoverage)): 208 return 209 s.glyphs.update(self.Substitute[i] for i in indices) 210 else: 211 assert 0, "unknown format: %s" % self.Format 212 213@_add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst) 214def subset_glyphs(self, s): 215 if self.Format == 1: 216 indices = self.Coverage.subset(s.glyphs) 217 self.Substitute = [self.Substitute[i] for i in indices] 218 # Now drop rules generating glyphs we don't want 219 indices = [i for i,sub in enumerate(self.Substitute) 220 if sub in s.glyphs] 221 self.Substitute = [self.Substitute[i] for i in indices] 222 self.Coverage.remap(indices) 223 self.GlyphCount = len(self.Substitute) 224 return bool(self.GlyphCount and 225 all(c.subset(s.glyphs) 226 for c in self.LookAheadCoverage+self.BacktrackCoverage)) 227 else: 228 assert 0, "unknown format: %s" % self.Format 229 230@_add_method(fontTools.ttLib.tables.otTables.SinglePos) 231def subset_glyphs(self, s): 232 if self.Format == 1: 233 return len(self.Coverage.subset(s.glyphs)) 234 elif self.Format == 2: 235 indices = self.Coverage.subset(s.glyphs) 236 self.Value = [self.Value[i] for i in indices] 237 self.ValueCount = len(self.Value) 238 return bool(self.ValueCount) 239 else: 240 assert 0, "unknown format: %s" % self.Format 241 242@_add_method(fontTools.ttLib.tables.otTables.SinglePos) 243def prune_post_subset(self, options): 244 if not options.hinting: 245 # Drop device tables 246 self.ValueFormat &= ~0x00F0 247 return True 248 249@_add_method(fontTools.ttLib.tables.otTables.PairPos) 250def subset_glyphs(self, s): 251 if self.Format == 1: 252 indices = self.Coverage.subset(s.glyphs) 253 self.PairSet = [self.PairSet[i] for i in indices] 254 for p in self.PairSet: 255 p.PairValueRecord = [r for r in p.PairValueRecord 256 if r.SecondGlyph in s.glyphs] 257 p.PairValueCount = len(p.PairValueRecord) 258 self.PairSet = [p for p in self.PairSet if p.PairValueCount] 259 self.PairSetCount = len(self.PairSet) 260 return bool(self.PairSetCount) 261 elif self.Format == 2: 262 class1_map = self.ClassDef1.subset(s.glyphs, remap=True) 263 class2_map = self.ClassDef2.subset(s.glyphs, remap=True) 264 self.Class1Record = [self.Class1Record[i] for i in class1_map] 265 for c in self.Class1Record: 266 c.Class2Record = [c.Class2Record[i] for i in class2_map] 267 self.Class1Count = len(class1_map) 268 self.Class2Count = len(class2_map) 269 return bool(self.Class1Count and 270 self.Class2Count and 271 self.Coverage.subset(s.glyphs)) 272 else: 273 assert 0, "unknown format: %s" % self.Format 274 275@_add_method(fontTools.ttLib.tables.otTables.PairPos) 276def prune_post_subset(self, options): 277 if not options.hinting: 278 # Drop device tables 279 self.ValueFormat1 &= ~0x00F0 280 self.ValueFormat2 &= ~0x00F0 281 return True 282 283@_add_method(fontTools.ttLib.tables.otTables.CursivePos) 284def subset_glyphs(self, s): 285 if self.Format == 1: 286 indices = self.Coverage.subset(s.glyphs) 287 self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices] 288 self.EntryExitCount = len(self.EntryExitRecord) 289 return bool(self.EntryExitCount) 290 else: 291 assert 0, "unknown format: %s" % self.Format 292 293@_add_method(fontTools.ttLib.tables.otTables.Anchor) 294def prune_hints(self): 295 # Drop device tables / contour anchor point 296 self.Format = 1 297 298@_add_method(fontTools.ttLib.tables.otTables.CursivePos) 299def prune_post_subset(self, options): 300 if not options.hinting: 301 for rec in self.EntryExitRecord: 302 if rec.EntryAnchor: rec.EntryAnchor.prune_hints() 303 if rec.ExitAnchor: rec.ExitAnchor.prune_hints() 304 return True 305 306@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos) 307def subset_glyphs(self, s): 308 if self.Format == 1: 309 mark_indices = self.MarkCoverage.subset(s.glyphs) 310 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] 311 for i in mark_indices] 312 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord) 313 base_indices = self.BaseCoverage.subset(s.glyphs) 314 self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i] 315 for i in base_indices] 316 self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord) 317 # Prune empty classes 318 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord) 319 self.ClassCount = len(class_indices) 320 for m in self.MarkArray.MarkRecord: 321 m.Class = class_indices.index(m.Class) 322 for b in self.BaseArray.BaseRecord: 323 b.BaseAnchor = [b.BaseAnchor[i] for i in class_indices] 324 return bool(self.ClassCount and 325 self.MarkArray.MarkCount and 326 self.BaseArray.BaseCount) 327 else: 328 assert 0, "unknown format: %s" % self.Format 329 330@_add_method(fontTools.ttLib.tables.otTables.MarkBasePos) 331def prune_post_subset(self, options): 332 if not options.hinting: 333 for m in self.MarkArray.MarkRecord: 334 m.MarkAnchor.prune_hints() 335 for b in self.BaseArray.BaseRecord: 336 for a in b.BaseAnchor: 337 a.prune_hints() 338 return True 339 340@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos) 341def subset_glyphs(self, s): 342 if self.Format == 1: 343 mark_indices = self.MarkCoverage.subset(s.glyphs) 344 self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] 345 for i in mark_indices] 346 self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord) 347 ligature_indices = self.LigatureCoverage.subset(s.glyphs) 348 self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i] 349 for i in ligature_indices] 350 self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach) 351 # Prune empty classes 352 class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord) 353 self.ClassCount = len(class_indices) 354 for m in self.MarkArray.MarkRecord: 355 m.Class = class_indices.index(m.Class) 356 for l in self.LigatureArray.LigatureAttach: 357 for c in l.ComponentRecord: 358 c.LigatureAnchor = [c.LigatureAnchor[i] for i in class_indices] 359 return bool(self.ClassCount and 360 self.MarkArray.MarkCount and 361 self.LigatureArray.LigatureCount) 362 else: 363 assert 0, "unknown format: %s" % self.Format 364 365@_add_method(fontTools.ttLib.tables.otTables.MarkLigPos) 366def prune_post_subset(self, options): 367 if not options.hinting: 368 for m in self.MarkArray.MarkRecord: 369 m.MarkAnchor.prune_hints() 370 for l in self.LigatureArray.LigatureAttach: 371 for c in l.ComponentRecord: 372 for a in c.LigatureAnchor: 373 a.prune_hints() 374 return True 375 376@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos) 377def subset_glyphs(self, s): 378 if self.Format == 1: 379 mark1_indices = self.Mark1Coverage.subset(s.glyphs) 380 self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i] 381 for i in mark1_indices] 382 self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord) 383 mark2_indices = self.Mark2Coverage.subset(s.glyphs) 384 self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i] 385 for i in mark2_indices] 386 self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record) 387 # Prune empty classes 388 class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord) 389 self.ClassCount = len(class_indices) 390 for m in self.Mark1Array.MarkRecord: 391 m.Class = class_indices.index(m.Class) 392 for b in self.Mark2Array.Mark2Record: 393 b.Mark2Anchor = [b.Mark2Anchor[i] for i in class_indices] 394 return bool(self.ClassCount and 395 self.Mark1Array.MarkCount and 396 self.Mark2Array.MarkCount) 397 else: 398 assert 0, "unknown format: %s" % self.Format 399 400@_add_method(fontTools.ttLib.tables.otTables.MarkMarkPos) 401def prune_post_subset(self, options): 402 if not options.hinting: 403 # Drop device tables or contour anchor point 404 for m in self.Mark1Array.MarkRecord: 405 m.MarkAnchor.prune_hints() 406 for b in self.Mark2Array.Mark2Record: 407 for m in rec.Mark2Anchor: 408 m.prune_hints() 409 return True 410 411@_add_method(fontTools.ttLib.tables.otTables.SingleSubst, 412 fontTools.ttLib.tables.otTables.MultipleSubst, 413 fontTools.ttLib.tables.otTables.AlternateSubst, 414 fontTools.ttLib.tables.otTables.LigatureSubst, 415 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst, 416 fontTools.ttLib.tables.otTables.SinglePos, 417 fontTools.ttLib.tables.otTables.PairPos, 418 fontTools.ttLib.tables.otTables.CursivePos, 419 fontTools.ttLib.tables.otTables.MarkBasePos, 420 fontTools.ttLib.tables.otTables.MarkLigPos, 421 fontTools.ttLib.tables.otTables.MarkMarkPos) 422def subset_lookups(self, lookup_indices): 423 pass 424 425@_add_method(fontTools.ttLib.tables.otTables.SingleSubst, 426 fontTools.ttLib.tables.otTables.MultipleSubst, 427 fontTools.ttLib.tables.otTables.AlternateSubst, 428 fontTools.ttLib.tables.otTables.LigatureSubst, 429 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst, 430 fontTools.ttLib.tables.otTables.SinglePos, 431 fontTools.ttLib.tables.otTables.PairPos, 432 fontTools.ttLib.tables.otTables.CursivePos, 433 fontTools.ttLib.tables.otTables.MarkBasePos, 434 fontTools.ttLib.tables.otTables.MarkLigPos, 435 fontTools.ttLib.tables.otTables.MarkMarkPos) 436def collect_lookups(self): 437 return [] 438 439@_add_method(fontTools.ttLib.tables.otTables.SingleSubst, 440 fontTools.ttLib.tables.otTables.MultipleSubst, 441 fontTools.ttLib.tables.otTables.AlternateSubst, 442 fontTools.ttLib.tables.otTables.LigatureSubst, 443 fontTools.ttLib.tables.otTables.ContextSubst, 444 fontTools.ttLib.tables.otTables.ChainContextSubst, 445 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst, 446 fontTools.ttLib.tables.otTables.SinglePos, 447 fontTools.ttLib.tables.otTables.PairPos, 448 fontTools.ttLib.tables.otTables.CursivePos, 449 fontTools.ttLib.tables.otTables.MarkBasePos, 450 fontTools.ttLib.tables.otTables.MarkLigPos, 451 fontTools.ttLib.tables.otTables.MarkMarkPos, 452 fontTools.ttLib.tables.otTables.ContextPos, 453 fontTools.ttLib.tables.otTables.ChainContextPos) 454def prune_pre_subset(self, options): 455 return True 456 457@_add_method(fontTools.ttLib.tables.otTables.SingleSubst, 458 fontTools.ttLib.tables.otTables.MultipleSubst, 459 fontTools.ttLib.tables.otTables.AlternateSubst, 460 fontTools.ttLib.tables.otTables.LigatureSubst, 461 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst, 462 fontTools.ttLib.tables.otTables.ContextSubst, 463 fontTools.ttLib.tables.otTables.ChainContextSubst, 464 fontTools.ttLib.tables.otTables.ContextPos, 465 fontTools.ttLib.tables.otTables.ChainContextPos) 466def prune_post_subset(self, options): 467 return True 468 469@_add_method(fontTools.ttLib.tables.otTables.SingleSubst, 470 fontTools.ttLib.tables.otTables.AlternateSubst, 471 fontTools.ttLib.tables.otTables.ReverseChainSingleSubst) 472def may_have_non_1to1(self): 473 return False 474 475@_add_method(fontTools.ttLib.tables.otTables.MultipleSubst, 476 fontTools.ttLib.tables.otTables.LigatureSubst, 477 fontTools.ttLib.tables.otTables.ContextSubst, 478 fontTools.ttLib.tables.otTables.ChainContextSubst) 479def may_have_non_1to1(self): 480 return True 481 482@_add_method(fontTools.ttLib.tables.otTables.ContextSubst, 483 fontTools.ttLib.tables.otTables.ChainContextSubst, 484 fontTools.ttLib.tables.otTables.ContextPos, 485 fontTools.ttLib.tables.otTables.ChainContextPos) 486def __classify_context(self): 487 488 class ContextHelper(object): 489 def __init__(self, klass, Format): 490 if klass.__name__.endswith('Subst'): 491 Typ = 'Sub' 492 Type = 'Subst' 493 else: 494 Typ = 'Pos' 495 Type = 'Pos' 496 if klass.__name__.startswith('Chain'): 497 Chain = 'Chain' 498 else: 499 Chain = '' 500 ChainTyp = Chain+Typ 501 502 self.Typ = Typ 503 self.Type = Type 504 self.Chain = Chain 505 self.ChainTyp = ChainTyp 506 507 self.LookupRecord = Type+'LookupRecord' 508 509 if Format == 1: 510 Coverage = lambda r: r.Coverage 511 ChainCoverage = lambda r: r.Coverage 512 ContextData = lambda r:(None,) 513 ChainContextData = lambda r:(None, None, None) 514 RuleData = lambda r:(r.Input,) 515 ChainRuleData = lambda r:(r.Backtrack, r.Input, r.LookAhead) 516 SetRuleData = None 517 ChainSetRuleData = None 518 elif Format == 2: 519 Coverage = lambda r: r.Coverage 520 ChainCoverage = lambda r: r.Coverage 521 ContextData = lambda r:(r.ClassDef,) 522 ChainContextData = lambda r:(r.LookAheadClassDef, 523 r.InputClassDef, 524 r.BacktrackClassDef) 525 RuleData = lambda r:(r.Class,) 526 ChainRuleData = lambda r:(r.LookAhead, r.Input, r.Backtrack) 527 def SetRuleData(r, d):(r.Class,) = d 528 def ChainSetRuleData(r, d):(r.LookAhead, r.Input, r.Backtrack) = d 529 elif Format == 3: 530 Coverage = lambda r: r.Coverage[0] 531 ChainCoverage = lambda r: r.InputCoverage[0] 532 ContextData = None 533 ChainContextData = None 534 RuleData = lambda r: r.Coverage 535 ChainRuleData = lambda r:(r.LookAheadCoverage + 536 r.InputCoverage + 537 r.BacktrackCoverage) 538 SetRuleData = None 539 ChainSetRuleData = None 540 else: 541 assert 0, "unknown format: %s" % Format 542 543 if Chain: 544 self.Coverage = ChainCoverage 545 self.ContextData = ChainContextData 546 self.RuleData = ChainRuleData 547 self.SetRuleData = ChainSetRuleData 548 else: 549 self.Coverage = Coverage 550 self.ContextData = ContextData 551 self.RuleData = RuleData 552 self.SetRuleData = SetRuleData 553 554 if Format == 1: 555 self.Rule = ChainTyp+'Rule' 556 self.RuleCount = ChainTyp+'RuleCount' 557 self.RuleSet = ChainTyp+'RuleSet' 558 self.RuleSetCount = ChainTyp+'RuleSetCount' 559 self.Intersect = lambda glyphs, c, r: [r] if r in glyphs else [] 560 elif Format == 2: 561 self.Rule = ChainTyp+'ClassRule' 562 self.RuleCount = ChainTyp+'ClassRuleCount' 563 self.RuleSet = ChainTyp+'ClassSet' 564 self.RuleSetCount = ChainTyp+'ClassSetCount' 565 self.Intersect = lambda glyphs, c, r: c.intersect_class(glyphs, r) 566 567 self.ClassDef = 'InputClassDef' if Chain else 'ClassDef' 568 self.Input = 'Input' if Chain else 'Class' 569 570 if self.Format not in [1, 2, 3]: 571 return None # Don't shoot the messenger; let it go 572 if not hasattr(self.__class__, "__ContextHelpers"): 573 self.__class__.__ContextHelpers = {} 574 if self.Format not in self.__class__.__ContextHelpers: 575 helper = ContextHelper(self.__class__, self.Format) 576 self.__class__.__ContextHelpers[self.Format] = helper 577 return self.__class__.__ContextHelpers[self.Format] 578 579@_add_method(fontTools.ttLib.tables.otTables.ContextSubst, 580 fontTools.ttLib.tables.otTables.ChainContextSubst) 581def closure_glyphs(self, s, cur_glyphs=None): 582 if cur_glyphs == None: cur_glyphs = s.glyphs 583 c = self.__classify_context() 584 585 indices = c.Coverage(self).intersect(s.glyphs) 586 if not indices: 587 return [] 588 cur_glyphs = c.Coverage(self).intersect_glyphs(s.glyphs); 589 590 if self.Format == 1: 591 ContextData = c.ContextData(self) 592 rss = getattr(self, c.RuleSet) 593 for i in indices: 594 if not rss[i]: continue 595 for r in getattr(rss[i], c.Rule): 596 if not r: continue 597 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist) 598 for cd,klist in zip(ContextData, c.RuleData(r))): 599 chaos = False 600 for ll in getattr(r, c.LookupRecord): 601 if not ll: continue 602 seqi = ll.SequenceIndex 603 if chaos: 604 pos_glyphs = s.glyphs 605 else: 606 if seqi == 0: 607 pos_glyphs = set([c.Coverage(self).glyphs[i]]) 608 else: 609 pos_glyphs = set([r.Input[seqi - 1]]) 610 lookup = s.table.LookupList.Lookup[ll.LookupListIndex] 611 chaos = chaos or lookup.may_have_non_1to1() 612 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs) 613 elif self.Format == 2: 614 ClassDef = getattr(self, c.ClassDef) 615 indices = ClassDef.intersect(cur_glyphs) 616 ContextData = c.ContextData(self) 617 rss = getattr(self, c.RuleSet) 618 for i in indices: 619 if not rss[i]: continue 620 for r in getattr(rss[i], c.Rule): 621 if not r: continue 622 if all(all(c.Intersect(s.glyphs, cd, k) for k in klist) 623 for cd,klist in zip(ContextData, c.RuleData(r))): 624 chaos = False 625 for ll in getattr(r, c.LookupRecord): 626 if not ll: continue 627 seqi = ll.SequenceIndex 628 if chaos: 629 pos_glyphs = s.glyphs 630 else: 631 if seqi == 0: 632 pos_glyphs = ClassDef.intersect_class(cur_glyphs, i) 633 else: 634 pos_glyphs = ClassDef.intersect_class(s.glyphs, 635 getattr(r, c.Input)[seqi - 1]) 636 lookup = s.table.LookupList.Lookup[ll.LookupListIndex] 637 chaos = chaos or lookup.may_have_non_1to1() 638 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs) 639 elif self.Format == 3: 640 if not all(x.intersect(s.glyphs) for x in c.RuleData(self)): 641 return [] 642 r = self 643 chaos = False 644 for ll in getattr(r, c.LookupRecord): 645 if not ll: continue 646 seqi = ll.SequenceIndex 647 if chaos: 648 pos_glyphs = s.glyphs 649 else: 650 if seqi == 0: 651 pos_glyphs = cur_glyphs 652 else: 653 pos_glyphs = r.InputCoverage[seqi].intersect_glyphs(s.glyphs) 654 lookup = s.table.LookupList.Lookup[ll.LookupListIndex] 655 chaos = chaos or lookup.may_have_non_1to1() 656 lookup.closure_glyphs(s, cur_glyphs=pos_glyphs) 657 else: 658 assert 0, "unknown format: %s" % self.Format 659 660@_add_method(fontTools.ttLib.tables.otTables.ContextSubst, 661 fontTools.ttLib.tables.otTables.ContextPos, 662 fontTools.ttLib.tables.otTables.ChainContextSubst, 663 fontTools.ttLib.tables.otTables.ChainContextPos) 664def subset_glyphs(self, s): 665 c = self.__classify_context() 666 667 if self.Format == 1: 668 indices = self.Coverage.subset(s.glyphs) 669 rss = getattr(self, c.RuleSet) 670 rss = [rss[i] for i in indices] 671 for rs in rss: 672 if not rs: continue 673 ss = getattr(rs, c.Rule) 674 ss = [r for r in ss 675 if r and all(all(g in s.glyphs for g in glist) 676 for glist in c.RuleData(r))] 677 setattr(rs, c.Rule, ss) 678 setattr(rs, c.RuleCount, len(ss)) 679 # Prune empty subrulesets 680 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)] 681 setattr(self, c.RuleSet, rss) 682 setattr(self, c.RuleSetCount, len(rss)) 683 return bool(rss) 684 elif self.Format == 2: 685 if not self.Coverage.subset(s.glyphs): 686 return False 687 indices = getattr(self, c.ClassDef).subset(self.Coverage.glyphs, 688 remap=False) 689 rss = getattr(self, c.RuleSet) 690 rss = [rss[i] for i in indices] 691 ContextData = c.ContextData(self) 692 klass_maps = [x.subset(s.glyphs, remap=True) for x in ContextData] 693 for rs in rss: 694 if not rs: continue 695 ss = getattr(rs, c.Rule) 696 ss = [r for r in ss 697 if r and all(all(k in klass_map for k in klist) 698 for klass_map,klist in zip(klass_maps, c.RuleData(r)))] 699 setattr(rs, c.Rule, ss) 700 setattr(rs, c.RuleCount, len(ss)) 701 702 # Remap rule classes 703 for r in ss: 704 c.SetRuleData(r, [[klass_map.index(k) for k in klist] 705 for klass_map,klist in zip(klass_maps, c.RuleData(r))]) 706 # Prune empty subrulesets 707 rss = [rs for rs in rss if rs and getattr(rs, c.Rule)] 708 setattr(self, c.RuleSet, rss) 709 setattr(self, c.RuleSetCount, len(rss)) 710 return bool(rss) 711 elif self.Format == 3: 712 return all(x.subset(s.glyphs) for x in c.RuleData(self)) 713 else: 714 assert 0, "unknown format: %s" % self.Format 715 716@_add_method(fontTools.ttLib.tables.otTables.ContextSubst, 717 fontTools.ttLib.tables.otTables.ChainContextSubst, 718 fontTools.ttLib.tables.otTables.ContextPos, 719 fontTools.ttLib.tables.otTables.ChainContextPos) 720def subset_lookups(self, lookup_indices): 721 c = self.__classify_context() 722 723 if self.Format in [1, 2]: 724 for rs in getattr(self, c.RuleSet): 725 if not rs: continue 726 for r in getattr(rs, c.Rule): 727 if not r: continue 728 setattr(r, c.LookupRecord, 729 [ll for ll in getattr(r, c.LookupRecord) 730 if ll and ll.LookupListIndex in lookup_indices]) 731 for ll in getattr(r, c.LookupRecord): 732 if not ll: continue 733 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex) 734 elif self.Format == 3: 735 setattr(self, c.LookupRecord, 736 [ll for ll in getattr(self, c.LookupRecord) 737 if ll and ll.LookupListIndex in lookup_indices]) 738 for ll in getattr(self, c.LookupRecord): 739 if not ll: continue 740 ll.LookupListIndex = lookup_indices.index(ll.LookupListIndex) 741 else: 742 assert 0, "unknown format: %s" % self.Format 743 744@_add_method(fontTools.ttLib.tables.otTables.ContextSubst, 745 fontTools.ttLib.tables.otTables.ChainContextSubst, 746 fontTools.ttLib.tables.otTables.ContextPos, 747 fontTools.ttLib.tables.otTables.ChainContextPos) 748def collect_lookups(self): 749 c = self.__classify_context() 750 751 if self.Format in [1, 2]: 752 return [ll.LookupListIndex 753 for rs in getattr(self, c.RuleSet) if rs 754 for r in getattr(rs, c.Rule) if r 755 for ll in getattr(r, c.LookupRecord) if ll] 756 elif self.Format == 3: 757 return [ll.LookupListIndex 758 for ll in getattr(self, c.LookupRecord) if ll] 759 else: 760 assert 0, "unknown format: %s" % self.Format 761 762@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst) 763def closure_glyphs(self, s, cur_glyphs=None): 764 if self.Format == 1: 765 self.ExtSubTable.closure_glyphs(s, cur_glyphs) 766 else: 767 assert 0, "unknown format: %s" % self.Format 768 769@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst) 770def may_have_non_1to1(self): 771 if self.Format == 1: 772 return self.ExtSubTable.may_have_non_1to1() 773 else: 774 assert 0, "unknown format: %s" % self.Format 775 776@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, 777 fontTools.ttLib.tables.otTables.ExtensionPos) 778def prune_pre_subset(self, options): 779 if self.Format == 1: 780 return self.ExtSubTable.prune_pre_subset(options) 781 else: 782 assert 0, "unknown format: %s" % self.Format 783 784@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, 785 fontTools.ttLib.tables.otTables.ExtensionPos) 786def subset_glyphs(self, s): 787 if self.Format == 1: 788 return self.ExtSubTable.subset_glyphs(s) 789 else: 790 assert 0, "unknown format: %s" % self.Format 791 792@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, 793 fontTools.ttLib.tables.otTables.ExtensionPos) 794def prune_post_subset(self, options): 795 if self.Format == 1: 796 return self.ExtSubTable.prune_post_subset(options) 797 else: 798 assert 0, "unknown format: %s" % self.Format 799 800@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, 801 fontTools.ttLib.tables.otTables.ExtensionPos) 802def subset_lookups(self, lookup_indices): 803 if self.Format == 1: 804 return self.ExtSubTable.subset_lookups(lookup_indices) 805 else: 806 assert 0, "unknown format: %s" % self.Format 807 808@_add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, 809 fontTools.ttLib.tables.otTables.ExtensionPos) 810def collect_lookups(self): 811 if self.Format == 1: 812 return self.ExtSubTable.collect_lookups() 813 else: 814 assert 0, "unknown format: %s" % self.Format 815 816@_add_method(fontTools.ttLib.tables.otTables.Lookup) 817def closure_glyphs(self, s, cur_glyphs=None): 818 for st in self.SubTable: 819 if not st: continue 820 st.closure_glyphs(s, cur_glyphs) 821 822@_add_method(fontTools.ttLib.tables.otTables.Lookup) 823def prune_pre_subset(self, options): 824 ret = False 825 for st in self.SubTable: 826 if not st: continue 827 if st.prune_pre_subset(options): ret = True 828 return ret 829 830@_add_method(fontTools.ttLib.tables.otTables.Lookup) 831def subset_glyphs(self, s): 832 self.SubTable = [st for st in self.SubTable if st and st.subset_glyphs(s)] 833 self.SubTableCount = len(self.SubTable) 834 return bool(self.SubTableCount) 835 836@_add_method(fontTools.ttLib.tables.otTables.Lookup) 837def prune_post_subset(self, options): 838 ret = False 839 for st in self.SubTable: 840 if not st: continue 841 if st.prune_post_subset(options): ret = True 842 return ret 843 844@_add_method(fontTools.ttLib.tables.otTables.Lookup) 845def subset_lookups(self, lookup_indices): 846 for s in self.SubTable: 847 s.subset_lookups(lookup_indices) 848 849@_add_method(fontTools.ttLib.tables.otTables.Lookup) 850def collect_lookups(self): 851 return _uniq_sort(sum((st.collect_lookups() for st in self.SubTable 852 if st), [])) 853 854@_add_method(fontTools.ttLib.tables.otTables.Lookup) 855def may_have_non_1to1(self): 856 return any(st.may_have_non_1to1() for st in self.SubTable if st) 857 858@_add_method(fontTools.ttLib.tables.otTables.LookupList) 859def prune_pre_subset(self, options): 860 ret = False 861 for l in self.Lookup: 862 if not l: continue 863 if l.prune_pre_subset(options): ret = True 864 return ret 865 866@_add_method(fontTools.ttLib.tables.otTables.LookupList) 867def subset_glyphs(self, s): 868 "Returns the indices of nonempty lookups." 869 return [i for i,l in enumerate(self.Lookup) if l and l.subset_glyphs(s)] 870 871@_add_method(fontTools.ttLib.tables.otTables.LookupList) 872def prune_post_subset(self, options): 873 ret = False 874 for l in self.Lookup: 875 if not l: continue 876 if l.prune_post_subset(options): ret = True 877 return ret 878 879@_add_method(fontTools.ttLib.tables.otTables.LookupList) 880def subset_lookups(self, lookup_indices): 881 self.Lookup = [self.Lookup[i] for i in lookup_indices 882 if i < self.LookupCount] 883 self.LookupCount = len(self.Lookup) 884 for l in self.Lookup: 885 l.subset_lookups(lookup_indices) 886 887@_add_method(fontTools.ttLib.tables.otTables.LookupList) 888def closure_lookups(self, lookup_indices): 889 lookup_indices = _uniq_sort(lookup_indices) 890 recurse = lookup_indices 891 while True: 892 recurse_lookups = sum((self.Lookup[i].collect_lookups() 893 for i in recurse if i < self.LookupCount), []) 894 recurse_lookups = [l for l in recurse_lookups 895 if l not in lookup_indices and l < self.LookupCount] 896 if not recurse_lookups: 897 return _uniq_sort(lookup_indices) 898 recurse_lookups = _uniq_sort(recurse_lookups) 899 lookup_indices.extend(recurse_lookups) 900 recurse = recurse_lookups 901 902@_add_method(fontTools.ttLib.tables.otTables.Feature) 903def subset_lookups(self, lookup_indices): 904 self.LookupListIndex = [l for l in self.LookupListIndex 905 if l in lookup_indices] 906 # Now map them. 907 self.LookupListIndex = [lookup_indices.index(l) 908 for l in self.LookupListIndex] 909 self.LookupCount = len(self.LookupListIndex) 910 return self.LookupCount 911 912@_add_method(fontTools.ttLib.tables.otTables.Feature) 913def collect_lookups(self): 914 return self.LookupListIndex[:] 915 916@_add_method(fontTools.ttLib.tables.otTables.FeatureList) 917def subset_lookups(self, lookup_indices): 918 "Returns the indices of nonempty features." 919 feature_indices = [i for i,f in enumerate(self.FeatureRecord) 920 if f.Feature.subset_lookups(lookup_indices)] 921 self.subset_features(feature_indices) 922 return feature_indices 923 924@_add_method(fontTools.ttLib.tables.otTables.FeatureList) 925def collect_lookups(self, feature_indices): 926 return _uniq_sort(sum((self.FeatureRecord[i].Feature.collect_lookups() 927 for i in feature_indices 928 if i < self.FeatureCount), [])) 929 930@_add_method(fontTools.ttLib.tables.otTables.FeatureList) 931def subset_features(self, feature_indices): 932 self.FeatureRecord = [self.FeatureRecord[i] for i in feature_indices] 933 self.FeatureCount = len(self.FeatureRecord) 934 return bool(self.FeatureCount) 935 936@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys, 937 fontTools.ttLib.tables.otTables.LangSys) 938def subset_features(self, feature_indices): 939 if self.ReqFeatureIndex in feature_indices: 940 self.ReqFeatureIndex = feature_indices.index(self.ReqFeatureIndex) 941 else: 942 self.ReqFeatureIndex = 65535 943 self.FeatureIndex = [f for f in self.FeatureIndex if f in feature_indices] 944 # Now map them. 945 self.FeatureIndex = [feature_indices.index(f) for f in self.FeatureIndex 946 if f in feature_indices] 947 self.FeatureCount = len(self.FeatureIndex) 948 return bool(self.FeatureCount or self.ReqFeatureIndex != 65535) 949 950@_add_method(fontTools.ttLib.tables.otTables.DefaultLangSys, 951 fontTools.ttLib.tables.otTables.LangSys) 952def collect_features(self): 953 feature_indices = self.FeatureIndex[:] 954 if self.ReqFeatureIndex != 65535: 955 feature_indices.append(self.ReqFeatureIndex) 956 return _uniq_sort(feature_indices) 957 958@_add_method(fontTools.ttLib.tables.otTables.Script) 959def subset_features(self, feature_indices): 960 if(self.DefaultLangSys and 961 not self.DefaultLangSys.subset_features(feature_indices)): 962 self.DefaultLangSys = None 963 self.LangSysRecord = [l for l in self.LangSysRecord 964 if l.LangSys.subset_features(feature_indices)] 965 self.LangSysCount = len(self.LangSysRecord) 966 return bool(self.LangSysCount or self.DefaultLangSys) 967 968@_add_method(fontTools.ttLib.tables.otTables.Script) 969def collect_features(self): 970 feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord] 971 if self.DefaultLangSys: 972 feature_indices.append(self.DefaultLangSys.collect_features()) 973 return _uniq_sort(sum(feature_indices, [])) 974 975@_add_method(fontTools.ttLib.tables.otTables.ScriptList) 976def subset_features(self, feature_indices): 977 self.ScriptRecord = [s for s in self.ScriptRecord 978 if s.Script.subset_features(feature_indices)] 979 self.ScriptCount = len(self.ScriptRecord) 980 return bool(self.ScriptCount) 981 982@_add_method(fontTools.ttLib.tables.otTables.ScriptList) 983def collect_features(self): 984 return _uniq_sort(sum((s.Script.collect_features() 985 for s in self.ScriptRecord), [])) 986 987@_add_method(fontTools.ttLib.getTableClass('GSUB')) 988def closure_glyphs(self, s): 989 s.table = self.table 990 feature_indices = self.table.ScriptList.collect_features() 991 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices) 992 while True: 993 orig_glyphs = s.glyphs.copy() 994 for i in lookup_indices: 995 if i >= self.table.LookupList.LookupCount: continue 996 if not self.table.LookupList.Lookup[i]: continue 997 self.table.LookupList.Lookup[i].closure_glyphs(s) 998 if orig_glyphs == s.glyphs: 999 break 1000 del s.table 1001 1002@_add_method(fontTools.ttLib.getTableClass('GSUB'), 1003 fontTools.ttLib.getTableClass('GPOS')) 1004def subset_glyphs(self, s): 1005 s.glyphs = s.glyphs_gsubed 1006 lookup_indices = self.table.LookupList.subset_glyphs(s) 1007 self.subset_lookups(lookup_indices) 1008 self.prune_lookups() 1009 return True 1010 1011@_add_method(fontTools.ttLib.getTableClass('GSUB'), 1012 fontTools.ttLib.getTableClass('GPOS')) 1013def subset_lookups(self, lookup_indices): 1014 """Retrains specified lookups, then removes empty features, language 1015 systems, and scripts.""" 1016 self.table.LookupList.subset_lookups(lookup_indices) 1017 feature_indices = self.table.FeatureList.subset_lookups(lookup_indices) 1018 self.table.ScriptList.subset_features(feature_indices) 1019 1020@_add_method(fontTools.ttLib.getTableClass('GSUB'), 1021 fontTools.ttLib.getTableClass('GPOS')) 1022def prune_lookups(self): 1023 "Remove unreferenced lookups" 1024 feature_indices = self.table.ScriptList.collect_features() 1025 lookup_indices = self.table.FeatureList.collect_lookups(feature_indices) 1026 lookup_indices = self.table.LookupList.closure_lookups(lookup_indices) 1027 self.subset_lookups(lookup_indices) 1028 1029@_add_method(fontTools.ttLib.getTableClass('GSUB'), 1030 fontTools.ttLib.getTableClass('GPOS')) 1031def subset_feature_tags(self, feature_tags): 1032 feature_indices = [i for i,f in 1033 enumerate(self.table.FeatureList.FeatureRecord) 1034 if f.FeatureTag in feature_tags] 1035 self.table.FeatureList.subset_features(feature_indices) 1036 self.table.ScriptList.subset_features(feature_indices) 1037 1038@_add_method(fontTools.ttLib.getTableClass('GSUB'), 1039 fontTools.ttLib.getTableClass('GPOS')) 1040def prune_pre_subset(self, options): 1041 if '*' not in options.layout_features: 1042 self.subset_feature_tags(options.layout_features) 1043 self.prune_lookups() 1044 self.table.LookupList.prune_pre_subset(options); 1045 return True 1046 1047@_add_method(fontTools.ttLib.getTableClass('GSUB'), 1048 fontTools.ttLib.getTableClass('GPOS')) 1049def prune_post_subset(self, options): 1050 self.table.LookupList.prune_post_subset(options); 1051 return True 1052 1053@_add_method(fontTools.ttLib.getTableClass('GDEF')) 1054def subset_glyphs(self, s): 1055 glyphs = s.glyphs_gsubed 1056 table = self.table 1057 if table.LigCaretList: 1058 indices = table.LigCaretList.Coverage.subset(glyphs) 1059 table.LigCaretList.LigGlyph = [table.LigCaretList.LigGlyph[i] 1060 for i in indices] 1061 table.LigCaretList.LigGlyphCount = len(table.LigCaretList.LigGlyph) 1062 if not table.LigCaretList.LigGlyphCount: 1063 table.LigCaretList = None 1064 if table.MarkAttachClassDef: 1065 table.MarkAttachClassDef.classDefs = dict((g,v) for g,v in 1066 table.MarkAttachClassDef. 1067 classDefs.iteritems() 1068 if g in glyphs) 1069 if not table.MarkAttachClassDef.classDefs: 1070 table.MarkAttachClassDef = None 1071 if table.GlyphClassDef: 1072 table.GlyphClassDef.classDefs = dict((g,v) for g,v in 1073 table.GlyphClassDef. 1074 classDefs.iteritems() 1075 if g in glyphs) 1076 if not table.GlyphClassDef.classDefs: 1077 table.GlyphClassDef = None 1078 if table.AttachList: 1079 indices = table.AttachList.Coverage.subset(glyphs) 1080 table.AttachList.AttachPoint = [table.AttachList.AttachPoint[i] 1081 for i in indices] 1082 table.AttachList.GlyphCount = len(table.AttachList.AttachPoint) 1083 if not table.AttachList.GlyphCount: 1084 table.AttachList = None 1085 return bool(table.LigCaretList or 1086 table.MarkAttachClassDef or 1087 table.GlyphClassDef or 1088 table.AttachList) 1089 1090@_add_method(fontTools.ttLib.getTableClass('kern')) 1091def prune_pre_subset(self, options): 1092 # Prune unknown kern table types 1093 self.kernTables = [t for t in self.kernTables if hasattr(t, 'kernTable')] 1094 return bool(self.kernTables) 1095 1096@_add_method(fontTools.ttLib.getTableClass('kern')) 1097def subset_glyphs(self, s): 1098 glyphs = s.glyphs_gsubed 1099 for t in self.kernTables: 1100 t.kernTable = dict(((a,b),v) for (a,b),v in t.kernTable.iteritems() 1101 if a in glyphs and b in glyphs) 1102 self.kernTables = [t for t in self.kernTables if t.kernTable] 1103 return bool(self.kernTables) 1104 1105@_add_method(fontTools.ttLib.getTableClass('vmtx'), 1106 fontTools.ttLib.getTableClass('hmtx')) 1107def subset_glyphs(self, s): 1108 self.metrics = dict((g,v) for g,v in self.metrics.iteritems() if g in s.glyphs) 1109 return bool(self.metrics) 1110 1111@_add_method(fontTools.ttLib.getTableClass('hdmx')) 1112def subset_glyphs(self, s): 1113 self.hdmx = dict((sz,_dict((g,v) for g,v in l.iteritems() if g in s.glyphs)) 1114 for sz,l in self.hdmx.iteritems()) 1115 return bool(self.hdmx) 1116 1117@_add_method(fontTools.ttLib.getTableClass('VORG')) 1118def subset_glyphs(self, s): 1119 self.VOriginRecords = dict((g,v) for g,v in self.VOriginRecords.iteritems() 1120 if g in s.glyphs) 1121 self.numVertOriginYMetrics = len(self.VOriginRecords) 1122 return True # Never drop; has default metrics 1123 1124@_add_method(fontTools.ttLib.getTableClass('post')) 1125def prune_pre_subset(self, options): 1126 if not options.glyph_names: 1127 self.formatType = 3.0 1128 return True 1129 1130@_add_method(fontTools.ttLib.getTableClass('post')) 1131def subset_glyphs(self, s): 1132 self.extraNames = [] # This seems to do it 1133 return True 1134 1135@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph) 1136def getComponentNamesFast(self, glyfTable): 1137 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0: 1138 return [] # Not composite 1139 data = self.data 1140 i = 10 1141 components = [] 1142 more = 1 1143 while more: 1144 flags, glyphID = struct.unpack(">HH", data[i:i+4]) 1145 i += 4 1146 flags = int(flags) 1147 components.append(glyfTable.getGlyphName(int(glyphID))) 1148 1149 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS 1150 else: i += 2 1151 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE 1152 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE 1153 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO 1154 more = flags & 0x0020 # MORE_COMPONENTS 1155 1156 return components 1157 1158@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph) 1159def remapComponentsFast(self, indices): 1160 if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0: 1161 return # Not composite 1162 data = array.array("B", self.data) 1163 i = 10 1164 more = 1 1165 while more: 1166 flags =(data[i] << 8) | data[i+1] 1167 glyphID =(data[i+2] << 8) | data[i+3] 1168 # Remap 1169 glyphID = indices.index(glyphID) 1170 data[i+2] = glyphID >> 8 1171 data[i+3] = glyphID & 0xFF 1172 i += 4 1173 flags = int(flags) 1174 1175 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS 1176 else: i += 2 1177 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE 1178 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE 1179 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO 1180 more = flags & 0x0020 # MORE_COMPONENTS 1181 1182 self.data = data.tostring() 1183 1184@_add_method(fontTools.ttLib.getTableModule('glyf').Glyph) 1185def dropInstructionsFast(self): 1186 if not self.data: 1187 return 1188 numContours = struct.unpack(">h", self.data[:2])[0] 1189 data = array.array("B", self.data) 1190 i = 10 1191 if numContours >= 0: 1192 i += 2 * numContours # endPtsOfContours 1193 instructionLen =(data[i] << 8) | data[i+1] 1194 # Zero it 1195 data[i] = data [i+1] = 0 1196 i += 2 1197 if instructionLen: 1198 # Splice it out 1199 data = data[:i] + data[i+instructionLen:] 1200 else: 1201 more = 1 1202 while more: 1203 flags =(data[i] << 8) | data[i+1] 1204 # Turn instruction flag off 1205 flags &= ~0x0100 # WE_HAVE_INSTRUCTIONS 1206 data[i+0] = flags >> 8 1207 data[i+1] = flags & 0xFF 1208 i += 4 1209 flags = int(flags) 1210 1211 if flags & 0x0001: i += 4 # ARG_1_AND_2_ARE_WORDS 1212 else: i += 2 1213 if flags & 0x0008: i += 2 # WE_HAVE_A_SCALE 1214 elif flags & 0x0040: i += 4 # WE_HAVE_AN_X_AND_Y_SCALE 1215 elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO 1216 more = flags & 0x0020 # MORE_COMPONENTS 1217 1218 # Cut off 1219 data = data[:i] 1220 if len(data) % 4: 1221 # add pad bytes 1222 nPadBytes = 4 -(len(data) % 4) 1223 for i in range(nPadBytes): 1224 data.append(0) 1225 self.data = data.tostring() 1226 1227@_add_method(fontTools.ttLib.getTableClass('glyf')) 1228def closure_glyphs(self, s): 1229 decompose = s.glyphs 1230 # I don't know if component glyphs can be composite themselves. 1231 # We handle them anyway. 1232 while True: 1233 components = set() 1234 for g in decompose: 1235 if g not in self.glyphs: 1236 continue 1237 gl = self.glyphs[g] 1238 if hasattr(gl, "data"): 1239 for c in gl.getComponentNamesFast(self): 1240 if c not in s.glyphs: 1241 components.add(c) 1242 else: 1243 # TTX seems to expand gid0..3 always 1244 if gl.isComposite(): 1245 for c in gl.components: 1246 if c.glyphName not in s.glyphs: 1247 components.add(c.glyphName) 1248 components = set(c for c in components if c not in s.glyphs) 1249 if not components: 1250 break 1251 decompose = components 1252 s.glyphs.update(components) 1253 1254@_add_method(fontTools.ttLib.getTableClass('glyf')) 1255def prune_pre_subset(self, options): 1256 if options.notdef_glyph and not options.notdef_outline: 1257 g = self[self.glyphOrder[0]] 1258 # Yay, easy! 1259 g.__dict__.clear() 1260 g.data = "" 1261 return True 1262 1263@_add_method(fontTools.ttLib.getTableClass('glyf')) 1264def subset_glyphs(self, s): 1265 self.glyphs = dict((g,v) for g,v in self.glyphs.iteritems() if g in s.glyphs) 1266 indices = [i for i,g in enumerate(self.glyphOrder) if g in s.glyphs] 1267 for v in self.glyphs.itervalues(): 1268 if hasattr(v, "data"): 1269 v.remapComponentsFast(indices) 1270 else: 1271 pass # No need 1272 self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs] 1273 # Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset. 1274 return True 1275 1276@_add_method(fontTools.ttLib.getTableClass('glyf')) 1277def prune_post_subset(self, options): 1278 if not options.hinting: 1279 for v in self.glyphs.itervalues(): 1280 if hasattr(v, "data"): 1281 v.dropInstructionsFast() 1282 else: 1283 v.program = fontTools.ttLib.tables.ttProgram.Program() 1284 v.program.fromBytecode([]) 1285 return True 1286 1287@_add_method(fontTools.ttLib.getTableClass('CFF ')) 1288def prune_pre_subset(self, options): 1289 cff = self.cff 1290 # CFF table must have one font only 1291 cff.fontNames = cff.fontNames[:1] 1292 1293 if options.notdef_glyph and not options.notdef_outline: 1294 for fontname in cff.keys(): 1295 font = cff[fontname] 1296 c,_ = font.CharStrings.getItemAndSelector('.notdef') 1297 c.bytecode = '\x0e' # endchar 1298 c.program = None 1299 1300 return True # bool(cff.fontNames) 1301 1302@_add_method(fontTools.ttLib.getTableClass('CFF ')) 1303def subset_glyphs(self, s): 1304 cff = self.cff 1305 for fontname in cff.keys(): 1306 font = cff[fontname] 1307 cs = font.CharStrings 1308 1309 # Load all glyphs 1310 for g in font.charset: 1311 if g not in s.glyphs: continue 1312 c,sel = cs.getItemAndSelector(g) 1313 1314 if cs.charStringsAreIndexed: 1315 indices = [i for i,g in enumerate(font.charset) if g in s.glyphs] 1316 csi = cs.charStringsIndex 1317 csi.items = [csi.items[i] for i in indices] 1318 csi.count = len(csi.items) 1319 del csi.file, csi.offsets 1320 if hasattr(font, "FDSelect"): 1321 sel = font.FDSelect 1322 sel.format = None 1323 sel.gidArray = [sel.gidArray[i] for i in indices] 1324 cs.charStrings = dict((g,indices.index(v)) 1325 for g,v in cs.charStrings.iteritems() 1326 if g in s.glyphs) 1327 else: 1328 cs.charStrings = dict((g,v) 1329 for g,v in cs.charStrings.iteritems() 1330 if g in s.glyphs) 1331 font.charset = [g for g in font.charset if g in s.glyphs] 1332 font.numGlyphs = len(font.charset) 1333 1334 return True # any(cff[fontname].numGlyphs for fontname in cff.keys()) 1335 1336@_add_method(fontTools.misc.psCharStrings.T2CharString) 1337def subset_subroutines(self, subrs, gsubrs): 1338 p = self.program 1339 assert len(p) 1340 for i in xrange(1, len(p)): 1341 if p[i] == 'callsubr': 1342 assert type(p[i-1]) is int 1343 p[i-1] = subrs._used.index(p[i-1] + subrs._old_bias) - subrs._new_bias 1344 elif p[i] == 'callgsubr': 1345 assert type(p[i-1]) is int 1346 p[i-1] = gsubrs._used.index(p[i-1] + gsubrs._old_bias) - gsubrs._new_bias 1347 1348@_add_method(fontTools.misc.psCharStrings.T2CharString) 1349def drop_hints(self): 1350 hints = self._hints 1351 1352 if hints.has_hint: 1353 self.program = self.program[hints.last_hint:] 1354 if hasattr(self, 'width'): 1355 # Insert width back if needed 1356 if self.width != self.private.defaultWidthX: 1357 self.program.insert(0, self.width - self.private.nominalWidthX) 1358 1359 if hints.has_hintmask: 1360 i = 0 1361 p = self.program 1362 while i < len(p): 1363 if p[i] in ['hintmask', 'cntrmask']: 1364 assert i + 1 <= len(p) 1365 del p[i:i+2] 1366 continue 1367 i += 1 1368 1369 assert len(self.program) 1370 1371 del self._hints 1372 1373class _MarkingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler): 1374 1375 def __init__(self, localSubrs, globalSubrs): 1376 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self, 1377 localSubrs, 1378 globalSubrs) 1379 for subrs in [localSubrs, globalSubrs]: 1380 if subrs and not hasattr(subrs, "_used"): 1381 subrs._used = set() 1382 1383 def op_callsubr(self, index): 1384 self.localSubrs._used.add(self.operandStack[-1]+self.localBias) 1385 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) 1386 1387 def op_callgsubr(self, index): 1388 self.globalSubrs._used.add(self.operandStack[-1]+self.globalBias) 1389 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) 1390 1391class _DehintingT2Decompiler(fontTools.misc.psCharStrings.SimpleT2Decompiler): 1392 1393 class Hints: 1394 def __init__(self): 1395 # Whether calling this charstring produces any hint stems 1396 self.has_hint = False 1397 # Index to start at to drop all hints 1398 self.last_hint = 0 1399 # Index up to which we know more hints are possible. Only 1400 # relevant if status is 0 or 1. 1401 self.last_checked = 0 1402 # The status means: 1403 # 0: after dropping hints, this charstring is empty 1404 # 1: after dropping hints, there may be more hints continuing after this 1405 # 2: no more hints possible after this charstring 1406 self.status = 0 1407 # Has hintmask instructions; not recursive 1408 self.has_hintmask = False 1409 pass 1410 1411 def __init__(self, css, localSubrs, globalSubrs): 1412 self._css = css 1413 fontTools.misc.psCharStrings.SimpleT2Decompiler.__init__(self, 1414 localSubrs, 1415 globalSubrs) 1416 1417 def execute(self, charString): 1418 old_hints = charString._hints if hasattr(charString, '_hints') else None 1419 charString._hints = self.Hints() 1420 1421 fontTools.misc.psCharStrings.SimpleT2Decompiler.execute(self, charString) 1422 1423 hints = charString._hints 1424 1425 if hints.has_hint or hints.has_hintmask: 1426 self._css.add(charString) 1427 1428 if hints.status != 2: 1429 # Check from last_check, make sure we didn't have any operators. 1430 for i in xrange(hints.last_checked, len(charString.program) - 1): 1431 if type(charString.program[i]) == str: 1432 hints.status = 2 1433 break; 1434 else: 1435 hints.status = 1 # There's *something* here 1436 hints.last_checked = len(charString.program) 1437 1438 if old_hints: 1439 assert hints.__dict__ == old_hints.__dict__ 1440 1441 def op_callsubr(self, index): 1442 subr = self.localSubrs[self.operandStack[-1]+self.localBias] 1443 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) 1444 self.processSubr(index, subr) 1445 1446 def op_callgsubr(self, index): 1447 subr = self.globalSubrs[self.operandStack[-1]+self.globalBias] 1448 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) 1449 self.processSubr(index, subr) 1450 1451 def op_hstem(self, index): 1452 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_hstem(self, index) 1453 self.processHint(index) 1454 def op_vstem(self, index): 1455 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_vstem(self, index) 1456 self.processHint(index) 1457 def op_hstemhm(self, index): 1458 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_hstemhm(self, index) 1459 self.processHint(index) 1460 def op_vstemhm(self, index): 1461 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_vstemhm(self, index) 1462 self.processHint(index) 1463 def op_hintmask(self, index): 1464 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_hintmask(self, index) 1465 self.processHintmask(index) 1466 def op_cntrmask(self, index): 1467 fontTools.misc.psCharStrings.SimpleT2Decompiler.op_cntrmask(self, index) 1468 self.processHintmask(index) 1469 1470 def processHintmask(self, index): 1471 cs = self.callingStack[-1] 1472 hints = cs._hints 1473 hints.has_hintmask = True 1474 if hints.status != 2 and hints.has_hint: 1475 # Check from last_check, see if we may be an implicit vstem 1476 for i in xrange(hints.last_checked, index - 1): 1477 if type(cs.program[i]) == str: 1478 hints.status = 2 1479 break; 1480 if hints.status != 2: 1481 # We are an implicit vstem 1482 hints.last_hint = index + 1 1483 hints.status = 0 1484 hints.last_checked = index + 1 1485 1486 def processHint(self, index): 1487 cs = self.callingStack[-1] 1488 hints = cs._hints 1489 hints.has_hint = True 1490 hints.last_hint = index 1491 hints.last_checked = index 1492 1493 def processSubr(self, index, subr): 1494 cs = self.callingStack[-1] 1495 hints = cs._hints 1496 subr_hints = subr._hints 1497 1498 if subr_hints.has_hint: 1499 if hints.status != 2: 1500 hints.has_hint = True 1501 hints.last_checked = index 1502 hints.status = subr_hints.status 1503 # Decide where to chop off from 1504 if subr_hints.status == 0: 1505 hints.last_hint = index 1506 else: 1507 hints.last_hint = index - 2 # Leave the subr call in 1508 else: 1509 # In my understanding, this is a font bug. Ie. it has hint stems 1510 # *after* path construction. I've seen this in widespread fonts. 1511 # Best to ignore the hints I suppose... 1512 pass 1513 #assert 0 1514 else: 1515 hints.status = max(hints.status, subr_hints.status) 1516 if hints.status != 2: 1517 # Check from last_check, make sure we didn't have 1518 # any operators. 1519 for i in xrange(hints.last_checked, index - 1): 1520 if type(cs.program[i]) == str: 1521 hints.status = 2 1522 break; 1523 hints.last_checked = index 1524 1525@_add_method(fontTools.ttLib.getTableClass('CFF ')) 1526def prune_post_subset(self, options): 1527 cff = self.cff 1528 for fontname in cff.keys(): 1529 font = cff[fontname] 1530 cs = font.CharStrings 1531 1532 1533 # 1534 # Drop unused FontDictionaries 1535 # 1536 if hasattr(font, "FDSelect"): 1537 sel = font.FDSelect 1538 indices = _uniq_sort(sel.gidArray) 1539 sel.gidArray = [indices.index (ss) for ss in sel.gidArray] 1540 arr = font.FDArray 1541 arr.items = [arr[i] for i in indices] 1542 arr.count = len(arr.items) 1543 del arr.file, arr.offsets 1544 1545 1546 # 1547 # Drop hints if not needed 1548 # 1549 if not options.hinting: 1550 1551 # 1552 # This can be tricky, but doesn't have to. What we do is: 1553 # 1554 # - Run all used glyph charstrings and recurse into subroutines, 1555 # - For each charstring (including subroutines), if it has any 1556 # of the hint stem operators, we mark it as such. Upon returning, 1557 # for each charstring we note all the subroutine calls it makes 1558 # that (recursively) contain a stem, 1559 # - Dropping hinting then consists of the following two ops: 1560 # * Drop the piece of the program in each charstring before the 1561 # last call to a stem op or a stem-calling subroutine, 1562 # * Drop all hintmask operations. 1563 # - It's trickier... A hintmask right after hints and a few numbers 1564 # will act as an implicit vstemhm. As such, we track whether 1565 # we have seen any non-hint operators so far and do the right 1566 # thing, recursively... Good luck understanding that :( 1567 # 1568 css = set() 1569 for g in font.charset: 1570 c,sel = cs.getItemAndSelector(g) 1571 # Make sure it's decompiled. We want our "decompiler" to walk 1572 # the program, not the bytecode. 1573 c.draw(fontTools.pens.basePen.NullPen()) 1574 subrs = getattr(c.private, "Subrs", []) 1575 decompiler = _DehintingT2Decompiler(css, subrs, c.globalSubrs) 1576 decompiler.execute(c) 1577 for charstring in css: 1578 charstring.drop_hints() 1579 1580 1581 # 1582 # Renumber subroutines to remove unused ones 1583 # 1584 1585 # Mark all used subroutines 1586 for g in font.charset: 1587 c,sel = cs.getItemAndSelector(g) 1588 subrs = getattr(c.private, "Subrs", []) 1589 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs) 1590 decompiler.execute(c) 1591 1592 all_subrs = [font.GlobalSubrs] 1593 if hasattr(font, 'FDSelect'): 1594 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs') and fd.Private.Subrs) 1595 elif hasattr(font.Private, 'Subrs') and font.Private.Subrs: 1596 all_subrs.append(font.Private.Subrs) 1597 1598 subrs = set(subrs) # Remove duplicates 1599 1600 # Prepare 1601 for subrs in all_subrs: 1602 if not hasattr(subrs, '_used'): 1603 subrs._used = set() 1604 subrs._used = _uniq_sort(subrs._used) 1605 subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs) 1606 subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used) 1607 1608 # Renumber glyph charstrings 1609 for g in font.charset: 1610 c,sel = cs.getItemAndSelector(g) 1611 subrs = getattr(c.private, "Subrs", []) 1612 c.subset_subroutines (subrs, font.GlobalSubrs) 1613 1614 # Renumber subroutines themselves 1615 for subrs in all_subrs: 1616 1617 if subrs == font.GlobalSubrs: 1618 if not hasattr(font, 'FDSelect') and hasattr(font.Private, 'Subrs'): 1619 local_subrs = font.Private.Subrs 1620 else: 1621 local_subrs = [] 1622 else: 1623 local_subrs = subrs 1624 1625 subrs.items = [subrs.items[i] for i in subrs._used] 1626 subrs.count = len(subrs.items) 1627 del subrs.file 1628 if hasattr(subrs, 'offsets'): 1629 del subrs.offsets 1630 1631 for i in xrange (subrs.count): 1632 subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs) 1633 1634 # Cleanup 1635 for subrs in all_subrs: 1636 del subrs._used, subrs._old_bias, subrs._new_bias 1637 1638 return True 1639 1640@_add_method(fontTools.ttLib.getTableClass('cmap')) 1641def closure_glyphs(self, s): 1642 tables = [t for t in self.tables 1643 if t.platformID == 3 and t.platEncID in [1, 10]] 1644 for u in s.unicodes_requested: 1645 found = False 1646 for table in tables: 1647 if u in table.cmap: 1648 s.glyphs.add(table.cmap[u]) 1649 found = True 1650 break 1651 if not found: 1652 s.log("No glyph for Unicode value %s; skipping." % u) 1653 1654@_add_method(fontTools.ttLib.getTableClass('cmap')) 1655def prune_pre_subset(self, options): 1656 if not options.legacy_cmap: 1657 # Drop non-Unicode / non-Symbol cmaps 1658 self.tables = [t for t in self.tables 1659 if t.platformID == 3 and t.platEncID in [0, 1, 10]] 1660 if not options.symbol_cmap: 1661 self.tables = [t for t in self.tables 1662 if t.platformID == 3 and t.platEncID in [1, 10]] 1663 # TODO(behdad) Only keep one subtable? 1664 # For now, drop format=0 which can't be subset_glyphs easily? 1665 self.tables = [t for t in self.tables if t.format != 0] 1666 return bool(self.tables) 1667 1668@_add_method(fontTools.ttLib.getTableClass('cmap')) 1669def subset_glyphs(self, s): 1670 s.glyphs = s.glyphs_cmaped 1671 for t in self.tables: 1672 # For reasons I don't understand I need this here 1673 # to force decompilation of the cmap format 14. 1674 try: 1675 getattr(t, "asdf") 1676 except AttributeError: 1677 pass 1678 if t.format == 14: 1679 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None). 1680 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs]) 1681 for v,l in t.uvsDict.iteritems()) 1682 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l) 1683 else: 1684 t.cmap = dict((u,g) for u,g in t.cmap.iteritems() 1685 if g in s.glyphs_requested or u in s.unicodes_requested) 1686 self.tables = [t for t in self.tables 1687 if (t.cmap if t.format != 14 else t.uvsDict)] 1688 # TODO(behdad) Convert formats when needed. 1689 # In particular, if we have a format=12 without non-BMP 1690 # characters, either drop format=12 one or convert it 1691 # to format=4 if there's not one. 1692 return bool(self.tables) 1693 1694@_add_method(fontTools.ttLib.getTableClass('name')) 1695def prune_pre_subset(self, options): 1696 if '*' not in options.name_IDs: 1697 self.names = [n for n in self.names if n.nameID in options.name_IDs] 1698 if not options.name_legacy: 1699 self.names = [n for n in self.names 1700 if n.platformID == 3 and n.platEncID == 1] 1701 if '*' not in options.name_languages: 1702 self.names = [n for n in self.names if n.langID in options.name_languages] 1703 return True # Retain even if empty 1704 1705 1706# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange? 1707# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries. 1708# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left 1709# TODO(behdad) Drop GDEF subitems if unused by lookups 1710# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF) 1711# TODO(behdad) Text direction considerations. 1712# TODO(behdad) Text script / language considerations. 1713 1714 1715class Options(object): 1716 1717 class UnknownOptionError(Exception): 1718 pass 1719 1720 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ', 1721 'PCLT', 'LTSH'] 1722 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite 1723 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color 1724 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2', 1725 'loca', 'name', 'cvt ', 'fpgm', 'prep'] 1726 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX'] 1727 1728 # Based on HarfBuzz shapers 1729 _layout_features_groups = { 1730 # Default shaper 1731 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'], 1732 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'], 1733 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'], 1734 'ltr': ['ltra', 'ltrm'], 1735 'rtl': ['rtla', 'rtlm'], 1736 # Complex shapers 1737 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3', 1738 'cswh', 'mset'], 1739 'hangul': ['ljmo', 'vjmo', 'tjmo'], 1740 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'], 1741 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half', 1742 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres', 1743 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'], 1744 } 1745 _layout_features_default = _uniq_sort(sum( 1746 _layout_features_groups.itervalues(), [])) 1747 1748 drop_tables = _drop_tables_default 1749 no_subset_tables = _no_subset_tables_default 1750 hinting_tables = _hinting_tables_default 1751 layout_features = _layout_features_default 1752 hinting = False 1753 glyph_names = False 1754 legacy_cmap = False 1755 symbol_cmap = False 1756 name_IDs = [1, 2] # Family and Style 1757 name_legacy = False 1758 name_languages = [0x0409] # English 1759 notdef_glyph = True # gid0 for TrueType / .notdef for CFF 1760 notdef_outline = False # No need for notdef to have an outline really 1761 recommended_glyphs = False # gid1, gid2, gid3 for TrueType 1762 recalc_bounds = False # Recalculate font bounding boxes 1763 canonical_order = False # Order tables as recommended 1764 flavor = None # May be 'woff' 1765 1766 def __init__(self, **kwargs): 1767 1768 self.set(**kwargs) 1769 1770 def set(self, **kwargs): 1771 for k,v in kwargs.iteritems(): 1772 if not hasattr(self, k): 1773 raise self.UnknownOptionError("Unknown option '%s'" % k) 1774 setattr(self, k, v) 1775 1776 def parse_opts(self, argv, ignore_unknown=False): 1777 ret = [] 1778 opts = {} 1779 for a in argv: 1780 orig_a = a 1781 if not a.startswith('--'): 1782 ret.append(a) 1783 continue 1784 a = a[2:] 1785 i = a.find('=') 1786 op = '=' 1787 if i == -1: 1788 if a.startswith("no-"): 1789 k = a[3:] 1790 v = False 1791 else: 1792 k = a 1793 v = True 1794 else: 1795 k = a[:i] 1796 if k[-1] in "-+": 1797 op = k[-1]+'=' # Ops is '-=' or '+=' now. 1798 k = k[:-1] 1799 v = a[i+1:] 1800 k = k.replace('-', '_') 1801 if not hasattr(self, k): 1802 if ignore_unknown == True or k in ignore_unknown: 1803 ret.append(orig_a) 1804 continue 1805 else: 1806 raise self.UnknownOptionError("Unknown option '%s'" % a) 1807 1808 ov = getattr(self, k) 1809 if isinstance(ov, bool): 1810 v = bool(v) 1811 elif isinstance(ov, int): 1812 v = int(v) 1813 elif isinstance(ov, list): 1814 vv = v.split(',') 1815 if vv == ['']: 1816 vv = [] 1817 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv] 1818 if op == '=': 1819 v = vv 1820 elif op == '+=': 1821 v = ov 1822 v.extend(vv) 1823 elif op == '-=': 1824 v = ov 1825 for x in vv: 1826 if x in v: 1827 v.remove(x) 1828 else: 1829 assert 0 1830 1831 opts[k] = v 1832 self.set(**opts) 1833 1834 return ret 1835 1836 1837class Subsetter(object): 1838 1839 def __init__(self, options=None, log=None): 1840 1841 if not log: 1842 log = Logger() 1843 if not options: 1844 options = Options() 1845 1846 self.options = options 1847 self.log = log 1848 self.unicodes_requested = set() 1849 self.glyphs_requested = set() 1850 self.glyphs = set() 1851 1852 def populate(self, glyphs=[], unicodes=[], text=""): 1853 self.unicodes_requested.update(unicodes) 1854 if isinstance(text, str): 1855 text = text.decode("utf8") 1856 for u in text: 1857 self.unicodes_requested.add(ord(u)) 1858 self.glyphs_requested.update(glyphs) 1859 self.glyphs.update(glyphs) 1860 1861 def _prune_pre_subset(self, font): 1862 1863 for tag in font.keys(): 1864 if tag == 'GlyphOrder': continue 1865 1866 if(tag in self.options.drop_tables or 1867 (tag in self.options.hinting_tables and not self.options.hinting)): 1868 self.log(tag, "dropped") 1869 del font[tag] 1870 continue 1871 1872 clazz = fontTools.ttLib.getTableClass(tag) 1873 1874 if hasattr(clazz, 'prune_pre_subset'): 1875 table = font[tag] 1876 self.log.lapse("load '%s'" % tag) 1877 retain = table.prune_pre_subset(self.options) 1878 self.log.lapse("prune '%s'" % tag) 1879 if not retain: 1880 self.log(tag, "pruned to empty; dropped") 1881 del font[tag] 1882 continue 1883 else: 1884 self.log(tag, "pruned") 1885 1886 def _closure_glyphs(self, font): 1887 1888 self.glyphs = self.glyphs_requested.copy() 1889 1890 if 'cmap' in font: 1891 font['cmap'].closure_glyphs(self) 1892 self.glyphs_cmaped = self.glyphs 1893 1894 if self.options.notdef_glyph: 1895 if 'glyf' in font: 1896 self.glyphs.add(font.getGlyphName(0)) 1897 self.log("Added gid0 to subset") 1898 else: 1899 self.glyphs.add('.notdef') 1900 self.log("Added .notdef to subset") 1901 if self.options.recommended_glyphs: 1902 if 'glyf' in font: 1903 for i in range(4): 1904 self.glyphs.add(font.getGlyphName(i)) 1905 self.log("Added first four glyphs to subset") 1906 1907 if 'GSUB' in font: 1908 self.log("Closing glyph list over 'GSUB': %d glyphs before" % 1909 len(self.glyphs)) 1910 self.log.glyphs(self.glyphs, font=font) 1911 font['GSUB'].closure_glyphs(self) 1912 self.log("Closed glyph list over 'GSUB': %d glyphs after" % 1913 len(self.glyphs)) 1914 self.log.glyphs(self.glyphs, font=font) 1915 self.log.lapse("close glyph list over 'GSUB'") 1916 self.glyphs_gsubed = self.glyphs.copy() 1917 1918 if 'glyf' in font: 1919 self.log("Closing glyph list over 'glyf': %d glyphs before" % 1920 len(self.glyphs)) 1921 self.log.glyphs(self.glyphs, font=font) 1922 font['glyf'].closure_glyphs(self) 1923 self.log("Closed glyph list over 'glyf': %d glyphs after" % 1924 len(self.glyphs)) 1925 self.log.glyphs(self.glyphs, font=font) 1926 self.log.lapse("close glyph list over 'glyf'") 1927 self.glyphs_glyfed = self.glyphs.copy() 1928 1929 self.glyphs_all = self.glyphs.copy() 1930 1931 self.log("Retaining %d glyphs: " % len(self.glyphs_all)) 1932 1933 def _subset_glyphs(self, font): 1934 for tag in font.keys(): 1935 if tag == 'GlyphOrder': continue 1936 clazz = fontTools.ttLib.getTableClass(tag) 1937 1938 if tag in self.options.no_subset_tables: 1939 self.log(tag, "subsetting not needed") 1940 elif hasattr(clazz, 'subset_glyphs'): 1941 table = font[tag] 1942 self.glyphs = self.glyphs_all 1943 retain = table.subset_glyphs(self) 1944 self.glyphs = self.glyphs_all 1945 self.log.lapse("subset '%s'" % tag) 1946 if not retain: 1947 self.log(tag, "subsetted to empty; dropped") 1948 del font[tag] 1949 else: 1950 self.log(tag, "subsetted") 1951 else: 1952 self.log(tag, "NOT subset; don't know how to subset; dropped") 1953 del font[tag] 1954 1955 glyphOrder = font.getGlyphOrder() 1956 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all] 1957 font.setGlyphOrder(glyphOrder) 1958 font._buildReverseGlyphOrderDict() 1959 self.log.lapse("subset GlyphOrder") 1960 1961 def _prune_post_subset(self, font): 1962 for tag in font.keys(): 1963 if tag == 'GlyphOrder': continue 1964 clazz = fontTools.ttLib.getTableClass(tag) 1965 if hasattr(clazz, 'prune_post_subset'): 1966 table = font[tag] 1967 retain = table.prune_post_subset(self.options) 1968 self.log.lapse("prune '%s'" % tag) 1969 if not retain: 1970 self.log(tag, "pruned to empty; dropped") 1971 del font[tag] 1972 else: 1973 self.log(tag, "pruned") 1974 1975 def subset(self, font): 1976 1977 self._prune_pre_subset(font) 1978 self._closure_glyphs(font) 1979 self._subset_glyphs(font) 1980 self._prune_post_subset(font) 1981 1982 1983class Logger(object): 1984 1985 def __init__(self, verbose=False, xml=False, timing=False): 1986 self.verbose = verbose 1987 self.xml = xml 1988 self.timing = timing 1989 self.last_time = self.start_time = time.time() 1990 1991 def parse_opts(self, argv): 1992 argv = argv[:] 1993 for v in ['verbose', 'xml', 'timing']: 1994 if "--"+v in argv: 1995 setattr(self, v, True) 1996 argv.remove("--"+v) 1997 return argv 1998 1999 def __call__(self, *things): 2000 if not self.verbose: 2001 return 2002 print ' '.join(str(x) for x in things) 2003 2004 def lapse(self, *things): 2005 if not self.timing: 2006 return 2007 new_time = time.time() 2008 print "Took %0.3fs to %s" %(new_time - self.last_time, 2009 ' '.join(str(x) for x in things)) 2010 self.last_time = new_time 2011 2012 def glyphs(self, glyphs, font=None): 2013 self("Names: ", sorted(glyphs)) 2014 if font: 2015 reverseGlyphMap = font.getReverseGlyphMap() 2016 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs)) 2017 2018 def font(self, font, file=sys.stdout): 2019 if not self.xml: 2020 return 2021 import xmlWriter 2022 writer = xmlWriter.XMLWriter(file) 2023 font.disassembleInstructions = False # Work around ttLib bug 2024 for tag in font.keys(): 2025 writer.begintag(tag) 2026 writer.newline() 2027 font[tag].toXML(writer, font) 2028 writer.endtag(tag) 2029 writer.newline() 2030 2031 2032def load_font(fontFile, 2033 options, 2034 checkChecksums=False, 2035 dontLoadGlyphNames=False): 2036 2037 font = fontTools.ttLib.TTFont(fontFile, 2038 checkChecksums=checkChecksums, 2039 recalcBBoxes=options.recalc_bounds) 2040 2041 # Hack: 2042 # 2043 # If we don't need glyph names, change 'post' class to not try to 2044 # load them. It avoid lots of headache with broken fonts as well 2045 # as loading time. 2046 # 2047 # Ideally ttLib should provide a way to ask it to skip loading 2048 # glyph names. But it currently doesn't provide such a thing. 2049 # 2050 if dontLoadGlyphNames: 2051 post = fontTools.ttLib.getTableClass('post') 2052 saved = post.decode_format_2_0 2053 post.decode_format_2_0 = post.decode_format_3_0 2054 f = font['post'] 2055 if f.formatType == 2.0: 2056 f.formatType = 3.0 2057 post.decode_format_2_0 = saved 2058 2059 return font 2060 2061def save_font(font, outfile, options): 2062 if options.flavor and not hasattr(font, 'flavor'): 2063 raise Exception("fonttools version does not support flavors.") 2064 font.flavor = options.flavor 2065 font.save(outfile, reorderTables=options.canonical_order) 2066 2067def main(args): 2068 2069 log = Logger() 2070 args = log.parse_opts(args) 2071 2072 options = Options() 2073 args = options.parse_opts(args, ignore_unknown=['text']) 2074 2075 if len(args) < 2: 2076 print >>sys.stderr, "usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]..." 2077 sys.exit(1) 2078 2079 fontfile = args[0] 2080 args = args[1:] 2081 2082 dontLoadGlyphNames =(not options.glyph_names and 2083 all(any(g.startswith(p) 2084 for p in ['gid', 'glyph', 'uni', 'U+']) 2085 for g in args)) 2086 2087 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames) 2088 subsetter = Subsetter(options=options, log=log) 2089 log.lapse("load font") 2090 2091 names = font.getGlyphNames() 2092 log.lapse("loading glyph names") 2093 2094 glyphs = [] 2095 unicodes = [] 2096 text = "" 2097 for g in args: 2098 if g == '*': 2099 glyphs.extend(font.getGlyphOrder()) 2100 continue 2101 if g in names: 2102 glyphs.append(g) 2103 continue 2104 if g.startswith('--text='): 2105 text += g[7:] 2106 continue 2107 if g.startswith('uni') or g.startswith('U+'): 2108 if g.startswith('uni') and len(g) > 3: 2109 g = g[3:] 2110 elif g.startswith('U+') and len(g) > 2: 2111 g = g[2:] 2112 u = int(g, 16) 2113 unicodes.append(u) 2114 continue 2115 if g.startswith('gid') or g.startswith('glyph'): 2116 if g.startswith('gid') and len(g) > 3: 2117 g = g[3:] 2118 elif g.startswith('glyph') and len(g) > 5: 2119 g = g[5:] 2120 try: 2121 glyphs.append(font.getGlyphName(int(g), requireReal=1)) 2122 except ValueError: 2123 raise Exception("Invalid glyph identifier: %s" % g) 2124 continue 2125 raise Exception("Invalid glyph identifier: %s" % g) 2126 log.lapse("compile glyph list") 2127 log("Unicodes:", unicodes) 2128 log("Glyphs:", glyphs) 2129 2130 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text) 2131 subsetter.subset(font) 2132 2133 outfile = fontfile + '.subset' 2134 2135 save_font (font, outfile, options) 2136 log.lapse("compile and save font") 2137 2138 log.last_time = log.start_time 2139 log.lapse("make one with everything(TOTAL TIME)") 2140 2141 if log.verbose: 2142 import os 2143 log("Input font: %d bytes" % os.path.getsize(fontfile)) 2144 log("Subset font: %d bytes" % os.path.getsize(outfile)) 2145 2146 log.font(font) 2147 2148 font.close() 2149 2150 2151__all__ = [ 2152 'Options', 2153 'Subsetter', 2154 'Logger', 2155 'load_font', 2156 'save_font', 2157 'main' 2158] 2159 2160if __name__ == '__main__': 2161 main(sys.argv[1:]) 2162