-
-
Save ryanbugden/20e2b4296ad5202eb4c10931d165c72c to your computer and use it in GitHub Desktop.
Align points, with addition for if there's an italic angle.
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 characters
| # menuTitle: Align Points | |
| from fontTools.misc.fixedTools import otRound | |
| import math | |
| ''' | |
| This script aligns selected points intelligently. | |
| It will look at the x-coordinate offset and | |
| the y-coordinate offset and align according to | |
| whichever one is smaller. It will also make | |
| an educated guess as to which direction you'd | |
| like to align it. | |
| Requires Robofont 3.2+ | |
| Ryan Bugden | |
| 2019.03.09 | |
| 2023.04.20 - Make it smarter | |
| with thanks to Frank Griesshammer for the idea | |
| ''' | |
| def avg_list(l): | |
| # Average the x and y lists independently | |
| return int(sum(l) / len(l)) | |
| g = CurrentGlyph() | |
| f = g.font | |
| if g.selectedPoints: | |
| x_ind = [p.x for p in g.selectedPoints] | |
| y_ind = [p.y for p in g.selectedPoints] | |
| max_x = max(x_ind) | |
| mid_x = avg_list(x_ind) | |
| min_x = min(x_ind) | |
| max_y = max(y_ind) | |
| mid_y = avg_list(y_ind) | |
| min_y = min(y_ind) | |
| def get_adj_pts_that_are_offcurve(point_index): | |
| adjacents = [point_index-1, point_index+1] | |
| l = [] | |
| for pt_i in adjacents: | |
| # Will avoid errors with first/last point indexes | |
| try: | |
| p.contour._getPoint(pt_i) | |
| except IndexError: | |
| continue | |
| if p.contour.points[pt_i].type == 'offcurve': | |
| l.append(pt_i) | |
| return l | |
| def get_x_align_vert(x, y): | |
| italic = 0 | |
| if f.info.italicAngle: | |
| italic = f.info.italicAngle | |
| y_off = otRound(y - mid_y) # Vertical offset | |
| x_off = otRound(math.tan(math.radians(-italic)) * y_off) # x-offset for if you have an italic angle | |
| x_align = mid_x + x_off | |
| return x_align | |
| def get_x_align_horiz(x, y): | |
| italic = 0 | |
| if f.info.italicAngle: | |
| italic = f.info.italicAngle | |
| y_off = otRound(mid_y - y) # Vertical offset | |
| x_off = otRound(math.tan(math.radians(-italic)) * y_off) # x-offset for if you have an italic angle | |
| x_align = x + x_off | |
| return x_align | |
| def check_which_alignment(): | |
| # Find the width of the selection and height of the selection independently | |
| x_range = max_x - get_x_align_vert(max_x, max_y) | |
| y_range = max_y - mid_y | |
| if x_range > y_range: | |
| return 'horizontal' | |
| else: | |
| return 'vertical' | |
| debug = True | |
| # Only works if there is a point selection | |
| if g.selectedPoints: | |
| with g.undo("Align Points"): | |
| # Threshold to determine whether to move off-curves drastically or not. | |
| ocp_dist_threshold = 1 | |
| # If the points are closer together horizontally, align vertically. | |
| if check_which_alignment() == 'vertical': | |
| for p in g.selectedPoints: | |
| p_i = p._get_index() | |
| x_align = get_x_align_vert(p.x, p.y) | |
| x_delta = x_align - p.x | |
| p.x = x_align | |
| if debug == True: | |
| print(1) | |
| # If all of the selection is offcurves, just stop here. | |
| if list(set([p.type for p in g.selectedPoints])) == ['offcurve']: | |
| continue | |
| # Otherwise, don't forget off-curves. | |
| for ocp_i in get_adj_pts_that_are_offcurve(p_i): | |
| compare_p = p.contour.points[p_i] | |
| ocp = p.contour.points[ocp_i] | |
| ocp_dist_x = abs(ocp.x - p.x) | |
| ocp_dist_y = abs(ocp.y - p.y) | |
| # If the point is close enough, it will snap to the alignment average. | |
| if compare_p.x - ocp_dist_threshold < ocp.x < compare_p.x + ocp_dist_threshold: | |
| ocp.x = x_align | |
| if debug == True: | |
| print(2) | |
| # If it's a smooth point and the handle isn't parallel to the alignment direction, the off-curve will snap to the alignment average. | |
| elif p.smooth == True and ocp.y != p.y and ocp_dist_x < ocp_dist_y: # and ocp.y < p.y - ocp_dist_threshold: | |
| ocp.x = get_x_align_vert(ocp.x, ocp.y) | |
| print("ocp.index", ocp.index) | |
| print("p_i", p_i) | |
| if debug == True: | |
| print(3) | |
| # Otherwise, the off-curve-to-on-curve relationship will be maintained | |
| else: | |
| ocp.x += x_delta | |
| if debug == True: | |
| print(4) | |
| # Same-ish for horizontal | |
| else: | |
| for p in g.selectedPoints: | |
| p_i = p._get_index() | |
| x_align = get_x_align_horiz(p.x, p.y) | |
| y_align = mid_y | |
| x_delta, y_delta = x_align - p.x, y_align - p.y | |
| p.x, p.y = x_align, y_align | |
| if debug == True: | |
| print(5) | |
| # If all of the selection is offcurves, just stop here. | |
| if list(set([p.type for p in g.selectedPoints])) == ['offcurve']: | |
| continue | |
| # Otherwise, don't forget off-curves. | |
| for ocp_i in get_adj_pts_that_are_offcurve(p_i): | |
| compare_p = p.contour.points[p_i] | |
| ocp = p.contour.points[ocp_i] | |
| ocp_dist_x = abs(ocp.x - p.x) | |
| ocp_dist_y = abs(ocp.y - p.y) | |
| if compare_p.y - ocp_dist_threshold < ocp.y < compare_p.y + ocp_dist_threshold: | |
| ocp.y = y_align | |
| if debug == True: | |
| print(6) | |
| # Make the following capture smooth off-curve more effectively. Currently only skips those that are perfectly, vertically above | |
| elif p.smooth == True and ocp.x != p.x and ocp_dist_y < ocp_dist_x: | |
| ocp.x = get_x_align_horiz(ocp.x, ocp.y) | |
| ocp.y = y_align | |
| if debug == True: | |
| print(7) | |
| else: | |
| ocp.x += x_delta | |
| ocp.y += y_delta | |
| if debug == True: | |
| print(8) | |
| # Immediately reflect the changes in glyph view. | |
| g.changed() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment