~ubuntu-branches/ubuntu/maverick/fonttools/maverick

« back to all changes in this revision

Viewing changes to Lib/fontTools/ttLib/tables/otTables.py

  • Committer: Bazaar Package Importer
  • Author(s): Paul Wise
  • Date: 2008-07-24 00:03:02 UTC
  • mfrom: (2.1.8 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080724000302-0tediu4ysxkn6dyy
Tags: 2.2-2
* Fix dependencies to use Numpy instead of Numeric (Closes: #492010)
* Drop unneeded quilt build-dependency
* Support noopt in DEB_BUILD_OPTIONS
* Complies with policy 3.8.0, update Standards-Version

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
Most are constructed upon import from data in otData.py, all are populated with
5
5
converter objects from otConverters.py.
6
6
"""
7
 
 
 
7
import operator
8
8
from otBase import BaseTable, FormatSwitchingBaseTable
9
9
from types import TupleType
10
10
 
14
14
 
15
15
 
16
16
class FeatureParams(BaseTable):
17
 
        """Dummy class; this table isn't defined, but is used, and is always NULL."""
 
17
        """This class has been used by Adobe, but but this one implementation was done wrong.
 
18
        No other use has been made, becuase there is no way to know how to interpret
 
19
        the data at the offset.. For now, if we see one, just skip the data on
 
20
        decompiling and dumping to XML. """
18
21
        # XXX The above is no longer true; the 'size' feature uses FeatureParams now.
19
 
 
 
22
        def __init__(self):
 
23
                BaseTable.__init__(self)
 
24
                self.converters = []
20
25
 
21
26
class Coverage(FormatSwitchingBaseTable):
22
27
        
28
33
                elif self.Format == 2:
29
34
                        glyphs = self.glyphs = []
30
35
                        ranges = rawTable["RangeRecord"]
 
36
                        getGlyphName = font.getGlyphName
31
37
                        for r in ranges:
32
38
                                assert r.StartCoverageIndex == len(glyphs), \
33
39
                                        (r.StartCoverageIndex, len(glyphs))
36
42
                                startID = font.getGlyphID(start)
37
43
                                endID = font.getGlyphID(end)
38
44
                                glyphs.append(start)
39
 
                                for glyphID in range(startID + 1, endID):
40
 
                                        glyphs.append(font.getGlyphName(glyphID))
 
45
                                rangeList = [getGlyphName(glyphID) for glyphID in range(startID + 1, endID) ]
 
46
                                glyphs += rangeList
41
47
                                if start != end:
42
48
                                        glyphs.append(end)
43
49
                else:
49
55
                        glyphs = self.glyphs = []
50
56
                format = 1
51
57
                rawTable = {"GlyphArray": glyphs}
 
58
                getGlyphID = font.getGlyphID
52
59
                if glyphs:
53
60
                        # find out whether Format 2 is more compact or not
54
 
                        glyphIDs = []
55
 
                        for glyphName in glyphs:
56
 
                                glyphIDs.append(font.getGlyphID(glyphName))
 
61
                        glyphIDs = [getGlyphID(glyphName) for glyphName in glyphs ]
57
62
                        
58
63
                        last = glyphIDs[0]
59
64
                        ranges = [[last]]
95
100
                glyphs.append(attrs["value"])
96
101
 
97
102
 
 
103
class LookupList(BaseTable):
 
104
        def preCompile(self):
 
105
                """ This function is used to optimize writing out extension subtables. This is useful
 
106
                when a font has been read in, modified, and we are now writing out a new version. If the
 
107
                the extension subtables have not been touched (proof being that they have not been decompiled)
 
108
                then we can write them out using the original data, and do not have to recompile them. This can save
 
109
                20-30% of the compile time for fonts with large extension tables, such as Japanese Pro fonts."""
 
110
 
 
111
                if hasattr(self, 'LookupCount'): #not defined if loading from xml
 
112
                        lookupCount = self.LookupCount
 
113
                else:
 
114
                        return # The optimization of not recompiling extension lookup subtables is not possible
 
115
                                        # when reading from XML.
 
116
 
 
117
                liRange = range(lookupCount)
 
118
                extTables = []
 
119
                for li in liRange:
 
120
                        lookup = self.Lookup[li]
 
121
                        if hasattr(lookup, 'SubTableCount'): #not defined if loading from xml
 
122
                                subtableCount = lookup.SubTableCount
 
123
                        else:
 
124
                                subtableCount = len(lookup.SubTable)
 
125
                        siRange = range(subtableCount)
 
126
                        for si in siRange:
 
127
                                subtable = lookup.SubTable[si]
 
128
                                if hasattr(subtable, 'ExtSubTable'):
 
129
                                        extTable = subtable.ExtSubTable
 
130
                                        extTables.append([extTable.start, extTable] )
 
131
 
 
132
                # Since offsets in one subtable can and do point forward into later
 
133
                # subtables, we can afford to simply copy data only for the last subtables 
 
134
                # which were not decompiled. So we start figuring out the
 
135
                # data segments starting with the last subtTable, and work our way towards
 
136
                # the first subtable, and then quit as soon as we see a subtable that was decompiled.
 
137
                if  extTables:
 
138
                        extTables.sort()
 
139
                        extTables.reverse()
 
140
                        lastTable = extTables[0][1]
 
141
                        if lastTable.compileStatus == 1:
 
142
                                lastTable.end = len(lastTable.reader.data)
 
143
                                lastTable.compileStatus = 3
 
144
                                for i in range(1, len(extTables)):
 
145
                                        extTable = extTables[i][1]
 
146
                                        if extTable.compileStatus != 1:
 
147
                                                break
 
148
                                        extTable.end = lastTable.start
 
149
                                        extTable.compileStatus = 3
 
150
                                        lastTable = extTable
 
151
 
 
152
def doModulo(value):
 
153
        if value < 0:
 
154
                return value + 65536
 
155
        return value
 
156
 
98
157
class SingleSubst(FormatSwitchingBaseTable):
99
158
 
100
159
        def postRead(self, rawTable, font):
101
160
                mapping = {}
102
161
                input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
 
162
                lenMapping = len(input)
103
163
                if self.Format == 1:
104
164
                        delta = rawTable["DeltaGlyphID"]
105
 
                        for inGlyph in input:
106
 
                                glyphID = font.getGlyphID(inGlyph)
107
 
                                mapping[inGlyph] = font.getGlyphName(glyphID + delta)
 
165
                        inputGIDS =  [ font.getGlyphID(name) for name in input ]
 
166
                        inputGIDS = map(doModulo, inputGIDS) 
 
167
                        outGIDS = [ glyphID + delta for glyphID in inputGIDS ]
 
168
                        outGIDS = map(doModulo, outGIDS) 
 
169
                        outNames = [ font.getGlyphName(glyphID) for glyphID in outGIDS ]
 
170
                        map(operator.setitem, [mapping]*lenMapping, input, outNames)
108
171
                elif self.Format == 2:
109
172
                        assert len(input) == rawTable["GlyphCount"], \
110
173
                                        "invalid SingleSubstFormat2 table"
111
174
                        subst = rawTable["Substitute"]
112
 
                        for i in range(len(input)):
113
 
                                mapping[input[i]] = subst[i]
 
175
                        map(operator.setitem, [mapping]*lenMapping, input, subst)
114
176
                else:
115
177
                        assert 0, "unknown format: %s" % self.Format
116
178
                self.mapping = mapping
120
182
                if mapping is None:
121
183
                        mapping = self.mapping = {}
122
184
                items = mapping.items()
123
 
                for i in range(len(items)):
124
 
                        inGlyph, outGlyph = items[i]
125
 
                        items[i] = font.getGlyphID(inGlyph), font.getGlyphID(outGlyph), \
126
 
                                        inGlyph, outGlyph
127
 
                items.sort()
128
 
                
 
185
                getGlyphID = font.getGlyphID
 
186
                gidItems = [(getGlyphID(item[0]), getGlyphID(item[1])) for item in items]
 
187
                sortableItems = zip(gidItems, items)
 
188
                sortableItems.sort()
 
189
 
 
190
                # figure out format
129
191
                format = 2
130
192
                delta = None
131
 
                for inID, outID, inGlyph, outGlyph in items:
 
193
                for inID, outID in gidItems:
132
194
                        if delta is None:
133
195
                                delta = outID - inID
134
196
                        else:
136
198
                                        break
137
199
                else:
138
200
                        format = 1
139
 
                
 
201
 
140
202
                rawTable = {}
141
203
                self.Format = format
142
204
                cov = Coverage()
143
 
                cov.glyphs = input = []
144
 
                subst = []
145
 
                for inID, outID, inGlyph, outGlyph in items:
146
 
                        input.append(inGlyph)
147
 
                        subst.append(outGlyph)
 
205
                input =  [ item [1][0] for item in sortableItems]
 
206
                subst =  [ item [1][1] for item in sortableItems]
 
207
                cov.glyphs = input
148
208
                rawTable["Coverage"] = cov
149
209
                if format == 1:
150
210
                        assert delta is not None
173
233
        
174
234
        def postRead(self, rawTable, font):
175
235
                classDefs = {}
 
236
                getGlyphName = font.getGlyphName
 
237
 
176
238
                if self.Format == 1:
177
239
                        start = rawTable["StartGlyph"]
 
240
                        classList = rawTable["ClassValueArray"]
 
241
                        lenList = len(classList)
178
242
                        glyphID = font.getGlyphID(start)
179
 
                        for cls in rawTable["ClassValueArray"]:
180
 
                                classDefs[font.getGlyphName(glyphID)] = cls
181
 
                                glyphID = glyphID + 1
 
243
                        gidList = range(glyphID, glyphID + len(classList))
 
244
                        keyList = [getGlyphName(glyphID) for glyphID in gidList]
 
245
 
 
246
                        map(operator.setitem, [classDefs]*lenList, keyList, classList)
 
247
 
182
248
                elif self.Format == 2:
183
249
                        records = rawTable["ClassRangeRecord"]
184
250
                        for rec in records:
186
252
                                end = rec.End
187
253
                                cls = rec.Class
188
254
                                classDefs[start] = cls
189
 
                                for glyphID in range(font.getGlyphID(start) + 1,
190
 
                                                font.getGlyphID(end)):
191
 
                                        classDefs[font.getGlyphName(glyphID)] = cls
 
255
                                glyphIDs = range(font.getGlyphID(start) + 1, font.getGlyphID(end))
 
256
                                lenList = len(glyphIDs)
 
257
                                keyList = [getGlyphName(glyphID) for glyphID in glyphIDs]
 
258
                                map(operator.setitem,  [classDefs]*lenList, keyList, [cls]*lenList)
192
259
                                classDefs[end] = cls
193
260
                else:
194
261
                        assert 0, "unknown format: %s" % self.Format
199
266
                if classDefs is None:
200
267
                        classDefs = self.classDefs = {}
201
268
                items = classDefs.items()
 
269
                getGlyphID = font.getGlyphID
202
270
                for i in range(len(items)):
203
271
                        glyphName, cls = items[i]
204
 
                        items[i] = font.getGlyphID(glyphName), glyphName, cls
 
272
                        items[i] = getGlyphID(glyphName), glyphName, cls
205
273
                items.sort()
206
274
                if items:
207
275
                        last, lastName, lastCls = items[0]
247
315
                if self.Format == 1:
248
316
                        input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
249
317
                        alts = rawTable["AlternateSet"]
250
 
                        assert len(input) == len(alts)
 
318
                        if len(input) != len(alts):
 
319
                                assert len(input) == len(alts)
251
320
                        for i in range(len(input)):
252
321
                                alternates[input[i]] = alts[i].Alternate
253
322
                else:
265
334
                        items[i] = font.getGlyphID(glyphName), glyphName, set
266
335
                items.sort()
267
336
                cov = Coverage()
268
 
                glyphs = []
 
337
                cov.glyphs = [ item[1] for item in items]
269
338
                alternates = []
270
 
                cov.glyphs = glyphs
271
 
                for glyphID, glyphName, set in items:
272
 
                        glyphs.append(glyphName)
 
339
                setList = [ item[-1] for item in items]
 
340
                for  set in setList:
273
341
                        alts = AlternateSet()
274
342
                        alts.Alternate = set
275
343
                        alternates.append(alts)
 
344
                # a special case to deal with the fact that several hundred Adobe Japan1-5
 
345
                # CJK fonts will overflow an offset if the coverage table isn't pushed to the end.
 
346
                # Also useful in that when splitting a sub-table because of an offset overflow
 
347
                # I don't need to calculate the change in the subtable offset due to the change in the coverage table size.
 
348
                # Allows packing more rules in subtable.
 
349
                self.sortCoverageLast = 1 
276
350
                return {"Coverage": cov, "AlternateSet": alternates}
277
351
        
278
352
        def toXML2(self, xmlWriter, font):
307
381
        def postRead(self, rawTable, font):
308
382
                ligatures = {}
309
383
                if self.Format == 1:
310
 
                        input = _getGlyphsFromCoverageTable(rawTable["Coverage"])
 
384
                        input = rawTable["Coverage"].glyphs
311
385
                        ligSets = rawTable["LigatureSet"]
312
386
                        assert len(input) == len(ligSets)
313
387
                        for i in range(len(input)):
317
391
                self.ligatures = ligatures
318
392
        
319
393
        def preWrite(self, font):
320
 
                self.Format = 1
321
394
                ligatures = getattr(self, "ligatures", None)
322
395
                if ligatures is None:
323
396
                        ligatures = self.ligatures = {}
326
399
                        glyphName, set = items[i]
327
400
                        items[i] = font.getGlyphID(glyphName), glyphName, set
328
401
                items.sort()
329
 
                glyphs = []
330
402
                cov = Coverage()
331
 
                cov.glyphs = glyphs
 
403
                cov.glyphs = [ item[1] for item in items]
 
404
 
332
405
                ligSets = []
333
 
                for glyphID, glyphName, set in items:
334
 
                        glyphs.append(glyphName)
 
406
                setList = [ item[-1] for item in items ]
 
407
                for set in setList:
335
408
                        ligSet = LigatureSet()
336
409
                        ligs = ligSet.Ligature = []
337
410
                        for lig in set:
338
411
                                ligs.append(lig)
339
412
                        ligSets.append(ligSet)
 
413
                # Useful in that when splitting a sub-table because of an offset overflow
 
414
                # I don't need to calculate the change in subtabl offset due to the coverage table size.
 
415
                # Allows packing more rules in subtable.
 
416
                self.sortCoverageLast = 1 
340
417
                return {"Coverage": cov, "LigatureSet": ligSets}
341
418
        
342
419
        def toXML2(self, xmlWriter, font):
400
477
        'JstfMax': ('ShrinkageJstfMax', 'ExtensionJstfMax',),
401
478
}
402
479
 
 
480
#
 
481
# OverFlow logic, to automatically create ExtensionLookups
 
482
# XXX This should probably move to otBase.py
 
483
#
 
484
 
 
485
def fixLookupOverFlows(ttf, overflowRecord):
 
486
        """ Either the offset from the LookupList to a lookup overflowed, or
 
487
        an offset from a lookup to a subtable overflowed. 
 
488
        The table layout is:
 
489
        GPSO/GUSB
 
490
                Script List
 
491
                Feature List
 
492
                LookUpList
 
493
                        Lookup[0] and contents
 
494
                                SubTable offset list
 
495
                                        SubTable[0] and contents
 
496
                                        ...
 
497
                                        SubTable[n] and contents
 
498
                        ...
 
499
                        Lookup[n] and contents
 
500
                                SubTable offset list
 
501
                                        SubTable[0] and contents
 
502
                                        ...
 
503
                                        SubTable[n] and contents
 
504
        If the offset to a lookup overflowed (SubTableIndex == None)
 
505
                we must promote the *previous*  lookup to an Extension type.
 
506
        If the offset from a lookup to subtable overflowed, then we must promote it 
 
507
                to an Extension Lookup type.
 
508
        """
 
509
        ok = 0
 
510
        lookupIndex = overflowRecord.LookupListIndex
 
511
        if (overflowRecord.SubTableIndex == None):
 
512
                lookupIndex = lookupIndex - 1
 
513
        if lookupIndex < 0:
 
514
                return ok
 
515
        if overflowRecord.tableType == 'GSUB':
 
516
                extType = 7
 
517
        elif overflowRecord.tableType == 'GPOS':
 
518
                extType = 9
 
519
 
 
520
        lookups = ttf[overflowRecord.tableType].table.LookupList.Lookup
 
521
        lookup = lookups[lookupIndex]
 
522
        # If the previous lookup is an extType, look further back. Very unlikely, but possible.
 
523
        while lookup.LookupType == extType:
 
524
                lookupIndex = lookupIndex -1
 
525
                if lookupIndex < 0:
 
526
                        return ok
 
527
                lookup = lookups[lookupIndex]
 
528
                
 
529
        for si in range(len(lookup.SubTable)):
 
530
                subTable = lookup.SubTable[si]
 
531
                extSubTableClass = lookupTypes[overflowRecord.tableType][extType]
 
532
                extSubTable = extSubTableClass()
 
533
                extSubTable.Format = 1
 
534
                extSubTable.ExtensionLookupType = lookup.LookupType
 
535
                extSubTable.ExtSubTable = subTable
 
536
                lookup.SubTable[si] = extSubTable
 
537
        lookup.LookupType = extType
 
538
        ok = 1
 
539
        return ok
 
540
 
 
541
def splitAlternateSubst(oldSubTable, newSubTable, overflowRecord):
 
542
        ok = 1
 
543
        newSubTable.Format = oldSubTable.Format
 
544
        if hasattr(oldSubTable, 'sortCoverageLast'):
 
545
                newSubTable.sortCoverageLast = oldSubTable.sortCoverageLast
 
546
        
 
547
        oldAlts = oldSubTable.alternates.items()
 
548
        oldAlts.sort()
 
549
        oldLen = len(oldAlts)
 
550
 
 
551
        if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
 
552
                # Coverage table is written last. overflow is to or within the
 
553
                # the coverage table. We will just cut the subtable in half.
 
554
                newLen = int(oldLen/2)
 
555
 
 
556
        elif overflowRecord.itemName == 'AlternateSet':
 
557
                # We just need to back up by two items 
 
558
                # from the overflowed AlternateSet index to make sure the offset
 
559
                # to the Coverage table doesn't overflow.
 
560
                newLen  = overflowRecord.itemIndex - 1
 
561
 
 
562
        newSubTable.alternates = {}
 
563
        for i in range(newLen, oldLen):
 
564
                item = oldAlts[i]
 
565
                key = item[0]
 
566
                newSubTable.alternates[key] = item[1]
 
567
                del oldSubTable.alternates[key]
 
568
 
 
569
 
 
570
        return ok
 
571
 
 
572
 
 
573
def splitLigatureSubst(oldSubTable, newSubTable, overflowRecord):
 
574
        ok = 1
 
575
        newSubTable.Format = oldSubTable.Format
 
576
        oldLigs = oldSubTable.ligatures.items()
 
577
        oldLigs.sort()
 
578
        oldLen = len(oldLigs)
 
579
 
 
580
        if overflowRecord.itemName in [ 'Coverage', 'RangeRecord']:
 
581
                # Coverage table is written last. overflow is to or within the
 
582
                # the coverage table. We will just cut the subtable in half.
 
583
                newLen = int(oldLen/2)
 
584
 
 
585
        elif overflowRecord.itemName == 'LigatureSet':
 
586
                # We just need to back up by two items 
 
587
                # from the overflowed AlternateSet index to make sure the offset
 
588
                # to the Coverage table doesn't overflow.
 
589
                newLen  = overflowRecord.itemIndex - 1
 
590
 
 
591
        newSubTable.ligatures = {}
 
592
        for i in range(newLen, oldLen):
 
593
                item = oldLigs[i]
 
594
                key = item[0]
 
595
                newSubTable.ligatures[key] = item[1]
 
596
                del oldSubTable.ligatures[key]
 
597
 
 
598
        return ok
 
599
 
 
600
 
 
601
splitTable = {  'GSUB': {
 
602
#                                       1: splitSingleSubst,
 
603
#                                       2: splitMultipleSubst,
 
604
                                        3: splitAlternateSubst,
 
605
                                        4: splitLigatureSubst,
 
606
#                                       5: splitContextSubst,
 
607
#                                       6: splitChainContextSubst,
 
608
#                                       7: splitExtensionSubst,
 
609
#                                       8: splitReverseChainSingleSubst,
 
610
                                        },
 
611
                                'GPOS': {
 
612
#                                       1: splitSinglePos,
 
613
#                                       2: splitPairPos,
 
614
#                                       3: splitCursivePos,
 
615
#                                       4: splitMarkBasePos,
 
616
#                                       5: splitMarkLigPos,
 
617
#                                       6: splitMarkMarkPos,
 
618
#                                       7: splitContextPos,
 
619
#                                       8: splitChainContextPos,
 
620
#                                       9: splitExtensionPos,
 
621
                                        }
 
622
 
 
623
                        }
 
624
 
 
625
def fixSubTableOverFlows(ttf, overflowRecord):
 
626
        """ 
 
627
        An offset has overflowed within a sub-table. We need to divide this subtable into smaller parts.
 
628
        """
 
629
        ok = 0
 
630
        table = ttf[overflowRecord.tableType].table
 
631
        lookup = table.LookupList.Lookup[overflowRecord.LookupListIndex]
 
632
        subIndex = overflowRecord.SubTableIndex
 
633
        subtable = lookup.SubTable[subIndex]
 
634
 
 
635
        if hasattr(subtable, 'ExtSubTable'):
 
636
                # We split the subtable of the Extension table, and add a new Extension table
 
637
                # to contain the new subtable.
 
638
 
 
639
                subTableType = subtable.ExtensionLookupType
 
640
                extSubTable = subtable
 
641
                subtable = extSubTable.ExtSubTable
 
642
                newExtSubTableClass = lookupTypes[overflowRecord.tableType][lookup.LookupType]
 
643
                newExtSubTable = newExtSubTableClass()
 
644
                newExtSubTable.Format = extSubTable.Format
 
645
                newExtSubTable.ExtensionLookupType = extSubTable.ExtensionLookupType
 
646
                lookup.SubTable.insert(subIndex + 1, newExtSubTable)
 
647
 
 
648
                newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
 
649
                newSubTable = newSubTableClass()
 
650
                newExtSubTable.ExtSubTable = newSubTable
 
651
        else:
 
652
                subTableType = lookup.LookupType
 
653
                newSubTableClass = lookupTypes[overflowRecord.tableType][subTableType]
 
654
                newSubTable = newSubTableClass()
 
655
                lookup.SubTable.insert(subIndex + 1, newSubTable)
 
656
 
 
657
        if hasattr(lookup, 'SubTableCount'): # may not be defined yet.
 
658
                lookup.SubTableCount = lookup.SubTableCount + 1
 
659
 
 
660
        try:
 
661
                splitFunc = splitTable[overflowRecord.tableType][subTableType]
 
662
        except KeyError:
 
663
                return ok
 
664
 
 
665
        ok = splitFunc(subtable, newSubTable, overflowRecord)
 
666
        return ok
 
667
 
 
668
# End of OverFlow logic
 
669
 
403
670
 
404
671
def _buildClasses():
405
672
        import new, re