otlLookup.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. from lxml.etree import Element
  2. from data import Tag
  3. # OTLFeature
  4. # -----------------------------
  5. # Classes representing common OpenType Layout Lookup structures.
  6. # (https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#features-and-lookups)
  7. def singleGlyphName(g): # TODO: replace this with a more complex name system for glyph classes.
  8. """
  9. Takes the first codepoint of a ligature and returns a string of it's glyph name.
  10. """
  11. return f"u{g.codepoints.seq[0]:x}"
  12. def glyphName(int): # TODO: replace this with a more complex name system for glyph classes.
  13. return f"u{int:x}"
  14. class LookupType4:
  15. """
  16. Table somewhat representing a LookupType 4.
  17. (https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#lookuptype-4-ligature-substitution-subtable)
  18. Generated from user input glyphs.
  19. """
  20. def __init__(self, glyphs):
  21. self.lookupType = 4
  22. self.lookupFlag = 0
  23. # creating a data structure that will work for LigatureSets.
  24. ligatureSubst = {}
  25. for g in glyphs['all']:
  26. if len(g.codepoints) > 1: # if a ligature
  27. # if the first glyph has already been represented,
  28. # append it
  29. if singleGlyphName(g) in ligatureSubst.keys():
  30. ligatureSubst[singleGlyphName(g)].append(g)
  31. # if not, then make a new list with it
  32. # and then append it
  33. else:
  34. ligatureSubst[singleGlyphName(g)] = []
  35. ligatureSubst[singleGlyphName(g)].append(g)
  36. self.ligatureSubst = ligatureSubst
  37. def toTTX(self, index):
  38. lookup = Element("Lookup", {"index": str(index) })
  39. lookup.append(Element("LookupType", {"value": str(self.lookupType) }))
  40. lookup.append(Element("LookupFlag", {"value": str(self.lookupFlag) }))
  41. ligatureSubst = Element("LigatureSubst", {"index": "0", "format": "1" })
  42. for firstCodepoint, ligatureSet in self.ligatureSubst.items():
  43. # Ligature subtables MUST be ordered from the longest lists to the shortest.
  44. # Otherwise, the text client probably won't find them.
  45. ligatureSet.sort(key=len, reverse=True)
  46. ligatureSetTTX = Element("LigatureSet", {"glyph" : firstCodepoint})
  47. for g in ligatureSet:
  48. if g.alias:
  49. glyphTarget = g.alias.name()
  50. else:
  51. glyphTarget = g.name()
  52. components = ','.join(map(glyphName, g.codepoints.seq[1:]))
  53. ligatureSetTTX.append(Element("Ligature", {"components": components, "glyph": glyphTarget }))
  54. ligatureSubst.append(ligatureSetTTX)
  55. lookup.append(ligatureSubst)
  56. return lookup
  57. class LookupList:
  58. def __init__(self, glyphs):
  59. self.lookups = [LookupType4(glyphs)]
  60. def toTTX(self):
  61. lookupList = Element("LookupList")
  62. for index, l in enumerate(self.lookups):
  63. lookupList.append(l.toTTX(index))
  64. return lookupList