Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | |
| 3 | # Python OpenType Layout Subsetter |
| 4 | # Writte by: Behdad Esfahbod |
| 5 | |
| 6 | import fontTools.ttx |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 7 | |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 8 | |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 9 | def add_method (*clazzes): |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 10 | def wrapper(method): |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 11 | for clazz in clazzes: |
| 12 | setattr (clazz, method.func_name, method) |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 13 | return wrapper |
| 14 | |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 15 | # Subset |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 16 | |
| 17 | @add_method(fontTools.ttLib.tables.otTables.Coverage) |
| 18 | def subset (self, glyphs): |
| 19 | indices = [i for (i,g) in enumerate (self.glyphs) if g in glyphs] |
| 20 | self.glyphs = [g for g in self.glyphs if g in glyphs] |
| 21 | return indices |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 22 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 23 | |
| 24 | @add_method(fontTools.ttLib.tables.otTables.ClassDef) |
| 25 | def subset (self, glyphs): |
| 26 | self.classDefs = {g:v for g,v in self.classDefs.items() if g in glyphs} |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 27 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 28 | |
| 29 | @add_method(fontTools.ttLib.tables.otTables.SingleSubst) |
| 30 | def subset (self, glyphs): |
| 31 | if self.Format in [1, 2]: |
| 32 | self.mapping = {g:v for g,v in self.mapping.items() if g in glyphs} |
| 33 | else: |
| 34 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 35 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 36 | |
| 37 | @add_method(fontTools.ttLib.tables.otTables.MultipleSubst) |
| 38 | def subset (self, glyphs): |
| 39 | if self.Format == 1: |
| 40 | indices = self.Coverage.subset (glyphs) |
| 41 | self.Sequence = [self.Sequence[i] for i in indices] |
| 42 | else: |
| 43 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 44 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 45 | |
| 46 | @add_method(fontTools.ttLib.tables.otTables.AlternateSubst) |
| 47 | def subset (self, glyphs): |
| 48 | if self.Format == 1: |
| 49 | self.alternates = {g:v for g,v in self.alternates.items() if g in glyphs} |
| 50 | else: |
| 51 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 52 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 53 | |
| 54 | @add_method(fontTools.ttLib.tables.otTables.LigatureSubst) |
| 55 | def subset (self, glyphs): |
| 56 | self.ligatures = {g:v for g,v in self.ligatures.items() if g in glyphs} |
| 57 | self.ligatures = {g:[seq for seq in seqs if all(c in glyphs for c in seq.Component)] |
| 58 | for g,seqs in self.ligatures.items()} |
| 59 | self.ligatures = {g:v for g,v in self.ligatures.items() if v} |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 60 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 61 | |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 62 | @add_method(fontTools.ttLib.tables.otTables.ReverseChainSingleSubst) |
| 63 | def subset (self, glyphs): |
| 64 | if self.Format == 1: |
| 65 | indices = self.Coverage.subset (glyphs) |
| 66 | self.Substitute = [self.Substitute[i] for i in indices] |
| 67 | self.GlyphCount = len (self.Substitute) |
| 68 | for c in self.LookAheadCoverage: |
| 69 | c.subset (glyphs) |
| 70 | for c in self.BacktrackCoverage: |
| 71 | c.subset (glyphs) |
| 72 | else: |
| 73 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 74 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 75 | |
| 76 | @add_method(fontTools.ttLib.tables.otTables.SinglePos) |
| 77 | def subset (self, glyphs): |
| 78 | if self.Format == 1: |
| 79 | self.Coverage.subset (glyphs) |
| 80 | elif self.Format == 2: |
| 81 | indices = self.Coverage.subset (glyphs) |
| 82 | self.Value = [self.Value[i] for i in indices] |
| 83 | self.ValueCount = len (self.Value) |
| 84 | else: |
| 85 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 86 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 87 | |
| 88 | @add_method(fontTools.ttLib.tables.otTables.PairPos) |
| 89 | def subset (self, glyphs): |
| 90 | if self.Format == 1: |
| 91 | indices = self.Coverage.subset (glyphs) |
| 92 | self.PairSet = [self.PairSet[i] for i in indices] |
| 93 | for p in self.PairSet: |
| 94 | p.PairValueRecord = [r for r in p.PairValueRecord if r.SecondGlyph in glyphs] |
| 95 | p.PairValueCount = len (p.PairValueRecord) |
| 96 | # TODO Prune empty rules |
| 97 | self.PairSetCount = len (self.PairSet) |
| 98 | elif self.Format == 2: |
| 99 | self.Coverage.subset (glyphs) |
| 100 | self.ClassDef1.subset (glyphs) |
| 101 | self.ClassDef2.subset (glyphs) |
| 102 | # TODO Prune empty classes |
| 103 | else: |
| 104 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 105 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 106 | |
| 107 | @add_method(fontTools.ttLib.tables.otTables.CursivePos) |
| 108 | def subset (self, glyphs): |
| 109 | if self.Format == 1: |
| 110 | indices = self.Coverage.subset (glyphs) |
| 111 | self.EntryExitRecord = [self.EntryExitRecord[i] for i in indices] |
| 112 | else: |
| 113 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 114 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 115 | |
| 116 | @add_method(fontTools.ttLib.tables.otTables.MarkBasePos) |
| 117 | def subset (self, glyphs): |
| 118 | if self.Format == 1: |
| 119 | mark_indices = self.MarkCoverage.subset (glyphs) |
| 120 | self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] for i in mark_indices] |
| 121 | self.MarkArray.MarkCount = len (self.MarkArray.MarkRecord) |
| 122 | base_indices = self.BaseCoverage.subset (glyphs) |
| 123 | self.BaseArray.BaseRecord = [self.BaseArray.BaseRecord[i] for i in base_indices] |
| 124 | self.BaseArray.BaseCount = len (self.BaseArray.BaseRecord) |
| 125 | # TODO Prune empty classes |
| 126 | else: |
| 127 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 128 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 129 | |
| 130 | @add_method(fontTools.ttLib.tables.otTables.MarkLigPos) |
| 131 | def subset (self, glyphs): |
| 132 | if self.Format == 1: |
| 133 | mark_indices = self.MarkCoverage.subset (glyphs) |
| 134 | self.MarkArray.MarkRecord = [self.MarkArray.MarkRecord[i] for i in mark_indices] |
| 135 | self.MarkArray.MarkCount = len (self.MarkArray.MarkRecord) |
| 136 | ligature_indices = self.LigatureCoverage.subset (glyphs) |
| 137 | self.LigatureArray.LigatureAttach = [self.LigatureArray.LigatureAttach[i] for i in ligature_indices] |
| 138 | self.LigatureArray.LigatureCount = len (self.LigatureArray.LigatureAttach) |
| 139 | # TODO Prune empty classes |
| 140 | else: |
| 141 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 142 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 143 | |
| 144 | @add_method(fontTools.ttLib.tables.otTables.MarkMarkPos) |
| 145 | def subset (self, glyphs): |
| 146 | if self.Format == 1: |
| 147 | mark1_indices = self.Mark1Coverage.subset (glyphs) |
| 148 | self.Mark1Array.MarkRecord = [self.Mark1Array.MarkRecord[i] for i in mark1_indices] |
| 149 | self.Mark1Array.MarkCount = len (self.Mark1Array.MarkRecord) |
| 150 | mark2_indices = self.Mark2Coverage.subset (glyphs) |
| 151 | self.Mark2Array.Mark2Record = [self.Mark2Array.Mark2Record[i] for i in mark2_indices] |
| 152 | self.Mark2Array.MarkCount = len (self.Mark2Array.Mark2Record) |
| 153 | # TODO Prune empty classes |
| 154 | else: |
| 155 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 156 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 157 | |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 158 | @add_method(fontTools.ttLib.tables.otTables.ContextSubst, fontTools.ttLib.tables.otTables.ContextPos) |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 159 | def subset (self, glyphs): |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 160 | if self.Format == 1: |
Behdad Esfahbod | b7fef90 | 2013-07-21 22:48:08 -0400 | [diff] [blame] | 161 | indices = self.Coverage.subset (glyphs) |
| 162 | self.SubRuleSet = [self.SubRuleSet[i] for i in indices] |
| 163 | self.SubRuleSetCount = len (self.SubRuleSet) |
| 164 | for rs in self.SubRuleSet: |
| 165 | rs.SubRule = [r for r in rs.SubRule |
| 166 | if all (g in glyphs for g in r.Input)] |
| 167 | rs.SubRuleCount = len (rs.SubRule) |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 168 | elif self.Format == 2: |
Behdad Esfahbod | b7fef90 | 2013-07-21 22:48:08 -0400 | [diff] [blame] | 169 | self.Coverage.subset (glyphs) |
| 170 | self.ClassDef.subset (glyphs) |
| 171 | pass |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 172 | elif self.Format == 3: |
Behdad Esfahbod | b7fef90 | 2013-07-21 22:48:08 -0400 | [diff] [blame] | 173 | for c in self.Coverage: |
| 174 | c.subset (glyphs) |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 175 | else: |
| 176 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 177 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 178 | |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 179 | @add_method(fontTools.ttLib.tables.otTables.ChainContextSubst, fontTools.ttLib.tables.otTables.ChainContextPos) |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 180 | def subset (self, glyphs): |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 181 | if self.Format == 1: |
Behdad Esfahbod | b7fef90 | 2013-07-21 22:48:08 -0400 | [diff] [blame] | 182 | indices = self.Coverage.subset (glyphs) |
| 183 | self.ChainSubRuleSet = [self.ChainSubRuleSet[i] for i in indices] |
| 184 | self.ChainSubRuleSetCount = len (self.ChainSubRuleSet) |
| 185 | for rs in self.ChainSubRuleSet: |
| 186 | rs.ChainSubRule = [r for r in rs.ChainSubRule |
| 187 | if all (g in glyphs for g in r.Backtrack + r.Input + r.LookAhead)] |
| 188 | rs.ChainSubRuleCount = len (rs.ChainSubRule) |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 189 | elif self.Format == 2: |
Behdad Esfahbod | b7fef90 | 2013-07-21 22:48:08 -0400 | [diff] [blame] | 190 | self.Coverage.subset (glyphs) |
| 191 | self.LookAheadClassDef.subset (glyphs) |
| 192 | self.BacktrackClassDef.subset (glyphs) |
| 193 | self.InputClassDef.subset (glyphs) |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 194 | elif self.Format == 3: |
| 195 | for c in self.InputCoverage: |
| 196 | c.subset (glyphs) |
| 197 | for c in self.LookAheadCoverage: |
| 198 | c.subset (glyphs) |
| 199 | for c in self.BacktrackCoverage: |
| 200 | c.subset (glyphs) |
| 201 | else: |
| 202 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 203 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 204 | |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 205 | @add_method(fontTools.ttLib.tables.otTables.ExtensionSubst, fontTools.ttLib.tables.otTables.ExtensionPos) |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 206 | def subset (self, glyphs): |
| 207 | if self.Format == 1: |
| 208 | self.ExtSubTable.subset (glyphs) |
| 209 | else: |
| 210 | assert 0, "unknown format: %s" % self.Format |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 211 | return True |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 212 | |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 213 | @add_method(fontTools.ttLib.tables.otTables.Lookup) |
| 214 | def subset (self, glyphs): |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 215 | self.SubTable = [s for s in self.SubTable if s.subset (glyphs)] |
| 216 | return any (self.SubTable) |
Behdad Esfahbod | 5466061 | 2013-07-21 18:16:55 -0400 | [diff] [blame] | 217 | |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 218 | @add_method(fontTools.ttLib.tables.otTables.GSUB, fontTools.ttLib.tables.otTables.GPOS) |
| 219 | def subset (self, glyphs): |
Behdad Esfahbod | 1be5345 | 2013-07-21 22:52:15 -0400 | [diff] [blame^] | 220 | self.LookupList.Lookup = [l for l in self.LookupList.Lookup if l.subset (glyphs)] |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 221 | |
Behdad Esfahbod | efb984a | 2013-07-21 22:26:16 -0400 | [diff] [blame] | 222 | @add_method(fontTools.ttLib.tables.otTables.GDEF) |
| 223 | def subset (self, glyphs): |
| 224 | if self.LigCaretList: |
| 225 | indices = self.LigCaretList.Coverage.subset (glyphs) |
| 226 | self.LigCaretList.LigGlyph = [self.LigCaretList.LigGlyph[i] for i in indices] |
| 227 | self.LigCaretList.LigGlyphCount = len (self.LigCaretList.LigGlyph) |
| 228 | if self.MarkAttachClassDef: |
| 229 | self.MarkAttachClassDef.classDefs = {g:v for g,v in self.MarkAttachClassDef.classDefs.items() if g in glyphs} |
| 230 | if self.GlyphClassDef: |
| 231 | self.GlyphClassDef.classDefs = {g:v for g,v in self.GlyphClassDef.classDefs.items() if g in glyphs} |
| 232 | if self.AttachList: |
| 233 | indices = self.AttachList.Coverage.subset (glyphs) |
| 234 | self.AttachList.AttachPoint = [self.AttachList.AttachPoint[i] for i in indices] |
| 235 | self.AttachList.GlyphCount = len (self.AttachList.AttachPoint) |
| 236 | |
| 237 | |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 238 | if __name__ == '__main__': |
| 239 | |
| 240 | import sys |
| 241 | |
| 242 | if len (sys.argv) < 3: |
| 243 | print >>sys.stderr, "usage: pyotlss.py font-file glyph..." |
| 244 | sys.exit (1) |
| 245 | |
| 246 | fontfile = sys.argv[1] |
| 247 | glyphs = sys.argv[2:] |
| 248 | |
| 249 | font = fontTools.ttx.TTFont (fontfile) |
| 250 | |
| 251 | names = font.getGlyphNames() |
| 252 | # Convert to glyph names |
| 253 | glyphs = [g if g in names else font.getGlyphName(int(g)) for g in glyphs] |
| 254 | |
Behdad Esfahbod | efb984a | 2013-07-21 22:26:16 -0400 | [diff] [blame] | 255 | for Gtag in ['GDEF', 'GSUB', 'GPOS']: |
Behdad Esfahbod | 02b9206 | 2013-07-21 18:40:59 -0400 | [diff] [blame] | 256 | if Gtag not in font: |
| 257 | continue |
| 258 | font[Gtag].table.subset (glyphs) |