Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close open corners in UFO #663

Merged
merged 17 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Lib/glyphsLib/builder/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import logging

from .common import to_ufo_time, from_ufo_time
from .constants import GLYPHS_PREFIX
from .constants import GLYPHS_PREFIX, UFO2FT_FILTERS_KEY

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -82,11 +82,17 @@ def to_ufo_font_attributes(self, family_name):

self.to_ufo_names(ufo, master, family_name)
self.to_ufo_family_user_data(ufo)

ufo.lib[UFO2FT_FILTERS_KEY] = [
simoncozens marked this conversation as resolved.
Show resolved Hide resolved
{"namespace": "glyphsLib.filters", "name": "eraseOpenCorners", "pre": True}
]

self.to_ufo_custom_params(ufo, font)

self.to_ufo_master_attributes(source, master)

ufo.lib[MASTER_ORDER_LIB_KEY] = index

# FIXME: (jany) in the future, yield this UFO (for memory, lazy iter)
self._designspace.addSource(source)
self._sources[master.id] = source
Expand Down
Empty file.
118 changes: 118 additions & 0 deletions Lib/glyphsLib/filters/eraseOpenCorners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import logging

from fontTools.pens.basePen import BasePen
from fontTools.misc.bezierTools import (
segmentSegmentIntersections,
_split_segment_at_t,
)
from ufo2ft.filters import BaseFilter

logger = logging.getLogger(__name__)


def _pointIsLeftOfLine(line, aPoint):
a, b = line
return (
(b[0] - a[0]) * (aPoint[1] - a[1]) - (b[1] - a[1]) * (aPoint[0] - a[0])
) >= 0


class EraseOpenCornersPen(BasePen):
simoncozens marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, outpen):
self.segments = []
self.is_closed = False
self.affected = False
self.outpen = outpen

def _moveTo(self, p1):
if self.segments or self.is_closed:
raise ValueError(
"EraseOpenCornersPen should only be used on single contours"
)

def _operate(self, *points):
self.segments.append((self._getCurrentPoint(), *points))

_qCurveTo = _curveTo = _lineTo = _qCurveToOne = _curveToOne = _operate

def closePath(self):
self.segments.append((self._getCurrentPoint(), self.segments[0][0]))
self.is_closed = True
self.endPath()

def endPath(self):
segs = self.segments
if not segs:
return

ix = 0
while ix < len(segs):
next_ix = (ix + 1) % len(segs)

# Am I a line segment?
if not len(segs[ix]) == 2:
ix = ix + 1
continue

# Are the first point of the previous segment and the last point
# of the next segment both on the right side of the line?
# (see discussion at https://github.com/googlefonts/glyphsLib/pull/663)
pt1 = segs[ix - 1][0]
pt2 = segs[next_ix][-1]
intersection = [
i
for i in segmentSegmentIntersections(segs[ix - 1], segs[next_ix])
if 0 <= i.t1 <= 1 and 0 <= i.t2 <= 1
]
if (
not intersection
or _pointIsLeftOfLine(segs[ix], pt1)
or _pointIsLeftOfLine(segs[ix], pt2)
):
ix = ix + 1
continue

if intersection and (intersection[0].t1 > 0.5 and intersection[0].t2 < 0.5):
(segs[ix - 1], _) = _split_segment_at_t(
segs[ix - 1], intersection[0].t1
)
(_, segs[next_ix]) = _split_segment_at_t(
segs[next_ix], intersection[0].t2
)
# Ensure the ends match up
segs[next_ix] = (segs[ix - 1][-1],) + segs[next_ix][1:]
segs[ix : ix + 1] = []
self.affected = True
ix = ix + 1

self.outpen.moveTo(segs[0][0])

for seg in segs:
if len(seg) == 2:
self.outpen.lineTo(*seg[1:])
elif len(seg) == 3:
self.outpen.qCurveTo(*seg[1:])
elif len(seg) == 4:
self.outpen.curveTo(*seg[1:])

if self.is_closed:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think self.is_closed should be reset at the end, before a new contour is drawn with the same pen, otherwise it stays True

self.outpen.closePath()
else:
self.outpen.endPath()


class EraseOpenCornersFilter(BaseFilter):
def filter(self, glyph):
if not len(glyph):
return False

affected = False
contours = list(glyph)
glyph.clearContours()
outpen = glyph.getPen()
for contour in contours:
p = EraseOpenCornersPen(outpen)
anthrotype marked this conversation as resolved.
Show resolved Hide resolved
contour.draw(p)
if p.affected:
affected = True
return affected
1 change: 1 addition & 0 deletions requirements-dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ xmldiff>=2.2
# extras
ufoNormalizer>=0.3.2
defcon>=0.6.0
ufo2ft
30 changes: 20 additions & 10 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,41 @@
#
# pip-compile requirements-dev.in
#

appdirs==1.4.4
# via fs
atomicwrites==1.4.0
# via pytest
attrs==20.3.0
# via pytest
colorama==0.4.4
# via pytest
coverage==5.4
booleanoperations==0.9.0
# via ufo2ft
compreffor==0.5.1
# via ufo2ft
coverage==5.5
# via -r requirements-dev.in
cu2qu==1.6.7
# via ufo2ft
defcon==0.7.2
# via -r requirements-dev.in
fonttools[ufo,unicode]==4.19.0
# via defcon
fonttools[ufo,unicode]==4.21.1
# via
# booleanoperations
# compreffor
# cu2qu
# defcon
# ufo2ft
fs==2.4.12
# via fonttools
iniconfig==1.1.1
# via pytest
lxml==4.6.2
# via xmldiff
packaging==20.8
packaging==20.9
# via pytest
pluggy==0.13.1
# via pytest
py==1.10.0
# via pytest
pyclipper==1.2.1
# via booleanoperations
pyparsing==2.4.7
# via packaging
pytest-randomly==3.5.0
Expand All @@ -39,14 +47,16 @@ pytest==6.2.2
# via
# -r requirements-dev.in
# pytest-randomly
pytz==2020.5
pytz==2021.1
# via fs
six==1.15.0
# via
# fs
# xmldiff
toml==0.10.2
# via pytest
ufo2ft==2.19.2
# via -r requirements-dev.in
ufonormalizer==0.5.3
# via -r requirements-dev.in
unicodedata2==13.0.0.post2
Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
#
# pip-compile setup.py
#

appdirs==1.4.4
# via fs
attrs==20.3.0
# via ufolib2
fonttools[ufo,unicode]==4.19.0
fonttools[ufo,unicode]==4.21.1
# via
# glyphsLib (setup.py)
# ufolib2
fs==2.4.12
# via fonttools
pytz==2020.5
pytz==2021.1
# via fs
six==1.15.0
# via fs
Expand Down
Loading