A deeper bevel on lineJoin()?

  • @jo I'd love to see your code! Like I said, the way Crouwel used the corners was unrelated to the rules he used to construct the letterforms, so I could illustrate his structure without the beveled corners. But it would be nice in a presentation to show audiences the letters as he styled them.

  • @frederik Are all the import dependencies/paths in the outliner extension only going to make sense if I have RoboFont installed? Because I don't have RoboFont …

  • @mauricemeilleur I don’t know how Crouwel defined his rules. but to me the corner-rule seems like an integral part of the design.

    I had a look at my code and tried to tidy the worst bits. some of the angle calculations seem very inefficient but ¯\(ツ)/¯ here it is:

    EDIT 2018-05-28 changed the code to simplify some of the horrible trigonometry and there now is the sq_cap bool to have squared linecaps.

    # Draw a 'folded outline' between a set of points. 
    # TO DO: There should be an error message when two dots are closer to each other and the weight.
    # There should be a warning if the folding is further than the next points. 
    # There should be a warning for collinear points. 
    import math
    # Just a bunch of points
    p01 = (894, 334)
    p02 = (808, 86)
    p03 = (274, 792)
    p04 = (481, 920)
    p05 = (583, 730)
    p06 = (85, 430)
    p07 = (318, 100)
    p08 = (870, 600)
    p09 = (720, 690)
    points = [p01, p02, p03, p04, p05, p06, p07, p08, p09]
    def get_dist(p1, p2): return sqrt( (p2[0] - p1[0] )**2 + (p2[1] - p1[1])**2 )
    def calc_angle(p1, p2): return atan2((p2[1] - p1[1]), (p2[0] - p1[0]))
    def calc_delta(p1, p2, p3): 
        Returns the angle between three points.
        return  calc_angle(p2, p3) - calc_angle(p2, p1)
    def calc_offset(weight, p1, p2, p3):
        Returns the x and y "offset" to be added to the points of the polygon. 
        halfWeight = weight/2
        if p1 == p2:
            alpha = calc_angle(p1, p3)
            b = -sin(alpha) * halfWeight
            a =  cos(alpha) * halfWeight
        elif p2 == p3:
            alpha = calc_angle(p1, p3)
            b =  sin(alpha) * halfWeight
            a = -cos(alpha) * halfWeight
            alpha = calc_angle(p2, p3)
            delta = calc_delta(p1, p2, p3)
            xx = halfWeight / sin((delta - pi) / 2)
            delta_f = (2 * alpha - pi - delta ) / 2
            a, b = xx * sin(delta_f), xx * cos(delta_f)
        return a, b
    def wrap_line(p1, p2, p3, p4, weight):
        This draws a polygon around a line. Four points (x,y) and a weight parameter are required. 
        The polygon will be drawn around the two middle points.
        The first and last points define the angles.
        a, b = calc_offset(weight, p1, p2, p3)
        # extend first point
        x1b, y1b = p2[0] - b, p2[1] - a
        x1c, y1c = p2[0] + b, p2[1] + a
        # extend second point
        c, d = calc_offset(weight, p2, p3, p4)
        x2b, y2b = p3[0] - d, p3[1] - c
        x2c, y2c = p3[0] + d, p3[1] + c
        polygon((x1b, y1b), (x1c, y1c), (x2b, y2b), (x2c, y2c))
    # --------------------------------------------------------------------
    # The actual drawing
    fill(0, .4)
    weight = 180
    sq_cap = True
    if len(points) == 1:
        rect(points[0][0] - weight/2, points[0][1] - weight/2, weight, weight)    
    for i, point in enumerate(points[:-1]):
        # print (get_dist(point, points[i+1]))
        if len(points) == 2:
            wrap_line(points[i], points[i], points[i+1], points[i+1], weight)
            if i == 0:
                aaa = calc_angle(points[i], points[i+1])
                if sq_cap:
                    point = point[0] - weight/2 * cos(aaa), point[1] - weight/2 * sin(aaa)
                wrap_line(point, point, points[i+1], points[i+2], weight)
            elif i == len(points)-2:
                next_p = points[i+1]
                aaa = calc_angle(points[i], next_p)
                if sq_cap:
                    next_p = next_p[0] + weight/2 * cos(aaa), next_p[1] + weight/2 * sin(aaa)
                wrap_line(points[i-1], points[i], next_p, next_p, weight)
                # print ( calc_delta(points[i-1], points[i], points[i+1]) )
                wrap_line(points[i-1], points[i], points[i+1], points[i+2], weight)


  • and thanks to @gferreira who helped me getting the initial bits!

  • Thanks for sharing this!—I'll have a look. I agree that Crouwel used that beveled join pretty consistently. It's just that the underlying logic of the letterforms—the grid of nodes, the segments used to make up the glyph paths—is independent of how the strokes are styled. (Thus he had for example a version of the alphabet with dots in the dimensions of his columns and rows.)

  • @jo good times!

  • @jo Johannes, thanks again for posting your code. I'm off to a promising start—the code is handling the 90° bends perfectly and capping the ends correctly; these paths all have 3 or more points to them. But it's not handling the 2-point segments correctly, as you can see here (the red circles mark the node locations in the grid). I'm digging into the code now, but in case you have any ideas … ?

    0_1527973542779_Screen Shot 2018-06-02 at 16.58.26.png

  • I can confirm the sq_cap toggle does work and affects the polygon drawn around the bent paths, but not the two-segment paths.

  • Figured it out, seems obvious: the <if len(points) == 2:> option wasn't adjusting for the square cap. Just took some adapting the code and it all works great, now. Thanks again!

  • @mauricemeilleur sorry. I made the changes and updated the post. Only after implementing the changes in my local version did I realize that i forgot to adapt the len(points) == 2 option. but great you figured it out anyway,
    good luck!

  • The last thing will be to provide for the case where the points are in a continuous chain, like the <o>. I'm pretty sure I know what to do, and it shouldn't be that hard, but I have some other problems with the Crouwel code to fix first (all to do with graphing and finding valid paths—this whole project is forcing me to teach myself the basics of computer science as I go). I'll post the revised and extended code here when it's done.