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