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