robotools / compositor Goto Github PK
View Code? Open in Web Editor NEWA basic OpenType GSUB and GPOS layout engine.
License: MIT License
A basic OpenType GSUB and GPOS layout engine.
License: MIT License
Hi Tal,
I can't load an OTF font with compositor using the latest version of fontTools.
Sounds familiar? Are they compatible?
Thanks in advance.
Here's the traceback:
>>> font = compositor.Font('/Users/yanone/Schriften/Font Produktion/Fonts/NonameSans-Regular.otf')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/yanone/Code/svn/typesupply/compositor.git/Lib/compositor/font.py", line 22, in __init__
self.loadFeatures()
File "/Users/yanone/Code/svn/typesupply/compositor.git/Lib/compositor/font.py", line 117, in loadFeatures
self.setFeatureTables(gdef, gsub, gpos)
File "/Users/yanone/Code/svn/typesupply/compositor.git/Lib/compositor/layoutEngine.py", line 37, in setFeatureTables
self.gsub = GSUB().loadFromFontTools(gsub, self.reversedCMAP, self.gdef)
File "/Users/yanone/Code/svn/typesupply/compositor.git/Lib/compositor/tables.py", line 67, in loadFromFontTools
self.LookupList = self._LookupListClass().loadFromFontTools(table.table.LookupList, gdef)
File "/Users/yanone/Code/svn/typesupply/compositor.git/Lib/compositor/lookupList.py", line 27, in loadFromFontTools
self.Lookup = [self._LookupClass().loadFromFontTools(lookup, self, gdef) for lookup in lookupList.Lookup]
File "/Users/yanone/Code/svn/typesupply/compositor.git/Lib/compositor/lookupList.py", line 58, in loadFromFontTools
obj = cls().loadFromFontTools(subtable, self)
File "/Users/yanone/Code/svn/typesupply/compositor.git/Lib/compositor/subTablesGSUB.py", line 101, in loadFromFontTools
self.Coverage = Coverage().loadFromFontTools(subtable.Coverage)
File "/Library/Python/2.7/site-packages/fonttools-3.6.3.dev0-py2.7.egg/fontTools/ttLib/tables/otBase.py", line 539, in __getattr__
raise AttributeError(attr)
AttributeError: Coverage
it should be CMAP with caps instead of lowercase
error: https://github.com/typesupply/compositor/blob/master/Lib/compositor/font.py#L37
api callback: https://github.com/typesupply/compositor/blob/master/Lib/compositor/layoutEngine.py#L23
A student of mine found a problem in the FeaturePreview.roboFontExt that I traced back to Compositor, but I'm a little bit lost with how to fix it — the handling of GPOS mark
positioning doesn't seem to be working properly when the text is right-to-left.
Here's an example as I visualize the problem in DrawBot using Noto Sans Hebrew as a sample font:
The green text is in a normal text box in DrawBot, and the marks are positioned correctly, as a control. The red text is when rightToLeft
is True, the direction is correct but the positioning is off by one base character. The purple text shows that when the text is processed in the incorrect order the mark positioning is correct.
I'm also attaching my DrawBot script below if it helps with testing the problem, and I'm testing with the latest release of Noto which can be found here: https://www.google.com/get/noto/
from compositor import Font as cFont
from fontTools.pens.cocoaPen import CocoaPen
size(200, 200)
fontPath = "./NotoSansHebrew-Medium.ttf"
def drawRecords(glyphRecords):
for record in glyphRecords:
save()
translate(record.xPlacement, record.yPlacement)
glyph = f[record.glyphName]
pen = CocoaPen(f)
glyph.draw(pen)
path = BezierPath()
drawPath(pen.path)
restore()
translate(glyph.width)
# Green text in a text box,
# Correct positioning
font("Lucida Grande")
fontSize(8)
fill(0)
text("In a text box", (10, 150))
font(fontPath)
fontSize(30)
fill(0, 0.5, 0, 1)
text("בַּיִת", (100, 150)) # oooh, this line looks weird but it works
# Red text with Compositor
# rightToLeft set to True
# Correct direction, incorrect mark positioning
font("Lucida Grande")
fontSize(8)
fill(0)
text("rightToLeft = True", (10, 100))
fill(0.75, 0, 0, 1)
save()
scale(0.05, 0.05)
translate(2000, 2000)
f = cFont(fontPath)
glyphRecords = f.process(u"בַּיִת", script="hebr", rightToLeft=True)
drawRecords(glyphRecords)
restore()
# Purple text with Compositor
# rightToLeft = False
# Incorrect direction, but correct mark positioning
font("Lucida Grande")
fontSize(8)
fill(0)
text("rightToLeft = False", (10, 50))
fill(0.5, 0, 0.5, 1)
save()
scale(0.05, 0.05)
translate(2000, 1000)
f = cFont(fontPath)
glyphRecords = f.process(u"בַּיִת", script="hebr", rightToLeft=False)
drawRecords(glyphRecords)
restore()
I was quickly testing a basic mark feature
The current version:
an older version:
this is tested with the current version of FeaturePreview.roboFontExt
The new version also leaves traceback on non existing glyphs.
Traceback (most recent call last):
File "/Users/frederik/Documents/dev/tmt/vanilla/Lib/vanilla/vanillaEditText.py", line 11, in controlTextDidChange_
self.action_(notification.object())
File "/Users/frederik/Documents/dev/tmt/vanilla/Lib/vanilla/vanillaBase.py", line 210, in action_
self.callback(sender)
File "/Users/frederik/Documents/dev/tmt/defconAppKit/Lib/defconAppKit/controls/glyphSequenceEditText.py", line 20, in _inputCallback
self._finalCallback(self)
File "featurePreview.py", line 91, in glyphLineViewInputCallback
File "featurePreview.py", line 189, in updateGlyphLineView
File "/Users/frederik/Documents/dev/typeSupply/compositor/Lib/compositor/font.py", line 157, in process
glyphRecord.advanceWidth += self[glyphRecord.glyphName].width
File "/Users/frederik/Documents/dev/typeSupply/compositor/Lib/compositor/font.py", line 130, in __getitem__
glyph = self.glyphSet[name]
File "/Users/frederik/Documents/dev/fonttools/Lib/fontTools/ttLib/__init__.py", line 701, in __getitem__
return self._glyphType(self, self._glyphs[glyphName], self._hmtx[glyphName])
File "/Users/frederik/Documents/dev/fonttools/Lib/fontTools/cffLib.py", line 532, in __getitem__
charString = self.charStrings[name]
KeyError: 'g'
a small patch to make compositor work with the github fontTools version
as far as I have tested it seems like the github fontTools doesn't add a Format attribute anymore, wondering why. It is not writing the Format attr to ttx.
if hasattr(coverage, "Format"):
self.CoverageFormat = coverage.Format
https://github.com/typesupply/compositor/blob/master/Lib/compositor/subTablesBase.py#L462
thanks
One thing that may help with the memory footprint is to recycle duplicate ClassDef objects. As it is now, every class is fully expanded at all times. This makes sense given that I was trying to keep the code simple instead of worrying about memory when I write this library. Anyway, here's a quick sketch:
import hashlib
class _ClassDefRegistry(object):
def __init__(self):
self._classes = {}
def _makeIdentifier(self, mapping):
identifier = []
for name, value in sorted(mapping.items()):
identifier.append("%s %d" % (name, value))
identifier = "\n".join(identifier)
identifier = hashlib.md5(identifier) # this may be over-engineering
return identifier
def store(self, mapping):
identifier = self._makeIdentifier(mapping)
self._classes[identifier] = mapping
return identifier
def __getitem__(self, identifier):
return self._classes[identifier]
class ClassDef(object):
__slots__ = ["_identifier", "_classDefRegistry", "_map", "ClassFormat", "Glyphs"]
def __init__(self, classDefRegistry):
self._classDefRegistry = classDefRegistry
self.ClassFormat = None
def loadFromFontTools(self, classDef):
self.ClassFormat = classDef.Format
self._identifier = self._classDefRegistry.store(dict(classDef.classDefs))
return self
def __getitem__(self, glyphName):
return self.Glyphs.get(glyphName, 0)
def _get_Glyphs(self):
return self._classDefRegistry[self._identifier]
Glyphs = property(_get_Glyphs, doc="This is for reference only. Not for use in processing.")
# ----
# Test
# ----
class _Dummy(object):
def __init__(self):
self.Format = 0
self.classDefs = {}
test = _Dummy()
letters = "abcdefghijklmnopqrstuvwxyz"
for i in range(26):
for j in range(1, 11):
name = letters[i] * j
value = len(test.classDefs)
test.classDefs[name] = value
registry = _ClassDefRegistry()
classDef = ClassDef(registry)
classDef.loadFromFontTools(test)
print classDef.Glyphs
print classDef["mmm"]
This would require some significant structural engineering so that the global registry is passed around as needed.
When I wrote Compositor, I didn't have any test cases for these so I implemented them following my interpretation of the spec. The spec for these is pretty complex and (as I recall) incomplete/contradictory/confusing. So, the implementation is probably not correct. Now that the .fea syntax supports these lookup types, some test cases can be built and the implementation can be validated.
The test cases need to be incremental and atomic so that only one variable is being tested at a time. A simple text format that builds an in-memory font on the fly with defcon and pushes it through feaLib is going to be the easiest way to implement this.
I need to document the meaning of the values in compositor's glyph records after GPOS processing.
The idea is that these are "pure" GPOS values, especially for the advances. In theory, this makes it easier to evaluate the results of GPOS processing. That was the original goal of compositor.
# syntax: position cursive <glyph|glyphclass> <entryAnchor> <exitAnchor>;
glyph1.width = 100
glyph1.height = 1000
glyph2.width = 300
glyph2.height = 3000
# no exit | no entry
position cursive glyph1 <anchor NULL> <anchor NULL>;
position cursive glyph2 <anchor NULL> <anchor NULL>;
<glyph1 0 0 0 0> <glyph2 0 0 0 0>
# exit | no entry
position cursive glyph1 <anchor NULL> <anchor 10 30>;
position cursive glyph2 <anchor NULL> <anchor NULL>;
<glyph1 0 0 0 0> <glyph2 -90 -970 ? ?>
# no exit | entry
position cursive glyph1 <anchor NULL> <anchor NULL>;
position cursive glyph2 <anchor 1 3> <anchor NULL>;
<glyph1 0 0 0 0> <glyph2 -1 -3 ? ?>
# exit | entry
position cursive glyph1 <anchor NULL> <anchor 10 30>;
position cursive glyph2 <anchor 1 3> <anchor NULL>;
<glyph1 0 0 0 0> <glyph2 -91 -973 ? ?>
# entry + no exit | no entry + exit
position cursive glyph1 <anchor 10 30> <anchor NULL>;
position cursive glyph2 <anchor NULL> <anchor 1 3>;
<glyph1 0 0 0 0> <glyph2 0 0 0 0>
Test Case Syntax Goes Here
Test Case Syntax Goes Here
Test Case Syntax Goes Here
File "c:\users\admin\downloads\defcon\lib\defcon\objects\layoutEngine.py", line 207, in process
self._updateEngine()
File "c:\users\admin\downloads\defcon\lib\defcon\objects\layoutEngine.py", line 109, in _updateEngine
self._layoutEngine.setFeatureTables(gdef, gsub, gpos)
File "c:\users\admin\downloads\compositor\lib\compositor\layoutEngine.py", line 34, in setFeatureTables
self.gdef = GDEF().loadFromFontTools(gdef)
File "c:\users\admin\downloads\compositor\lib\compositor\tables.py", line 358, in loadFromFontTools
self.GlyphClassDef = GlyphClassDef().loadFromFontTools(table.GlyphClassDef)
File "c:\users\admin\downloads\compositor\lib\compositor\classDefinitionTables.py", line 30, in loadFromFontTools
self.ClassFormat = classDef.Format
File "C:\Program Files\Python36\lib\site-packages\fontTools\ttLib\tables\otBase.py", line 539, in __getattr__
raise AttributeError(attr)
AttributeError: Format
@anthrotype Would you know how to resolve this?
The repository is here:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.