Last active
June 29, 2025 08:16
Revisions
-
LettError revised this gist
Jun 29, 2025 . 1 changed file with 31 additions and 14 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,11 +1,14 @@ # coding: utf-8 # menuTitle : Guide at Slant Angle Tangent # shortCut : command+shift+f # [email protected] # June 2025 # version 1.1 # For robofont # Dedicated to my github sponsors who encourate explorations like this.z # this can find the t for horizontals in a cubic bezier segment. # this can find the tangent at any angle (by rotating the segment and finding horizontals) @@ -64,6 +67,7 @@ def add(p1, p2): def findHorizontals(glyph): results = [] res = 0.01 for contourIndex, c in enumerate(glyph.contours): bps = c.bPoints l = len(bps) @@ -82,6 +86,11 @@ def findHorizontals(glyph): elif 0 < value < 1: cpt = cubicPointAtT(s[0], s[1], s[2], s[3], value) results.append((contourIndex, bPointIndex, value)) elif -res < value < 1+res: # this catches values that are very very close # to 0 or 1 but on the outer edge cpt = cubicPointAtT(s[0], s[1], s[2], s[3], value) results.append((contourIndex, bPointIndex, value)) else: pass return results @@ -102,7 +111,7 @@ def findTangentInGlyph(glyph, angle): points = [] results = findHorizontals(g2) for contourIndex, bPointIndex, value in results: pt = findPointFromT(glyph, contourIndex, bPointIndex, value) points.append(pt) return points @@ -117,11 +126,18 @@ def nearSelected(glyph, pt, dst=100): if len(near) > 0: return True return False def recenterGuide(pt, angle, d=300): x, y = pt dx = math.cos(radians(angle)) * d dy = math.sin(radians(angle)) * d return x+dx, y+dy # - - - glyph = CurrentGlyph() #glyph = glyph.getLayer("foreground") # this cleans guides with names that start with "angled_" guideNamePrefix = "angled_" @@ -135,19 +151,20 @@ def nearSelected(glyph, pt, dst=100): # this can be any other angle of course # in case someone wants to write a UI for it. fontItalicAngle = glyph.font.info.italicAngle if fontItalicAngle is None: fontItalicAngle = 0 # add guides for candidate tangents near selected points in the glyph # or all candidates if there is no selection for p in findTangentInGlyph(glyph, fontItalicAngle): if nearSelected(glyph, p): glyph.appendGuideline(recenterGuide(p, fontItalicAngle), fontItalicAngle, color=(1,.5,0,1), name=f"{guideNamePrefix}{fontItalicAngle:3.3f}") glyph.appendGuideline(recenterGuide(p, fontItalicAngle-90), fontItalicAngle-90, color=(1,.25,0,1), name=f"{guideNamePrefix}_ortho_{fontItalicAngle:3.3f}") # this adds guides on tangents +90 from the given angle. # maybe not what you need. Easy to remove. for p in findTangentInGlyph(glyph, fontItalicAngle + 90): if nearSelected(glyph, p): glyph.appendGuideline(p, fontItalicAngle, color=(0,.5,1,1), name=f"angled_{fontItalicAngle:3.3f}") glyph.appendGuideline(p, fontItalicAngle-90, color=(1,.25,1,1), name=f"angled_ortho_{fontItalicAngle:3.3f}") -
LettError revised this gist
Apr 13, 2024 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,7 +4,8 @@ # April 2024 # version 1 # For robofont # Dedicated to my github sponsors who encourate explorations like this. # this can find the t for horizontals in a cubic bezier segment. # this can find the tangent at any angle (by rotating the segment and finding horizontals) -
LettError created this gist
Apr 13, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,152 @@ # [email protected] # April 2024 # version 1 # for robofont # this can find the t for horizontals in a cubic bezier segment. # this can find the tangent at any angle (by rotating the segment and finding horizontals) # this adds guides to the current glyph at the font's italic angle # .. near selected points. To prevent a mass of guidelines to be added. # to do: find origins for the guides that will give the same line # but don't have the guide label so near the tangent point. # some of the reasoning: # the function of the tangent is the derivative of the cubic. # https://math.stackexchange.com/questions/477165/find-angle-at-point-on-bezier-curve # 𝐏′(𝑡)=(1−𝑡)^2(𝐏1−𝐏0)+2𝑡(1−𝑡)(𝐏2−𝐏1)+𝑡^2(𝐏3−𝐏2) # code should be similar for px = 0 # solve t for py = 0 # this is my solution, it seems to work. #py = (1-t)**2 * a + 2*t*(1-t) * b + t**2 * c #py = (1-t)(1-t) * a + 2*t*(1-t) * b + t**2 * c #py = (1 -2*t + t**2) * a + (2 * t -2 * t**2) * b + c*t**2 #py = a - 2*a*t + a*t**2 + 2 * b * t - 2 * b * t**2 + c*t**2 #py = a*t**2 -2*b*t**2 + c*t**2 - 2*a*t + 2 * b * t + a #py = (a - 2*b + c)*t**2 + (2*b - 2*a) * t + a import math from math import degrees, atan2, sin, cos, pi, radians, degrees from random import random from fontTools.misc.bezierTools import cubicPointAtT, solveQuadratic def tangentAtT(p0, p1, p2, p3, t): # not used in this script, but kept for reference # get the tangent point at t a = (1-t)**2 b = 2*t*(1-t) c = t**2 px = a * (p1[0]-p0[0]) + b*(p2[0]-p1[0]) + c*(p3[0]-p2[0]) py = a * (p1[1]-p0[1]) + b*(p2[1]-p1[1]) + c*(p3[1]-p2[1]) #return px + p0[0], py + p0[1] return px, py def t_for_horizontal(p0, p1, p2, p3, horizontal=True): if horizontal: i = 1 else: i = 0 a = (p1[i]-p0[i]) b = (p2[i]-p1[i]) c = (p3[i]-p2[i]) # solve the quadratic r = solveQuadratic((a - 2*b + c), (2*b - 2*a), a) return r def add(p1, p2): return p1[0]+p2[0],p1[1]+p2[1] def findHorizontals(glyph): results = [] for contourIndex, c in enumerate(glyph.contours): bps = c.bPoints l = len(bps) for bPointIndex, bp1 in enumerate(bps): bp2 = bps[(bPointIndex+1)%l] if bp1.type != "curve" and bp2.type != "curve": continue # so the segment we're interested in will bp1.anchor, bp1.out, bp2.in, bp2.anchhor s = (bp1.anchor, add(bp1.anchor,bp1.bcpOut), add(bp2.anchor,bp2.bcpIn), bp2.anchor) r = t_for_horizontal(s[0], s[1], s[2], s[3], 1) for value in r: # this does not catch everything -- if value == 0: results.append((contourIndex, bPointIndex, 0)) elif value == 1: results.append((contourIndex, bPointIndex, 1 )) elif 0 < value < 1: cpt = cubicPointAtT(s[0], s[1], s[2], s[3], value) results.append((contourIndex, bPointIndex, value)) else: pass return results def findPointFromT(glyph, contourIndex, bPointIndex, value): bps = glyph.contours[contourIndex].bPoints bp1 = bps[bPointIndex] bp2 = bps[(bPointIndex + 1)%len( bps)] s = (bp1.anchor, add(bp1.anchor,bp1.bcpOut), add(bp2.anchor,bp2.bcpIn), bp2.anchor) cpt = cubicPointAtT(s[0], s[1], s[2], s[3], value) return cpt def findTangentInGlyph(glyph, angle): # pay attention, from robofont slant angle angle = -(angle - 90) g2 = glyph.copy() g2.rotate(angle) points = [] results = findHorizontals(g2) for contourIndex, bPointIndex, value in results: pt = findPointFromT(g, contourIndex, bPointIndex, value) points.append(pt) return points def nearSelected(glyph, pt, dst=100): # if the point is close to a currently selected point near = [] if len(glyph.selectedPoints) == 0: return True for p in glyph.selectedPoints: if math.hypot(p.x-pt[0],p.y-pt[1]) <= dst: near.append(pt) if len(near) > 0: return True return False # - - - g = CurrentGlyph() glyph = g.getLayer("foreground") # this cleans guides with names that start with "angled_" guideNamePrefix = "angled_" remove = [] for guide in glyph.guidelines: if guide.name is not None: if guideNamePrefix in guide.name: remove.append(guide) for guide in remove: glyph.removeGuideline(guide) # this can be any other angle of course # in case someone wants to write a UI for it. angle = g.font.info.italicAngle # add guides for candidate tangents near selected points in the glyph # or all candidates if there is no selection for p in findTangentInGlyph(g, angle): if nearSelected(glyph, p): glyph.appendGuideline(p, angle, color=(1,.5,0,1), name=f"{guideNamePrefix}{angle:3.3f}") glyph.appendGuideline(p, angle-90, color=(1,.25,0,1), name=f"{guideNamePrefix}_ortho_{angle:3.3f}") # this adds guides on tangents +90 from the given angle. # maybe not what you need. Easy to remove. for p in findTangentInGlyph(g, angle + 90): if nearSelected(glyph, p): glyph.appendGuideline(p, angle, color=(0,.5,1,1), name=f"angled_{angle:3.3f}") glyph.appendGuideline(p, angle-90, color=(1,.25,1,1), name=f"angled_ortho_{angle:3.3f}")