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