Skip to content

Instantly share code, notes, and snippets.

@mathieureguer
Last active October 6, 2021 23:16
Show Gist options
  • Save mathieureguer/6f98645003ab0d02fc72e6412f9eb6af to your computer and use it in GitHub Desktop.
Save mathieureguer/6f98645003ab0d02fc72e6412f9eb6af to your computer and use it in GitHub Desktop.
from mojo.UI import SetCurrentLayerByName
from mojo.subscriber import Subscriber, registerGlyphEditorSubscriber
from fontTools.ufoLib.pointPen import AbstractPointPen
import math
"""
This is *largely* based on the great work of Jackson Cavanaugh on AddOverlapp
and the awesome drag offset update by Ryan Bugden
It is *extremly* likely not the best way to use merz and subscriber :)
"""
# ----------------------------------------
DEFAULT_OFFSET = 30
# ----------------------------------------
def getLength(pt1, pt2):
x1, y1 = pt1
x2, y2 = pt2
return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
def pointOnACurve(curve, value):
(x1, y1), (cx1, cy1), (cx2, cy2), (x2, y2) = curve
dx = x1
cx = (cx1 - dx) * 3.0
bx = (cx2 - cx1) * 3.0 - cx
ax = x2 - dx - cx - bx
dy = y1
cy = (cy1 - dy) * 3.0
by = (cy2 - cy1) * 3.0 - cy
ay = y2 - dy - cy - by
mx = ax * (value)**3 + bx * (value)**2 + cx * (value) + dx
my = ay * (value)**3 + by * (value)**2 + cy * (value) + dy
return mx, my
class AddOverlapPointPen(AbstractPointPen):
def __init__(self, selectedPoints=[], offset=30):
self.offset = int(offset)
self.selectedPoints = selectedPoints
self._contours = []
self._components = []
def beginPath(self):
self._contours.append([])
self.firstSegment = None
self.prevOncurve = None
def addPoint(self, pt, segmentType=None, smooth=False, name=None, **kwargs):
data = dict(point=pt, segmentType=segmentType, smooth=smooth, name=name, kwargs=kwargs)
self._contours[-1].append(data)
def endPath(self):
pass
def addComponent(self, baseGlyphName, transformation):
pass
def _offset(self, pt1, pt2):
x1, y1 = pt1
x2, y2 = pt2
length = getLength((x1, y1), (x2, y2))
if length == 0:
return 0, 0
ox = (x2 - x1) / length * self.offset
oy = (y2 - y1) / length * self.offset
return int(round(ox)), int(round(oy))
def drawPoints(self, outpen):
for pointsData in self._contours:
if len(pointsData) == 1:
# ignore single movetos and anchors
continue
outpen.beginPath()
lenPointsData = len(pointsData)
for i, pointData in enumerate(pointsData):
currentPoint = pointData["point"]
addExtraPoint = None
if pointData["segmentType"] and pointData["point"] in self.selectedPoints:
prevPointData = pointsData[i - 1]
nextPointData = pointsData[(i + 1) % lenPointsData]
prevOffsetX, prevOffsetY = self._offset(prevPointData["point"], pointData["point"])
nextOffsetX, nextOffsetY = self._offset(pointData["point"], nextPointData["point"])
if (nextOffsetX, nextOffsetY) == (0, 0) and nextPointData["segmentType"] is None:
nextSegment = [
pointsData[(i + 3) % lenPointsData]["point"],
pointsData[(i + 2) % lenPointsData]["point"],
nextPointData["point"],
pointData["point"]
]
newPoint = pointOnACurve(nextSegment, 0.9)
nextOffsetX, nextOffsetY = self._offset(pointData["point"], newPoint)
addExtraPoint = currentPoint[0] - nextOffsetX, currentPoint[1] - nextOffsetY
if (prevOffsetX, prevOffsetY) == (0, 0) and prevPointData["segmentType"] is None:
prevSegment = [
pointsData[i - 3]["point"],
pointsData[i - 2]["point"],
prevPointData["point"],
pointData["point"]
]
newPoint = pointOnACurve(prevSegment, 0.9)
prevOffsetX, prevOffsetY = self._offset(newPoint, pointData["point"])
currentPoint = currentPoint[0] + prevOffsetX, currentPoint[1] + prevOffsetY
outpen.addPoint(currentPoint,
pointData["segmentType"],
pointData["smooth"],
pointData["name"],
**pointData["kwargs"]
)
if addExtraPoint:
outpen.addPoint(addExtraPoint, "line")
outpen.endPath()
for baseGlyphName, transformation in self._components:
outpen.addComponent(baseGlyphName, transformation)
# ----------------------------------------
class AddOverlap(Subscriber):
debug = True
def build(self):
self.initial_drag_x = None
self.glyph = CurrentGlyph()
glyphEditor = self.getGlyphEditor()
self.container = glyphEditor.extensionContainer(
identifier="com.jackson.ryan.mathieu.addoverlap",
location="background",
clear=True)
self.overlap_layer = self.container.appendPathSublayer(
fillColor=None,
strokeColor=(1, 0, 1, 1),
strokeWidth=2,
name="overlap_layer")
def destroy(self):
self.container.clearSublayers()
def glyphEditorDidKeyDown(self, info):
if info['deviceState']['keyDownWithoutModifiers'] == "v":
offset_value = self._calculate_drag_offset(info)
self.overlap_glyph = self._make_overlap_glyph(offset_value)
path = self.overlap_glyph.getRepresentation("merz.CGPath")
self.overlap_layer.setPath(path)
def glyphEditorDidKeyUp(self, info):
# !! there must be a better way here
if info['deviceState']['keyDownWithoutModifiers'] == "v" \
and info['deviceState']['optionDown'] == 0 \
and info['deviceState']['controlDown'] == 0 \
and info['deviceState']['commandDown'] == 0:
self.initial_drag_x = None
self.overlap_layer.setPath(None)
self.glyph.prepareUndo('Add Overlap')
self.glyph.clearContours()
self.overlap_glyph.draw(self.glyph.getPen())
self.glyph.performUndo()
def _calculate_drag_offset(self, info):
if self.initial_drag_x == None:
self.initial_drag_x = int(info['deviceState']['locationInWindow'].x)
current_drag_x = int(info['deviceState']['locationInWindow'].x)
return int((current_drag_x - self.initial_drag_x) / 2 + DEFAULT_OFFSET)
def _make_overlap_glyph(self, offset):
selected_pts = [(p.x, p.y) for p in self.glyph.selectedPoints]
overlap_pen = AddOverlapPointPen(selected_pts, offset)
self.glyph.drawPoints(overlap_pen)
# draw result into a ghost glyph
# !! there probably a smarter way to do this than creating a glyph object :)
ghost_glyph = RGlyph()
ghost_glyph_pen = ghost_glyph.getPointPen()
overlap_pen.drawPoints(ghost_glyph_pen)
return ghost_glyph
registerGlyphEditorSubscriber(AddOverlap)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment