Defining lineHeight and first baseline: status?



  • I've reviewed everything I could find that people have posted here and on the DB GitHub repo about the difficulties with defining lineHeight and first baselines, and the conversations/threads feel either abortive or circular/self-referential—so let me ask this outright: Is it (still) impossible to specify exactly the lineHeight of a FormattedString (in a given line of text, across multiple lines, from one line to the next) and the position of the baseline of the first line of text in a textBox? Or, have (precise, reliable) solutions been found to these problems?

    Even fixes that have to be tailored to a given font that still allow me to set type algorithmically would be welcome. I'm going to have a lot of diagrams and specimens to make soon, and I'd really hate to drag InDesign and manual typesetting back into my workflow any more than I absolutely have to …



  • @MauriceMeilleur

    not sure I understand your question correctly.
    my assumption is you want to specify a position for the first baseline and have the textbox draw the text so the first line is at that given position no matter the lineheight or font size?
    the following script should do that. I tested a few fonts, fontSizes and lineHeights.

    # -----------------------------------
    #  S E T T I N G S  
    
    fnt = 'Helvetica'
    
    fnt_s = 16  # the fontsize 
    lh = fnt_s * 1.6 # the lineheight 
    
    txt = 'Testing the position of the baseline and the setting of the line height in drawBot. '*30
    
    tbx_x = 100  # x position of the textbox 
    
    frst_bl_y = 657 # the exact position of the first baseline within the textbox 
    
    tbx_w = 350 # textbox width
    tbx_h = 400 # textbox heigth
    
    
    # --------------
    # F U N C T I O N S 
    
    def calc_baseline_offset(fnt, fnt_s, lh):
        '''calculates the position of the first baseline of a placeholder textbox and returns the distance from the top edge of the textbox'''
        fstr = FormattedString('Hm', font = fnt, fontSize = fnt_s, lineHeight = lh) 
        temp_first_baseline = textBoxBaselines(fstr, (0, 0, fnt_s * 3, fnt_s * lh))[0][1]
        return temp_first_baseline - (fnt_s * lh)
    
    # -----------------------------------
    #  D R A W I N G S  
    
    
    newPage('A4')
    
    fstr = FormattedString(txt, font = fnt, fontSize = fnt_s, lineHeight = lh)
    
    rect(tbx_x - 20, frst_bl_y, tbx_w + 40, -1) # drawing the first baseline 
    
    y_shift  = calc_baseline_offset(fnt, fnt_s, lh)
    
    
    tbx_y = frst_bl_y - tbx_h - y_shift # the y position for the textbox so that the first baseline stays at the given value
    
    fill(1, 0, 0, .1)
    rect(tbx_x, tbx_y, tbx_w, tbx_h)
    
    textBox(fstr, (tbx_x, tbx_y, tbx_w, tbx_h))
    
    # drawing all the baselines 
    for x, y in textBoxBaselines(fstr, (tbx_x, tbx_y, tbx_w, tbx_h)):
        rect(tbx_x, y, tbx_w, -1)
        
    
    

    asdfasdf.png



  • Hi, Jo—

    Sorry, I've packed a lot into my question. Here's my initial problem:

    IMG_3304.JPG

    For the book I'm writing on Jurriaan Schrofer's constructed scripts, I'm designing a series of variable fonts for the diagrams and reconstructions. To recreate this cover Schrofer used for a series of sociology texts I've created a font with two variation axes, height and width, both of which change the ratio of stroke to counter. The strokes or counters are larger depending on whether the value of the axis in question is positive or negative. The height axis is defined such that the glyphs are at their tallest and occupy the entire glyphspace minus top/bottom spaces when the ratio of horizontal strokes to counters is 10:1. They're at their shortest when the ratio is 1:1. Sidebearings are constant and equal to .5 the stroke/counter width at 1:1. (If it helps: I'm using 550upm to keep integer coordinates for all my masters; descender is -5; x-, cap-, and ascender are 545; LSB/RSB are both 5.)

    What I'd like to do is keep fontSize constant and vary the lineHeight from line to line based on the calculated height of the drawn glyphs in each line according to the setting of the height axis. (The heights will all be less than the nominal fontSize because the glyphs never need to be drawn to their maximum height for this layout.) The linespacings are easy enough to calculate, but for some reason I can't feed them as lineHeights to a FormattedString and get linespacing I can make any sense of.

    So for example: here I have a textBox the dimensions of the canvas and the height axis in the first line set to my intended max ratio 3.5:1 (strokes/counters), except for the last glyph S with the axis set to 10:1 for reference, and the lineHeight set to the fontSize. The first baseline is exactly where I'd expect it to be; the first red line from the top is the fontSize distance from the top of the textBox. But the next baseline, for a line of type with identical linespacing to the first line (fontSize), is … not. The second red line shows where the next baseline should be (twice the fontSize from the top of the textBox), and you can see where DrawBot is actually drawing the type.

    baseline_test.png

    It gets weirder: If I calculate what the linespacing for the first line should be and give it to both lines of text in the FormattedString, here's what I get. The two blue lines are where the lines of type should be, the red lines are from the first example for reference.

    baseline_test.png

    You can see the lines are moved up from the first example, but not by nearly enough, and the lines are much farther apart than their specified linespacing.

    Here's the code (I can share the font too if needed):

    source = 'lestextessociologiques' 
    fSize = 550
    array = FormattedString()
    ft = '/Users/meilleur/Desktop/Variable/schrofer_02_textes-VF.ttf'
    
    u = fSize/55
    lHAdj = (5 * u) + (3.5 * 5 * u)
    
    temp = FormattedString()
    for index, char in enumerate(source):
        temp.append(char, font=ft, fontSize=fSize, lineHeight=lHAdj, align='center', fontVariations={'wdth': -5.5 + index * .5, 'hght': 2.5 if index < (len(source) - 1) else 9}) #9 is max height, 10:1 stroke:counter
    array.append(temp)
    array.append('\n')
    
    temp = FormattedString()
    for index, char in enumerate(source):
        temp.append(char, font=ft, fontSize=fSize, lineHeight =lHAdj, align='center', fontVariations={'wdth': -5.5 + index * .5, 'hght': 2.5})
    array.append(temp)
    # array.append('\n')
    
    newPage(2000, 2000)
    with savedState():
        fill(1)
        rect(0, 0, width(), height())
    textBox(array, (0, 0, width(), height()))
    fill(None); stroke(1, 0, 0)
    line((0, height() - fSize), (width(), height() - fSize))
    line((0, height() - 2 * fSize), (width(), height() - 2 * fSize))
    stroke(0, 0, 1)
    line((0, height() - lHAdj), (width(), height() - lHAdj))
    line((0, height() - 2 * lHAdj), (width(), height() - 2 * lHAdj))
    stroke(0); strokeWidth(2)
    rect(0, 0, width(), height())
    
    saveImage('~/Desktop/baseline_test.png')
    


  • I’m trying to understand the problem. Does this simplification work with your font?

    What I'd like to do is keep fontSize constant and vary the lineHeight from line to line […]

    source = 'lestextessociologiques' 
    fSize = 90
    array = FormattedString()
    ft = 'Helvetica'
    lh_min = 100
    lh_max = 200
    lh_step = 10
    
    temp = FormattedString()
    for lh in range(lh_min, lh_max, lh_step):
        temp.append(source, font=ft, fontSize=fSize, lineHeight=lh, align='center')
        temp.append('\n')
    array.append(temp)
    
    newPage()
    with savedState():
        fill(1)
        rect(0, 0, width(), height())
    
    textBox(array, (0, 0, width(), height()))
    
    fill(None); stroke(1, 0, 0)
    y = height()
    for lh in range(lh_min, lh_max, lh_step):
        y -= lh
        line((0, y), (width(), y))
    
    saveImage('~/Desktop/baseline_test.png')
    

    baseline_test.png



  • thanks for clarifying.
    one thing that made me wonder when reading the unconventional UPM of 550: might this be related to this bug: https://github.com/typemytype/drawbot/issues/555#issuecomment-1948050444.

    as @monomonnik suggests I also tried a simpler script with Mutator Sans and I think this does what you expect?

    txt = 'lestextessociologiques'.upper()
    
    fnt = 'MutatorSans.ttf'
    
    fnt_s = 24
    lh = 32
    
    lh_shift = -3
    
    rows = 11 
    
    
    newPage('A4')
    txt_l = len(txt)
    
    fstr = FormattedString(fontSize = fnt_s, font = fnt)
    
    for r_i in range(rows):
        for t_i in range(txt_l):
            fstr.append(txt[(t_i + r_i)%txt_l], font = fnt, fontSize = fnt_s, lineHeight = lh, fontVariations={'wght': 1000/rows * r_i, 'wdth': 1000/txt_l*t_i})
        fstr.append('\n')
        
        lh += lh_shift if r_i < rows/2 else -lh_shift
        
    text(fstr, (20, height() - 100))
    

    Screenshot 2024-05-02 at 14.55.21.png

    otherwise just using a text() for each line might me a way?

    
    
    txt = 'lestextessociologiques'.upper()
    fnt = '/Users/jo/Desktop/MutatorSans.ttf'
    
    fnt_s = 24
    lh = 50
    lh_shift = -3
    
    rows = 11 
    
     
    newPage('A4')
    
    txt_l = len(txt)
    
    y = height() - 100
    
    for r_i in range(rows):
    
        fstr = FormattedString(fontSize = fnt_s, font = fnt)
    
        for t_i in range(txt_l):
            fstr.append(txt[(t_i + r_i)%txt_l], font = fnt, fontSize = fnt_s, fontVariations={'wght': 1000/rows * r_i, 'wdth': 1000/txt_l*t_i})
    
    
        text(fstr, (10, y))
        lh += lh_shift if r_i < rows/2 else -lh_shift
        
        y -= lh
    


  • @monomonnik said in Defining lineHeight and first baseline: status?:

    source = 'lestextessociologiques'
    fSize = 90
    array = FormattedString()
    ft = 'Helvetica'
    lh_min = 100
    lh_max = 200
    lh_step = 10

    temp = FormattedString()
    for lh in range(lh_min, lh_max, lh_step):
    temp.append(source, font=ft, fontSize=fSize, lineHeight=lh, align='center')
    temp.append('\n')
    array.append(temp)

    newPage()
    with savedState():
    fill(1)
    rect(0, 0, width(), height())

    textBox(array, (0, 0, width(), height()))

    fill(None); stroke(1, 0, 0)
    y = height()
    for lh in range(lh_min, lh_max, lh_step):
    y -= lh
    line((0, y), (width(), y))

    saveImage('~/Desktop/baseline_test.png')

    Well, this is interesting: when I run that code, here's the output I get:

    baseline_test.png



  • @jo I doubt it's a UPM issue … I was noticing the problem before I went back and remade the fonts (I did that to solve a different problem, namely to fix problems caused by RoboFont rounding floating-point coordinates in ways that threw off alignment). And I'm getting weird behavior with other fonts, as for example see above in response to Wim's post and using his code …



  • In case anyone's following this thread: over on RoboFont's Discord, thanks to Wim asking we determined that the issue had mainly to do with a bug in a pre-release version of DB I was using; using v3.130 cleared things up.


Log in to reply