subset.py revision fbb9fc1b4787e561711797b9f25e86073250bb82
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 for fontname in cff.keys(): 1369 font = cff[fontname] 1370 cs = font.CharStrings 1371 1372 # Drop unused FontDictionaries 1373 if hasattr(font, "FDSelect"): 1374 sel = font.FDSelect 1375 indices = _uniq_sort(sel.gidArray) 1376 sel.gidArray = [indices.index (ss) for ss in sel.gidArray] 1377 arr = font.FDArray 1378 arr.items = [arr[i] for i in indices] 1379 arr.count = len(arr.items) 1380 del arr.file, arr.offsets 1381 1382 # Mark all used subroutines 1383 for g in font.charset: 1384 c,sel = cs.getItemAndSelector(g) 1385 subrs = getattr(c.private, "Subrs", []) 1386 decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs) 1387 decompiler.execute(c) 1388 1389 # Renumber subroutines to remove unused ones 1390 all_subrs = [font.GlobalSubrs] 1391 if hasattr(font, 'FDSelect'): 1392 all_subrs.extend(fd.Private.Subrs for fd in font.FDArray if hasattr(fd.Private, 'Subrs')) 1393 elif hasattr(font.Private, 'Subrs'): 1394 all_subrs.append(font.Private.Subrs) 1395 # Prepare 1396 for subrs in all_subrs: 1397 if not subrs: continue 1398 if not hasattr(subrs, '_used'): 1399 subrs._used = set() 1400 subrs._used = _uniq_sort(subrs._used) 1401 subrs._old_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs) 1402 subrs._new_bias = fontTools.misc.psCharStrings.calcSubrBias(subrs._used) 1403 # Renumber glyph charstrings 1404 for g in font.charset: 1405 c,sel = cs.getItemAndSelector(g) 1406 subrs = getattr(c.private, "Subrs", []) 1407 c.subset_subroutines (subrs, font.GlobalSubrs) 1408 # Renumber subroutines themselves 1409 for subrs in all_subrs: 1410 if not subrs: continue 1411 for i in xrange (subrs.count): 1412 if i not in subrs._used: continue 1413 if subrs == font.GlobalSubrs: 1414 if not hasattr(font, 'FDSelect') and hasattr(font.Private, 'Subrs'): 1415 local_subrs = font.Private.Subrs 1416 else: 1417 local_subrs = [] 1418 else: 1419 local_subrs = subrs 1420 subrs[i].subset_subroutines (local_subrs, font.GlobalSubrs) 1421 # Cleanup 1422 for subrs in all_subrs: 1423 if not subrs: continue 1424 subrs.items = [subrs.items[i] for i in subrs._used] 1425 del subrs.file, subrs.offsets 1426 del subrs._used, subrs._old_bias, subrs._new_bias 1427 1428 if not options.hinting: 1429 pass # TODO(behdad) Drop hints 1430 1431 return True 1432 1433@_add_method(fontTools.ttLib.getTableClass('cmap')) 1434def closure_glyphs(self, s): 1435 tables = [t for t in self.tables 1436 if t.platformID == 3 and t.platEncID in [1, 10]] 1437 for u in s.unicodes_requested: 1438 found = False 1439 for table in tables: 1440 if u in table.cmap: 1441 s.glyphs.add(table.cmap[u]) 1442 found = True 1443 break 1444 if not found: 1445 s.log("No glyph for Unicode value %s; skipping." % u) 1446 1447@_add_method(fontTools.ttLib.getTableClass('cmap')) 1448def prune_pre_subset(self, options): 1449 if not options.legacy_cmap: 1450 # Drop non-Unicode / non-Symbol cmaps 1451 self.tables = [t for t in self.tables 1452 if t.platformID == 3 and t.platEncID in [0, 1, 10]] 1453 if not options.symbol_cmap: 1454 self.tables = [t for t in self.tables 1455 if t.platformID == 3 and t.platEncID in [1, 10]] 1456 # TODO(behdad) Only keep one subtable? 1457 # For now, drop format=0 which can't be subset_glyphs easily? 1458 self.tables = [t for t in self.tables if t.format != 0] 1459 return bool(self.tables) 1460 1461@_add_method(fontTools.ttLib.getTableClass('cmap')) 1462def subset_glyphs(self, s): 1463 s.glyphs = s.glyphs_cmaped 1464 for t in self.tables: 1465 # For reasons I don't understand I need this here 1466 # to force decompilation of the cmap format 14. 1467 try: 1468 getattr(t, "asdf") 1469 except AttributeError: 1470 pass 1471 if t.format == 14: 1472 # TODO(behdad) XXX We drop all the default-UVS mappings(g==None). 1473 t.uvsDict = dict((v,[(u,g) for u,g in l if g in s.glyphs]) 1474 for v,l in t.uvsDict.iteritems()) 1475 t.uvsDict = dict((v,l) for v,l in t.uvsDict.iteritems() if l) 1476 else: 1477 t.cmap = dict((u,g) for u,g in t.cmap.iteritems() 1478 if g in s.glyphs_requested or u in s.unicodes_requested) 1479 self.tables = [t for t in self.tables 1480 if (t.cmap if t.format != 14 else t.uvsDict)] 1481 # TODO(behdad) Convert formats when needed. 1482 # In particular, if we have a format=12 without non-BMP 1483 # characters, either drop format=12 one or convert it 1484 # to format=4 if there's not one. 1485 return bool(self.tables) 1486 1487@_add_method(fontTools.ttLib.getTableClass('name')) 1488def prune_pre_subset(self, options): 1489 if '*' not in options.name_IDs: 1490 self.names = [n for n in self.names if n.nameID in options.name_IDs] 1491 if not options.name_legacy: 1492 self.names = [n for n in self.names 1493 if n.platformID == 3 and n.platEncID == 1] 1494 if '*' not in options.name_languages: 1495 self.names = [n for n in self.names if n.langID in options.name_languages] 1496 return True # Retain even if empty 1497 1498 1499# TODO(behdad) OS/2 ulUnicodeRange / ulCodePageRange? 1500# TODO(behdad) Drop unneeded GSUB/GPOS Script/LangSys entries. 1501# TODO(behdad) Drop empty GSUB/GPOS, and GDEF if no GSUB/GPOS left 1502# TODO(behdad) Drop GDEF subitems if unused by lookups 1503# TODO(behdad) Avoid recursing too much (in GSUB/GPOS and in CFF) 1504# TODO(behdad) Text direction considerations. 1505# TODO(behdad) Text script / language considerations. 1506 1507 1508class Options(object): 1509 1510 class UnknownOptionError(Exception): 1511 pass 1512 1513 _drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ', 1514 'PCLT', 'LTSH'] 1515 _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite 1516 _drop_tables_default += ['CBLC', 'CBDT', 'sbix', 'COLR', 'CPAL'] # Color 1517 _no_subset_tables_default = ['gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2', 1518 'loca', 'name', 'cvt ', 'fpgm', 'prep'] 1519 _hinting_tables_default = ['cvt ', 'fpgm', 'prep', 'hdmx', 'VDMX'] 1520 1521 # Based on HarfBuzz shapers 1522 _layout_features_groups = { 1523 # Default shaper 1524 'common': ['ccmp', 'liga', 'locl', 'mark', 'mkmk', 'rlig'], 1525 'horizontal': ['calt', 'clig', 'curs', 'kern', 'rclt'], 1526 'vertical': ['valt', 'vert', 'vkrn', 'vpal', 'vrt2'], 1527 'ltr': ['ltra', 'ltrm'], 1528 'rtl': ['rtla', 'rtlm'], 1529 # Complex shapers 1530 'arabic': ['init', 'medi', 'fina', 'isol', 'med2', 'fin2', 'fin3', 1531 'cswh', 'mset'], 1532 'hangul': ['ljmo', 'vjmo', 'tjmo'], 1533 'tibetal': ['abvs', 'blws', 'abvm', 'blwm'], 1534 'indic': ['nukt', 'akhn', 'rphf', 'rkrf', 'pref', 'blwf', 'half', 1535 'abvf', 'pstf', 'cfar', 'vatu', 'cjct', 'init', 'pres', 1536 'abvs', 'blws', 'psts', 'haln', 'dist', 'abvm', 'blwm'], 1537 } 1538 _layout_features_default = _uniq_sort(sum( 1539 _layout_features_groups.itervalues(), [])) 1540 1541 drop_tables = _drop_tables_default 1542 no_subset_tables = _no_subset_tables_default 1543 hinting_tables = _hinting_tables_default 1544 layout_features = _layout_features_default 1545 hinting = False 1546 glyph_names = False 1547 legacy_cmap = False 1548 symbol_cmap = False 1549 name_IDs = [1, 2] # Family and Style 1550 name_legacy = False 1551 name_languages = [0x0409] # English 1552 notdef_glyph = True # gid0 for TrueType / .notdef for CFF 1553 notdef_outline = False # No need for notdef to have an outline really 1554 recommended_glyphs = False # gid1, gid2, gid3 for TrueType 1555 recalc_bounds = False # Recalculate font bounding boxes 1556 canonical_order = False # Order tables as recommended 1557 flavor = None # May be 'woff' 1558 1559 def __init__(self, **kwargs): 1560 1561 self.set(**kwargs) 1562 1563 def set(self, **kwargs): 1564 for k,v in kwargs.iteritems(): 1565 if not hasattr(self, k): 1566 raise self.UnknownOptionError("Unknown option '%s'" % k) 1567 setattr(self, k, v) 1568 1569 def parse_opts(self, argv, ignore_unknown=False): 1570 ret = [] 1571 opts = {} 1572 for a in argv: 1573 orig_a = a 1574 if not a.startswith('--'): 1575 ret.append(a) 1576 continue 1577 a = a[2:] 1578 i = a.find('=') 1579 op = '=' 1580 if i == -1: 1581 if a.startswith("no-"): 1582 k = a[3:] 1583 v = False 1584 else: 1585 k = a 1586 v = True 1587 else: 1588 k = a[:i] 1589 if k[-1] in "-+": 1590 op = k[-1]+'=' # Ops is '-=' or '+=' now. 1591 k = k[:-1] 1592 v = a[i+1:] 1593 k = k.replace('-', '_') 1594 if not hasattr(self, k): 1595 if ignore_unknown == True or k in ignore_unknown: 1596 ret.append(orig_a) 1597 continue 1598 else: 1599 raise self.UnknownOptionError("Unknown option '%s'" % a) 1600 1601 ov = getattr(self, k) 1602 if isinstance(ov, bool): 1603 v = bool(v) 1604 elif isinstance(ov, int): 1605 v = int(v) 1606 elif isinstance(ov, list): 1607 vv = v.split(',') 1608 if vv == ['']: 1609 vv = [] 1610 vv = [int(x, 0) if len(x) and x[0] in "0123456789" else x for x in vv] 1611 if op == '=': 1612 v = vv 1613 elif op == '+=': 1614 v = ov 1615 v.extend(vv) 1616 elif op == '-=': 1617 v = ov 1618 for x in vv: 1619 if x in v: 1620 v.remove(x) 1621 else: 1622 assert 0 1623 1624 opts[k] = v 1625 self.set(**opts) 1626 1627 return ret 1628 1629 1630class Subsetter(object): 1631 1632 def __init__(self, options=None, log=None): 1633 1634 if not log: 1635 log = Logger() 1636 if not options: 1637 options = Options() 1638 1639 self.options = options 1640 self.log = log 1641 self.unicodes_requested = set() 1642 self.glyphs_requested = set() 1643 self.glyphs = set() 1644 1645 def populate(self, glyphs=[], unicodes=[], text=""): 1646 self.unicodes_requested.update(unicodes) 1647 if isinstance(text, str): 1648 text = text.decode("utf8") 1649 for u in text: 1650 self.unicodes_requested.add(ord(u)) 1651 self.glyphs_requested.update(glyphs) 1652 self.glyphs.update(glyphs) 1653 1654 def _prune_pre_subset(self, font): 1655 1656 for tag in font.keys(): 1657 if tag == 'GlyphOrder': continue 1658 1659 if(tag in self.options.drop_tables or 1660 (tag in self.options.hinting_tables and not self.options.hinting)): 1661 self.log(tag, "dropped") 1662 del font[tag] 1663 continue 1664 1665 clazz = fontTools.ttLib.getTableClass(tag) 1666 1667 if hasattr(clazz, 'prune_pre_subset'): 1668 table = font[tag] 1669 retain = table.prune_pre_subset(self.options) 1670 self.log.lapse("prune '%s'" % tag) 1671 if not retain: 1672 self.log(tag, "pruned to empty; dropped") 1673 del font[tag] 1674 continue 1675 else: 1676 self.log(tag, "pruned") 1677 1678 def _closure_glyphs(self, font): 1679 1680 self.glyphs = self.glyphs_requested.copy() 1681 1682 if 'cmap' in font: 1683 font['cmap'].closure_glyphs(self) 1684 self.glyphs_cmaped = self.glyphs 1685 1686 if self.options.notdef_glyph: 1687 if 'glyf' in font: 1688 self.glyphs.add(font.getGlyphName(0)) 1689 self.log("Added gid0 to subset") 1690 else: 1691 self.glyphs.add('.notdef') 1692 self.log("Added .notdef to subset") 1693 if self.options.recommended_glyphs: 1694 if 'glyf' in font: 1695 for i in range(4): 1696 self.glyphs.add(font.getGlyphName(i)) 1697 self.log("Added first four glyphs to subset") 1698 1699 if 'GSUB' in font: 1700 self.log("Closing glyph list over 'GSUB': %d glyphs before" % 1701 len(self.glyphs)) 1702 self.log.glyphs(self.glyphs, font=font) 1703 font['GSUB'].closure_glyphs(self) 1704 self.log("Closed glyph list over 'GSUB': %d glyphs after" % 1705 len(self.glyphs)) 1706 self.log.glyphs(self.glyphs, font=font) 1707 self.log.lapse("close glyph list over 'GSUB'") 1708 self.glyphs_gsubed = self.glyphs.copy() 1709 1710 if 'glyf' in font: 1711 self.log("Closing glyph list over 'glyf': %d glyphs before" % 1712 len(self.glyphs)) 1713 self.log.glyphs(self.glyphs, font=font) 1714 font['glyf'].closure_glyphs(self) 1715 self.log("Closed glyph list over 'glyf': %d glyphs after" % 1716 len(self.glyphs)) 1717 self.log.glyphs(self.glyphs, font=font) 1718 self.log.lapse("close glyph list over 'glyf'") 1719 self.glyphs_glyfed = self.glyphs.copy() 1720 1721 self.glyphs_all = self.glyphs.copy() 1722 1723 self.log("Retaining %d glyphs: " % len(self.glyphs_all)) 1724 1725 def _subset_glyphs(self, font): 1726 for tag in font.keys(): 1727 if tag == 'GlyphOrder': continue 1728 clazz = fontTools.ttLib.getTableClass(tag) 1729 1730 if tag in self.options.no_subset_tables: 1731 self.log(tag, "subsetting not needed") 1732 elif hasattr(clazz, 'subset_glyphs'): 1733 table = font[tag] 1734 self.glyphs = self.glyphs_all 1735 retain = table.subset_glyphs(self) 1736 self.glyphs = self.glyphs_all 1737 self.log.lapse("subset '%s'" % tag) 1738 if not retain: 1739 self.log(tag, "subsetted to empty; dropped") 1740 del font[tag] 1741 else: 1742 self.log(tag, "subsetted") 1743 else: 1744 self.log(tag, "NOT subset; don't know how to subset; dropped") 1745 del font[tag] 1746 1747 glyphOrder = font.getGlyphOrder() 1748 glyphOrder = [g for g in glyphOrder if g in self.glyphs_all] 1749 font.setGlyphOrder(glyphOrder) 1750 font._buildReverseGlyphOrderDict() 1751 self.log.lapse("subset GlyphOrder") 1752 1753 def _prune_post_subset(self, font): 1754 for tag in font.keys(): 1755 if tag == 'GlyphOrder': continue 1756 clazz = fontTools.ttLib.getTableClass(tag) 1757 if hasattr(clazz, 'prune_post_subset'): 1758 table = font[tag] 1759 retain = table.prune_post_subset(self.options) 1760 self.log.lapse("prune '%s'" % tag) 1761 if not retain: 1762 self.log(tag, "pruned to empty; dropped") 1763 del font[tag] 1764 else: 1765 self.log(tag, "pruned") 1766 1767 def subset(self, font): 1768 1769 self._prune_pre_subset(font) 1770 self._closure_glyphs(font) 1771 self._subset_glyphs(font) 1772 self._prune_post_subset(font) 1773 1774 1775class Logger(object): 1776 1777 def __init__(self, verbose=False, xml=False, timing=False): 1778 self.verbose = verbose 1779 self.xml = xml 1780 self.timing = timing 1781 self.last_time = self.start_time = time.time() 1782 1783 def parse_opts(self, argv): 1784 argv = argv[:] 1785 for v in ['verbose', 'xml', 'timing']: 1786 if "--"+v in argv: 1787 setattr(self, v, True) 1788 argv.remove("--"+v) 1789 return argv 1790 1791 def __call__(self, *things): 1792 if not self.verbose: 1793 return 1794 print ' '.join(str(x) for x in things) 1795 1796 def lapse(self, *things): 1797 if not self.timing: 1798 return 1799 new_time = time.time() 1800 print "Took %0.3fs to %s" %(new_time - self.last_time, 1801 ' '.join(str(x) for x in things)) 1802 self.last_time = new_time 1803 1804 def glyphs(self, glyphs, font=None): 1805 self("Names: ", sorted(glyphs)) 1806 if font: 1807 reverseGlyphMap = font.getReverseGlyphMap() 1808 self("Gids : ", sorted(reverseGlyphMap[g] for g in glyphs)) 1809 1810 def font(self, font, file=sys.stdout): 1811 if not self.xml: 1812 return 1813 import xmlWriter 1814 writer = xmlWriter.XMLWriter(file) 1815 font.disassembleInstructions = False # Work around ttLib bug 1816 for tag in font.keys(): 1817 writer.begintag(tag) 1818 writer.newline() 1819 font[tag].toXML(writer, font) 1820 writer.endtag(tag) 1821 writer.newline() 1822 1823 1824def load_font(fontFile, 1825 options, 1826 checkChecksums=False, 1827 dontLoadGlyphNames=False): 1828 1829 font = fontTools.ttLib.TTFont(fontFile, 1830 checkChecksums=checkChecksums, 1831 recalcBBoxes=options.recalc_bounds) 1832 1833 # Hack: 1834 # 1835 # If we don't need glyph names, change 'post' class to not try to 1836 # load them. It avoid lots of headache with broken fonts as well 1837 # as loading time. 1838 # 1839 # Ideally ttLib should provide a way to ask it to skip loading 1840 # glyph names. But it currently doesn't provide such a thing. 1841 # 1842 if dontLoadGlyphNames: 1843 post = fontTools.ttLib.getTableClass('post') 1844 saved = post.decode_format_2_0 1845 post.decode_format_2_0 = post.decode_format_3_0 1846 f = font['post'] 1847 if f.formatType == 2.0: 1848 f.formatType = 3.0 1849 post.decode_format_2_0 = saved 1850 1851 return font 1852 1853def save_font(font, outfile, options): 1854 if options.flavor and not hasattr(font, 'flavor'): 1855 raise Exception("fonttools version does not support flavors.") 1856 font.flavor = options.flavor 1857 font.save(outfile, reorderTables=options.canonical_order) 1858 1859def main(args): 1860 1861 log = Logger() 1862 args = log.parse_opts(args) 1863 1864 options = Options() 1865 args = options.parse_opts(args, ignore_unknown=['text']) 1866 1867 if len(args) < 2: 1868 print >>sys.stderr, "usage: pyftsubset font-file glyph... [--text=ABC]... [--option=value]..." 1869 sys.exit(1) 1870 1871 fontfile = args[0] 1872 args = args[1:] 1873 1874 dontLoadGlyphNames =(not options.glyph_names and 1875 all(any(g.startswith(p) 1876 for p in ['gid', 'glyph', 'uni', 'U+']) 1877 for g in args)) 1878 1879 font = load_font(fontfile, options, dontLoadGlyphNames=dontLoadGlyphNames) 1880 subsetter = Subsetter(options=options, log=log) 1881 log.lapse("load font") 1882 1883 names = font.getGlyphNames() 1884 log.lapse("loading glyph names") 1885 1886 glyphs = [] 1887 unicodes = [] 1888 text = "" 1889 for g in args: 1890 if g in names: 1891 glyphs.append(g) 1892 continue 1893 if g.startswith('--text='): 1894 text += g[7:] 1895 continue 1896 if g.startswith('uni') or g.startswith('U+'): 1897 if g.startswith('uni') and len(g) > 3: 1898 g = g[3:] 1899 elif g.startswith('U+') and len(g) > 2: 1900 g = g[2:] 1901 u = int(g, 16) 1902 unicodes.append(u) 1903 continue 1904 if g.startswith('gid') or g.startswith('glyph'): 1905 if g.startswith('gid') and len(g) > 3: 1906 g = g[3:] 1907 elif g.startswith('glyph') and len(g) > 5: 1908 g = g[5:] 1909 try: 1910 glyphs.append(font.getGlyphName(int(g), requireReal=1)) 1911 except ValueError: 1912 raise Exception("Invalid glyph identifier: %s" % g) 1913 continue 1914 raise Exception("Invalid glyph identifier: %s" % g) 1915 log.lapse("compile glyph list") 1916 log("Unicodes:", unicodes) 1917 log("Glyphs:", glyphs) 1918 1919 subsetter.populate(glyphs=glyphs, unicodes=unicodes, text=text) 1920 subsetter.subset(font) 1921 1922 outfile = fontfile + '.subset' 1923 1924 save_font (font, outfile, options) 1925 log.lapse("compile and save font") 1926 1927 log.last_time = log.start_time 1928 log.lapse("make one with everything(TOTAL TIME)") 1929 1930 if log.verbose: 1931 import os 1932 log("Input font: %d bytes" % os.path.getsize(fontfile)) 1933 log("Subset font: %d bytes" % os.path.getsize(outfile)) 1934 1935 log.font(font) 1936 1937 font.close() 1938 1939 1940__all__ = [ 1941 'Options', 1942 'Subsetter', 1943 'Logger', 1944 'load_font', 1945 'save_font', 1946 'main' 1947] 1948 1949if __name__ == '__main__': 1950 main(sys.argv[1:]) 1951