source: packages/compositor/trunk/Lib/compositor/tables.py @ 4

Revision 4, 13.1 KB checked in by tal, 6 years ago (diff)
The cmap coming in is a reversed cmap. No need to reverse it again.
Line 
1"""
2GSUB, GPOS and GDEF table objects.
3"""
4
5try:
6    set
7except NameError:
8    from sets import Set as set
9
10try:
11    sorted
12except NameError:
13    def sorted(iterable):
14        if not isinstance(iterable, list):
15            iterable = list(iterable)
16        iterable.sort()
17        return iterable
18
19import unicodedata
20from cmap import reverseCMAP
21from scriptList import ScriptList
22from featureList import FeatureList
23from lookupList import GSUBLookupList, GPOSLookupList
24from classDefinitionTables import MarkAttachClassDef, GlyphClassDef
25
26
27defaultOnFeatures = [
28    # GSUB
29    "calt",
30    "ccmp", # this should always be the first feature processed
31    "clig",
32    "fina",
33    "half", # applies only to indic
34    "init",
35    "isol",
36    "liga",
37    "locl",
38    "med2", # applies only to syriac
39    "medi",
40    "nukt", # applies only to indic
41    "pref", # applies only to khmer and myanmar
42    "pres", # applies only to indic
43    "pstf", # applies only to indic
44    "psts",
45    "rand",
46    "rlig", # applies only to arabic and syriac
47    "rphf", # applies only to indic
48    "tjmo", # applies only to hangul
49    "vatu", # applies only to indic
50    "vjmo", # applies only to hangul
51    # GPOS
52    "abvm",  # applies only to indic
53    "blwm",  # applies only to indic
54    "kern",
55    "mark",
56    "mkmk",
57    "opbd",
58    "vkrn"
59]
60
61
62class BaseTable(object):
63
64    def __init__(self, table, reversedCMAP, gdef):
65        self.ScriptList = ScriptList(table.table.ScriptList)
66        self.FeatureList = FeatureList(table.table.FeatureList)
67        self.LookupList = self._LookupListClass(table.table.LookupList, self, gdef)
68
69        self._cmap = reversedCMAP
70
71        self._featureApplicationStates = {}
72        self._applicableFeatureCache = {}
73        self._featureTags = None
74        self.getFeatureList()
75        self._setDefaultFeatureApplicationStates()
76
77    def process(self, glyphRecords, script="latn", langSys=None, logger=None):
78        """
79        Pass the list of GlyphRecord objects through the features
80        applicable for the given script and langSys. This returns
81        a list of processed GlyphRecord objects.
82        """
83        applicableLookups = self._preprocess(script, langSys)
84        if logger:
85            logger.logApplicableLookups(self, applicableLookups)
86            logger.logProcessingStart()
87        result = self._processLookups(glyphRecords, applicableLookups, logger=logger)
88        if logger:
89            logger.logProcessingEnd()
90        return result
91
92    # ------------------
93    # feature management
94    # ------------------
95
96    def _setDefaultFeatureApplicationStates(self):
97        """
98        Activate all features defined as on by
99        default in the Layout Tag Registry.
100        """
101        for tag in self._featureTags:
102            if tag in defaultOnFeatures:
103                state = True
104            else:
105                state = False
106            self._featureApplicationStates[tag] = state
107
108    def __contains__(self, featureTag):
109        return featureTag in self._featureTags
110
111    def getFeatureList(self):
112        """
113        Get a list of all available features in the table.
114        """
115        if self._featureTags is None:
116            featureList = self.FeatureList
117            featureRecords = featureList.FeatureRecord
118            self._featureTags = []
119            for featureRecord in featureRecords:
120                tag = featureRecord.FeatureTag
121                if tag not in self._featureTags:
122                    self._featureTags.append(tag)
123        return self._featureTags
124
125    def getFeatureState(self, featureTag):
126        """
127        Get a boolean representing if a feature is on or not.
128        """
129        return self._featureApplicationStates[featureTag]
130
131    def setFeatureState(self, featureTag, state):
132        """
133        Set the application state of a feature.
134        """
135        self._featureApplicationStates[featureTag] = state
136
137    # -------------
138    # preprocessing
139    # -------------
140
141    def _preprocess(self, script, langSys):
142        """
143        Get a list of ordered (featureTag, lookupObject)
144        for the given script and langSys.
145        """
146        # 1. get a list of applicable feature records
147        #    based on the script and langSys
148        features = self._getApplicableFeatures(script, langSys)
149        # 2. get a list of applicable lookup tables based on the
150        #    found features and the feature application states
151        lookupIndexes = set()
152        for feature in features:
153            featureTag = feature.FeatureTag
154            if not self._featureApplicationStates[featureTag]:
155                continue
156            featureRecord = feature.Feature
157            if featureRecord.LookupCount:
158                for lookupIndex in featureRecord.LookupListIndex:
159                    lookupIndexes.add((lookupIndex, featureTag))
160        # 3. get a list of ordered lookup records for each feature
161        lookupList = self.LookupList
162        lookupRecords = lookupList.Lookup
163        applicableLookups = []
164        for lookupIndex, featureTag in sorted(lookupIndexes):
165            lookup = lookupRecords[lookupIndex]
166            applicableLookups.append((featureTag, lookup))
167        return applicableLookups
168
169    def _getApplicableFeatures(self, script, langSys):
170        """
171        Get a list of features that apply to
172        a particular script and langSys. Both
173        script and langSys can be None. However,
174        if script is None and no script record
175        in the font is assigned to DFLT, no
176        features wil be found.
177        """
178        # first check to see if this has already been found
179        if (script, langSys) in self._applicableFeatureCache:
180            return self._applicableFeatureCache[script, langSys]
181        scriptList = self.ScriptList
182        # 1. Find the appropriate script record
183        scriptRecords = scriptList.ScriptRecord
184        defaultScript = None
185        applicableScript = None
186        for scriptRecord in scriptRecords:
187            scriptTag = scriptRecord.ScriptTag
188            if scriptTag == "DFLT":
189                defaultScript = scriptRecord.Script
190                continue
191            if scriptTag == script:
192                applicableScript = scriptRecord.Script
193                break
194        # 2. if no suitable script record was found, return an empty list
195        if applicableScript is None:
196            applicableScript = defaultScript
197        if applicableScript is None:
198            return []
199        # 3. get the applicable langSys records
200        defaultLangSys = applicableScript.DefaultLangSys
201        specificLangSys = None
202        # if we have a langSys and the table
203        # defines specific langSys behavior,
204        # try to find a matching langSys record
205        if langSys is not None and applicableScript.LangSysCount:
206            for langSysRecord in applicableScript.LangSysRecord:
207                langSysTag = langSysRecord.LangSysTag
208                if langSysTag == langSys:
209                    specificLangSys = langSysRecord.LangSys
210                    break
211        # 4. get the list of applicable features
212        applicableFeatures = set()
213        if defaultLangSys.FeatureCount:
214            applicableFeatures |= set(defaultLangSys.FeatureIndex)
215        if defaultLangSys.ReqFeatureIndex != 0xFFFF:
216            applicableFeatures.add(defaultLangSys.ReqFeatureIndex)
217        if specificLangSys is not None:
218            if specificLangSys.FeatureCount:
219                applicableFeatures |= set(specificLangSys.FeatureIndex)
220            if specificLangSys.ReqFeatureIndex != 0xFFFF:
221                applicableFeatures.add(specificLangSys.ReqFeatureIndex)
222        applicableFeatures = self._getFeatures(applicableFeatures)
223        # store the found features for potential use by this method
224        self._applicableFeatureCache[script, langSys] = applicableFeatures
225        return applicableFeatures
226
227    def _getFeatures(self, indices):
228        """
229        Get a list of ordered features located at indices.
230        """
231        featureList = self.FeatureList
232        featureRecords = featureList.FeatureRecord
233        features = [featureRecords[index] for index in sorted(indices)]
234        return features
235
236    def _getLookups(self, indices):
237        """
238        Get a list of ordered lookups at indices
239        """
240        lookupList = self.LookupList
241        lookupRecords = lookupList.Lookup
242        lookups = [lookupRecords[index] for index in sorted(indices)]
243        return lookups
244
245    # ----------
246    # processing
247    # ----------
248
249    def _processLookups(self, glyphRecords, lookups, processingAalt=False, logger=None):
250        aaltHolding = []
251        whitespaceSensitive = set(["init", "medi", "fina", "isol"])
252        for featureTag, lookup in lookups:
253            # store aalt for processing at the end
254            if not processingAalt and featureTag == "aalt":
255                aaltHolding.append((featureTag, lookup))
256                continue
257            if logger:
258                logger.logLookupStart(self, featureTag, lookup)
259            processed = []
260            # init, medi and fina need to be aware
261            # of word boundaries. determining the
262            # boundaries incurs some expense, so
263            # only do it when necessary.
264            testForWhitespace = featureTag in whitespaceSensitive
265            if testForWhitespace:
266                previousWasWhitespace = True
267                nextIsWhiteSpace = self._nextIsWhitespace(glyphRecords)
268            # loop through the glyph records
269            while glyphRecords:
270                skip = False
271                if testForWhitespace:
272                    previousWasWhitespace = self._previousWasWhitespace(processed)
273                    nextIsWhiteSpace = self._nextIsWhitespace(glyphRecords)
274                    if featureTag == "init" and not previousWasWhitespace:
275                        skip = True
276                    elif featureTag == "fina" and not nextIsWhiteSpace:
277                        skip = True
278                    elif featureTag == "medi":
279                        if previousWasWhitespace:
280                            skip = True
281                        if nextIsWhiteSpace:
282                            skip = True
283                    elif featureTag == "isol":
284                        if not previousWasWhitespace:
285                            skip = True
286                        if not nextIsWhiteSpace:
287                            skip = True
288                # loop through the lookups subtables
289                performedAction = False
290                if not skip:
291                    processed, glyphRecords, performedAction = self._processLookup(processed, glyphRecords, lookup, featureTag, logger=logger)
292                if not performedAction:
293                    processed.append(glyphRecords[0])
294                    glyphRecords = glyphRecords[1:]
295            glyphRecords = processed
296            if logger:
297                logger.logLookupEnd()
298        # process aalt for the final glyph records
299        if not processingAalt and aaltHolding:
300            glyphRecords = self._processLookups(glyphRecords, aaltHolding, processingAalt=True, logger=logger)
301        return glyphRecords
302
303    def _processLookup(self, processed, glyphRecords, lookup, featureTag, logger=None):
304        performedAction = False
305        for subtable in lookup.SubTable:
306            if logger:
307                logger.logSubTableStart(lookup, subtable)
308                logger.logInput(processed, glyphRecords)
309            processed, glyphRecords, performedAction = subtable.process(processed, glyphRecords, featureTag)
310            if logger:
311                if performedAction:
312                    logger.logOutput(processed, glyphRecords)
313                logger.logSubTableEnd()
314            if performedAction:
315                break
316        return processed, glyphRecords, performedAction
317
318    # ------------------
319    # whitespace testing
320    # ------------------
321
322    def _isWhitespace(self, glyphRecord):
323        glyphName = glyphRecord.glyphName
324        if glyphName not in self._cmap:
325            return False
326        for uniValue in self._cmap[glyphName]:
327            uniChr = unichr(uniValue)
328            if unicodedata.category(uniChr) == "Zs":
329                return True
330        return False
331
332    def _previousWasWhitespace(self, processed):
333        if not processed:
334            return True
335        glyphRecord = processed[-1]
336        return self._isWhitespace(glyphRecord)
337
338    def _nextIsWhitespace(self, glyphRecords):
339        if len(glyphRecords) < 2:
340            return True
341        glyphRecord = glyphRecords[1]
342        return self._isWhitespace(glyphRecord)
343
344
345class GSUB(BaseTable):
346
347    _LookupListClass = GSUBLookupList
348
349
350class GPOS(BaseTable):
351
352    _LookupListClass = GPOSLookupList
353
354
355class GDEF(object):
356
357    def __init__(self, table):
358        table = table.table
359        self.GlyphClassDef = GlyphClassDef(table.GlyphClassDef)
360        if table.AttachList is not None:
361            raise NotImplementedError("Need GDEF sample with AttachList")
362        if table.LigCaretList is not None:
363            raise NotImplementedError("Need GDEF sample with LigCaretList")
364        if table.MarkAttachClassDef is None:
365            self.MarkAttachClassDef = None
366        else:
367            self.MarkAttachClassDef = MarkAttachClassDef(table.MarkAttachClassDef)
368
Note: See TracBrowser for help on using the repository browser.