fontParts icon indicating copy to clipboard operation
fontParts copied to clipboard

Proposal: Smart Margins

Open typesupply opened this issue 1 year ago • 5 comments

The left, right, top, bottom margins rely on glyph bounds, which is fine for most work. There are times when it would be nice to be able to specify the y (in the case of left, right) where we want the measurement to be taken instead of the bounding box. I started writing this for a tool, but then realized that it might be useful in fontParts. Here's the idea (for left, right for now, but top, bottom are similar):

  • the y value is defined with a guideline
  • the guideline can be located at the font or glyph level.
  • glyph guidelines override font guidelines.
  • guidelines may be defined for use on both sides, the left or the right by setting the guideline name: margins, margin-left, margin-right`
  • side specific guidelines override both side guidelines.
  • if a side specific guideline is not defined, the base leftMargin or rightMargin value is used.

These would be accessible through properties, just like leftMargin and rightMargin. The names that popped into my head were smartLeftMargin and smartRightMargin but I don't really like the vagueness of smart. It implies magic but there is no magic in this. Maybe something like local*Margin? I don't know...

Here is a quick implementation:

from fontPens.marginPen import MarginPen

def getSmartMargins(glyph):
    font = glyph.font
    both = None
    left = None
    right = None
    # look for glyph level guidelines
    while any((both is None, left is None, right is None)):
        for guideline in glyph.guidelines:
            if guideline.name == "margins":
                both = guideline
            elif guideline.name == "margin-left":
                left = guideline
            elif guideline.name == "margin-right":
                right = guideline
        break
    # use font guidelines if there
    # glyph level guidelines are
    # not defined
    buffer = 10
    if all((font is not None, both is None, left is None, right is None)):
        buffer = font.info.unitsPerEm * 0.01
        while any((both is None, left is None, right is None)):
            for guideline in glyph.guidelines:
                if all((both is None, guideline.name == "margins")):
                    both = guideline
                elif all((left is None, guideline.name == "margin-left")):
                    left = guideline
                elif all((right is None, guideline.name == "margin-right")):
                    left = guideline
            break
    # calculate the left
    leftMargin = 0
    if all((left is None, both is None)):
        leftMargin = glyph.leftMargin
    elif any((glyph.contours, glyph.components)):
        if left is not None:
            y = left.y
        elif both is not None:
            y = both.y
        pen = MarginPen(glyphSet=glyph.layer, value=y)
        glyph.draw(pen)
        leftMargin = pen.getMargins()[0]
    # calculate the right
    rightMargin = 0
    if all((right is None, both is None)):
        rightMargin = glyph.rightMargin
    elif any((glyph.contours, glyph.components)):
        if right is not None:
            y = right.y
        elif both is not None:
            y = both.y
        pen = MarginPen(glyphSet=glyph.layer, value=y)
        glyph.draw(pen)
        rightMargin = glyph.width - pen.getMargins()[1]
    # done
    return (leftMargin, rightMargin)

def setSmartLeftMargin(glyph, value):
    oldValue = getSmartMargins(glyph)[0]
    diff = value - oldValue
    glyph.leftMargin += diff

def setSmartRightMargin(glyph, value):
    oldValue = getSmartMargins(glyph)[1]
    diff = value - oldValue
    glyph.rightMargin += diff

Would this be a good addition to fontParts or is this something better left to an extension or something like that?

typesupply avatar Nov 07 '24 17:11 typesupply

Any thoughts @benkiel, @LettError , @typemytype?

typesupply avatar Nov 07 '24 17:11 typesupply

I really like the idea of being able to approach margin values at a specific height. But that means there needs to be a way to record those heights somewhere in the glyph. Are guides the best medium? These can be angled - is that useful, or complicated?

Also how would this relate to angledMargins? In an italic, with italic margins set?

Would a y value, stored in glyph.lib suffice?

(I don't like glyph.lib solutions, there is an impermanence to them)

LettError avatar Nov 07 '24 18:11 LettError

It could also be a named anchor, and you'd ignore the x value.

justvanrossum avatar Nov 07 '24 20:11 justvanrossum

Anchor or guidelines would work. My first thought was anchor but I switched to guidelines because they are already visualized in editors with a line. Extensions could add visualization to anchors (something like a line from the anchor to the intersection in the outline). If we use guides, I'd document that angles should be ignored.

I tried to think about angled margins but my brain melted. I'm sure we can figure it out though.

typesupply avatar Nov 07 '24 20:11 typesupply

FWIW, I was thinking how having same functionality for spacing Arabic glyphs a while ago. When I set the margin for many glyphs, a lot of time I want the reference for the margin to be taken from the same height for most glyphs and not the margin that is based on the glyph bounds.

typoman avatar Nov 28 '24 17:11 typoman