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