robotools / fontmath Goto Github PK
View Code? Open in Web Editor NEWA collection of objects that implement fast font, glyph, etc. math.
License: MIT License
A collection of objects that implement fast font, glyph, etc. math.
License: MIT License
that must be filterReduntantPoints
https://github.com/typesupply/fontMath/blob/master/Lib/fontMath/mathGlyph.py#L291
(I can do a PR but for tonight Im to lazy, sorry :)
I came in to a situation where I needed to translate
a mathGlyph
if there is interest I can add the full translate as a PR
see https://gist.github.com/typemytype/52de64953b7486ae9090f19525e22c9b
I get this mathFunctions
module error when trying to use MutatorMath.
File "/usr/local/lib/python2.7/dist-packages/mutatorMath/ufo/instance.py", line 8, in <module> from fontMath.mathKerning import MathKerning File "/usr/local/lib/python2.7/dist-packages/fontMath/mathKerning.py", line 3, in <module> from mathFunctions import add, sub, mul, div ImportError: No module named mathFunctions
I could correct this issue by changing the import statement inmathKerning.py
and mathInfo.py
in to:
from fontMath.mathFunctions import add, sub, mul, div
I tried this Ubuntu 32 bit with Python 2.7
Could you add add build-backend to pyproject.toml?
This test is failing. The result is,
('curve', (0, 50), False, 'name 1', 'point 1'),
(None, (0, 75), False, None, None),
(None, (25, 100), False, None, None),
(None, (25, 100), False, None, None), <--- line 4
(None, (50, 100), False, None, None), <--- line 5
('curve', (50, 100), True, 'name 2', 'point 2'),
(None, (75, 100), False, None, None),
(None, (100, 75), False, None, None),
('curve', (100, 50), True, 'name 3', 'point 3'),
(None, (100, 25), False, None, None),
(None, (75, 0), False, None, None),
('curve', (50, 0), False, 'name 4', 'point 4'),
(None, (25, 0), False, None, None),
(None, (0, 25), False, None, None)
Lines 4 and 5 are what's different from the expected result.
I tried with both RoboFab's master
and ufo3k
branches. The outcome was the same.
Can someone else confirm this?
Followup to #27.
I came across a situation where depending on the start point, FilterRedundantPointPen
is not able to catch redundant offcurve points.
In a glyph like the one here, the offcurve points stay in place after running the pen.
<?xml version='1.0' encoding='UTF-8'?>
<glyph name="C" format="2">
<advance width="500"/>
<outline>
<contour>
<point x="298" y="132"/>
<point x="298" y="80"/>
<point x="298" y="80" type="curve"/>
<point x="600" y="80" type="line"/>
<point x="600" y="132" type="line"/>
<point x="298" y="132" type="line"/>
</contour>
</outline>
</glyph>
In the case below, FilterRedundantPointPen
works as expected by getting rid of the 2 points without a type.
<?xml version='1.0' encoding='UTF-8'?>
<glyph name="C" format="2">
<advance width="500"/>
<outline>
<contour>
<point x="298" y="80" type="curve"/>
<point x="600" y="80" type="line"/>
<point x="600" y="132" type="line"/>
<point x="298" y="132" type="line"/>
<point x="298" y="132"/>
<point x="298" y="80"/>
</contour>
</outline>
</glyph>
During the kerning extraction process, loop through the kerning and catch any conflicting pairs. Either toss or average the pairs to eliminate the conflict. This is not a 100% correct solution to the problem, but there is no way to algorithmically resolve this problem. However, sending out conflicting pairs is going to result in data that is 100% in conflict, so we need to do something about it.
Just remove it. It does not make sense from a mathematical point of view (2 / 4 = 2?!) and I cannot imagine any use for it.
It is currently not interpolated.
It's not clear to me what the purpose of test_mathTransform.py::MathTransformTest.test_functions
is.
It seems to assert that at least for some of the _testData
, the three testFunctions
yield slightly different results.
However:
Another problem is that since the interpolation value is random.random()
, sometimes the test can unpredictably fail (i.e. FontMathWarning
is never raised, all results are same between functions), for example:
https://travis-ci.org/typesupply/fontMath/jobs/172252357#L356
I think we need to narrow this test down a little bit, though I'm not sure how.
Maybe @typesupply or @moyogo can shed some light?
def test_functions(self):
"""
In this test various complex transformations are interpolated using
3 different methods:
- straight linear interpolation, the way glyphMath does it now.
- using the MathTransform interpolation method.
- using the ShallowTransform with an initial decompose and final
compose.
"""
value = random()
testFunctions = [
_polarDecomposeInterpolationTransformation,
_mathPolarDecomposeInterpolationTransformation,
_linearInterpolationTransformMatrix,
]
with self.assertRaisesRegex(
FontMathWarning,
"Minor differences occured when "
"comparing the interpolation functions."):
for i, m in enumerate(_testData):
m1, m2 = m
results = []
for func in testFunctions:
r = func(m1, m2, value)
results.append(r)
if not results[0] == results[1]:
raise FontMathWarning(
"Minor differences occured when "
"comparing the interpolation functions.")
While it might solve short term compatibility problems, it really makes it difficult to prepare outlines for variable fonts.
I propose MathGlyphPen() gets a flag, something like addOffCurves=False
. Then line 373 can be conditional.
This is an interesting problem with mathInfo objects when processing attributes that do not exist in the other object. This can happen when one of the masters has more values set than the other. For instance when preparing the "deltas", the objects with relative data
Bottom line is: when c = a - b
, then also a = b + c
, Currently this is not the case.
This is a proposed fix: https://github.com/typesupply/fontMath/tree/fix-mathinfo
This is a test. Best to see with and without the proposed fix.
from fontParts.world import RFont
from fontMath import MathInfo
import fontMath
print(fontMath.__file__)
# master 1
f1 = RFont()
f1.info.ascender = 800 # present in both masters
f1.info.descender = -200 # present in both masters
f1.info.openTypeHheaAscender = 500 # example value that won't be in master 2
m1 = f1.info.toMathInfo()
f2 = RFont()
f2.info.ascender = 750 # present in both masters
f2.info.descender = -180 # present in both masters
f2.info.openTypeOS2TypoAscender = 300 # example value that won't be in master 1
m2 = f2.info.toMathInfo()
# subtract m2 from m1
m3 = m1 - m2
# extract to new font
f3 = RFont()
f3.info.fromMathInfo(m3)
print("check:", f3.info.ascender)
# this is 800 - 750 = 50
assert f3.info.ascender == 50 # check
print("check:", f3.info.descender)
# this is -200 - -180 = -20
assert f3.info.descender == -20 # check
# here we have wrong results
print("unexpected:", f3.info.openTypeHheaAscender)
print("unexpected:", f3.info.openTypeOS2TypoAscender)
# if c = a - b
# then a = c + b
m4 = m3 + m2
f4 = RFont()
f4.info.fromMathInfo(m4)
print("openTypeHheaAscender m4:", f4.info.openTypeHheaAscender, f1.info.openTypeHheaAscender)
print("openTypeOS2TypoAscender m4:", f4.info.openTypeOS2TypoAscender, f1.info.openTypeOS2TypoAscender)
# when a value is not present in the other master
# the delta should be 0, no change,
# maybe it should be like this:
# <value a> - <unknown b> = 0
# <value a> + <unknown b> = <value a>
# <unknown a> - <value b> = 0
# <unknown a> + <value b> = <value a>
# scalar * <unknown b> = <unknown b>
# <unknown b> / factor = <unknown b>
# This works in mutatormath as it started with the actual default object
# but varlib starts with the esult of 1.0 * default object.
#
Hi fontMath
team. I am having trouble with a variable font project that uses stacked nodes to keep compatibility across masters. Tracing the root of the problem I have arrived here. I've also heard colleagues complaining about facing similar issues with their projects. Fiddling with the code I think I have found a civilized solution and I am offering it to you.
Fix utilizes simple name/tag technique to sort out the newly introduced nodes from MathGlyphPen
, thus leaving stacked nodes intentionally made by the type designer alone.
Problem first referred here: LettError/ufoProcessor#35
I believe my issue is similar to: #127
Please see the attached code: mathGlyph.zip
Does anybody fancy adding a short README text file at the top-level of the repository?
I would like to use that for the long_description
in setup.py
, so that it can also be used for the project homepage on PyPI.
If it is formatted in reStructuredText
instead of markdown it'd be even better, as currently PyPI cannot render .md files while Github can render .rst ones just fine.
It's easy to convert md to rst, e.g. with pandoc:
pandoc --from=markdown --to=rst --output=README.rst README.md
Seems like it is time.
See deprecation notices
Say you have two Info objects, a
and b
; in a
, the postscriptFamilyBlues
is some list of integers, whereas in b
object, that attribute is None
(not an empty list).
When subtracting a - b
, the resulting object will have the attribute set to 0
, because of this code here:
fontMath/Lib/fontMath/mathInfo.py
Lines 62 to 73 in a07bcc3
but that's invalid, because the type of postscriptFamilyBlues attribute must be an Optional[list], not int.
I don't understand the rationale of special-casing a subtrahend value of None. For the "add" function it simply sets the value to self (i.e. adding None doesn't do anything). Why for subtraction it would be any different?
All in _processMathOne(). Two major issues I see:
This is problematic, as varLib uses otRound
, so static instances do not exactly match their cousins inside a variable font made with varLib. In testing this with Cantarell, I found a large number of off-by-ones between the two rounding methods.
Simply swapping the methods out doesn't work, as round3()
takes an ndigits
argument, while otRound
doesn't. Maybe as a compromise, round3()
can be used when there is an ndigits
argument, otherwise otRound
? Can provide PR.
Edit: although, varLib is not the only possible user. I haven't looked at mutatorMath.
Right now the mutatorMath InstanceWriter cannot properly generate glyphs on Python 3 because MathGlyph is missing the required __lt__
and __eq__
methods (see also LettError/MutatorMath#65 for a similar issue). Here we have the current __cmp__
:
I patched my local copy of mathGlyph.py with a simple:
def __eq__(self, other):
self.__cmp__(other)
def __lt__(self, other):
self.__cmp__(other)
This all works fine, and mutatorMath is happy. I wonder what the right behavior for a “proper” __lt__
would be, though, if any.
raise FontPartsError("no setter for %r" % self.name)
fontParts.base.errors.FontPartsError: no setter for 'anchors'
there is a difference between fontParts and defcon
in defcon a guideline is actually a dict and fontMath iterates over all the keywords
This is not possible in fontParts...
g = CurrentGlyph()
for key in g.naked().guidelines[0]:
print(key)
for key in g.guidelines[0]:
print(key)
x
y
angle
identifier
Traceback (most recent call last):
File "<untitled>", line 5, in <module>
TypeError: 'RGuideline' object is not iterable
_processMathTwo() does not copy the data of MathKerning.groups
which leads to wrong assumptions in cleanup
and guessPairType
, which can result in exceptions getting lost.
A small test is here:
from fontMath.mathKerning import MathKerning
from defcon.objects.font import Font
f = Font()
f.groups["public.kern1.groupA"] = ['one', 'Bee']
f.groups["public.kern2.groupB"] = ['two', 'Three']
f.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100
m = MathKerning(f.kerning, f.groups)
print(m.groups())
print((m*1.0).groups())
>>> {'public.kern1.groupA': ['one', 'Bee'], 'public.kern2.groupB': ['two', 'Three']}
>>> {}
A bigger test with related issue is here.
I would like to scale a UFO's unitsPerEm, while keeping the font the same by scaling everything else at the same time.
I thought I could simply use fontMath since it already implements __mul__
on info, kerning and glyph objects.
Here's what I have:
https://gist.github.com/anthrotype/62d0bfe1d38b8f11a199bd3b66574bcc
Now the problem is the components' xScale
and yScale
are also scaled by the given factor, which is not what I was expecting. Say they are set to 1.0 in the input font, if I multiply the glyphs by a factor or 2, I get the component scale set to 2.0. The simple contour glyphs have also been scaled by the same amount already, I don't want the components to be scaled again.
I was thinking we may add an option in MathGlyph constructor to only scale the component's offsets and exclude the other scale/shear attributes?
A FontParts font.info object does not have a guidelines attribute.
https://github.com/typesupply/fontMath/blob/master/Lib/fontMath/mathInfo.py#L17
MathGlyph objects (and maybe others) need to test if the object is from defcon or fontParts.
As an example, creating a new MathGlyph from a fontParts glyph fails here:
https://github.com/typesupply/fontMath/blob/master/Lib/fontMath/mathGlyph.py#L715
Here's a test:
from fontMath.mathGlyph import _expandImage
from fontParts.nonelab import RFont
f = RFont()
f.newGlyph("A")
g = f["A"]
print type(g.image)
try:
print _expandImage(g.image)
except TypeError:
print "meh1"
from defcon.objects.font import Font as DFont
f = DFont()
f.newGlyph("A")
g = f["A"]
print type(g.image)
try:
print _expandImage(g.image)
except TypeError:
print "meh2"
<class 'fontParts.nonelab.image.RImage'>
meh1
<class 'defcon.objects.image.Image'>
{'color': None, 'transformation': (1, 0, 0, 1, 0, 0), 'fileName': None}
Hello,
I'm currently packaging fontMath for Alpine Linux and the mentioned tests fails. Here's the output:
___________________________________________________________________________________________________ MathGlyphTest.test_guidelines_mul ___________________________________________________________________________________________________
self = <fontMath.test.test_mathGlyph.MathGlyphTest testMethod=test_guidelines_mul>
def test_guidelines_mul(self):
glyph1 = self._setupTestGlyph()
glyph1.guidelines = [
dict(x=1, y=3, angle=5, name="test", identifier="1",
color="0,0,0,0")
]
glyph2 = glyph1 * 3
expected = [
dict(x=1 * 3, y=3 * 3, angle=5, name="test", identifier="1",
color="0,0,0,0")
]
> self.assertEqual(glyph2.guidelines, expected)
E AssertionError: Lists differ: [{'x'[17 chars]le': 4.999999999999999, 'name': 'test', 'ident[28 chars],0'}] != [{'x'[17 chars]le': 5, 'name': 'test', 'identifier': '1', 'color': '0,0,0,0'}]
E
E First differing element 0:
E {'x':[16 chars]le': 4.999999999999999, 'name': 'test', 'ident[27 chars]0,0'}
E {'x':[16 chars]le': 5, 'name': 'test', 'identifier': '1', 'color': '0,0,0,0'}
E
E - [{'angle': 4.999999999999999,
E + [{'angle': 5,
E 'color': '0,0,0,0',
E 'identifier': '1',
E 'name': 'test',
E 'x': 3,
E 'y': 9}]
Lib/fontMath/test/test_mathGlyph.py:398: AssertionError
We're using Python 3.8.1
I filed this issue in the wrong repo:
LettError/MutatorMath#259
Copy-pasting the original issue below.
Please follow the link above for some thoughts by @LettError.
First pointed out by @kaibernau:
An anisotropic interpolation will result in an unexpected Italic angle for the instance UFOs.
Anisotropic interpolation basically splits the x- and y- axis by allowing different factors for each.
This is how to indicate it for a given instance:
<dimension name="weight" xvalue="57" yvalue="52"/>
In the attached project, the Italic angle for both masters is -11°.
Using anisotropic interpolation, the italic angle for the instance ends up as -90°.
This is true for any value of y
, including 0
and = x
I reproduced Kai’s finding using the AFDKO’s makeinstancesufo
:
makeinstancesufo -ac -d designspace.designspace
# -ac switches off autohint/checkoutlinesufo
Hi there,
We noticed the fontMath 0.8.1 update breaks some of our tests in the AFDKO, specifically in our makeinstancesufo
tool. It seems to be tied to these changes in factorAngle. The new italicAngle values after this update are concerning to us; we're curious what the purpose of this change was.
Here's an example:
files here:
input.zip
makeinstancesufo -d font.designspace -i 5 -n
Running that will produce a bold.ufo
instance
The italicAngle value here was previously 2
, now it's outputting 1.4430379746835442
:
<key>italicAngle</key>
- <integer>2</integer>
+ <real>1.4430379746835442</real>
<key>openTypeHheaAscender</key>
italicAngle value changes within our other tests were all similar:
<key>italicAngle</key>
- <integer>2</integer>
+ <real>0.082278481</real>
<key>openTypeHheaAscender</key>
<key>italicAngle</key>
- <integer>2</integer>
+ <real>0.7341772152</real>
<key>openTypeHheaAscender</key>
<key>italicAngle</key>
- <integer>2</integer>
+ <integer>0</integer>
<key>openTypeHheaAscender</key>
<key>italicAngle</key>
- <integer>-180</integer>
+ <integer>0</integer>
<key>postscriptBlueFuzz</key>
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.