gumyr / build123d

A python CAD programming library
Apache License 2.0
510 stars 86 forks source link

Text does not factor in line height #459

Open MatthiasJ1 opened 9 months ago

MatthiasJ1 commented 9 months ago

The text alignment bounding box height should be determined by the font's line height rather than the rendered text's bounding box. The current implementation leads to errors like this:

Text("A car.", 5)
Screenshot
Text("A", 5) + Pos(6)*Text("car", 5) + Pos(10)*Text(".", 5)
Screenshot2
gumyr commented 9 months ago

Align applies to Text like all objects:

t = (
    Text("A", 5, align=Align.MIN)
    + Pos(5) * Text("car", 5, align=Align.MIN)
    + Pos(12) * Text(".", 5, align=Align.MIN)
)
show(t)

image

The default is Align.CENTER,Align.CENTER. Do you still think there is a problem here?

MatthiasJ1 commented 9 months ago
t = (
    Text("ye", 5, align=Align.MIN)
    + Pos(5.5) * Text("s", 5, align=Align.MIN)
)
show(t)
Screenshot
gumyr commented 9 months ago

If alignment worked based on the font's line height, vertically centering a single lower case letter would always be wrong which would be very frustrating. build123d isn't a typesetting tool. Users are primarily going to be embossing very small amounts of text onto their parts so control over single characters is more important then sentences spanning multiple Text blocks.

Won't fix.

MatthiasJ1 commented 9 months ago

@gumyr if you want to center text based on its bounding box rather than the text parameters you could easily realign the object using its bounding box (add could have an align parameter) or you could have an alignmode=[Text, Geometry] argument for Text.

Where this is an issue is say you have a ruler or panel with text underneath some feature. If the text includes different anchors ("mode" vs "43" vs "yaw") there is currently no way to align those without manually specifying offsets for each label.

gumyr commented 9 months ago

Sorry for closing the issue prematurely. I see now that there are some circumstances where the current implementation would make it difficult to align a series of Text elements as you describe. I don't think just changing how align works is the correct solution though; some new functionality is required to introduce text specific alignment features.

CarpeNecopinum commented 1 week ago

My workaround for this is the following function:

def SafeText(text, size, *args, **kwargs):
    modded_text = f"█ {text} █"
    faces = Text(modded_text, size, *args, **kwargs)
    faces = faces.faces().sort_by(Axis.X)[1:-1]

    return faces

i.e. I add a U+2588 "Full Block" to either end of the String, and filter out the resulting two faces afterwards. That way, aligning (especially vertically) is a lot more consistent.

gumyr commented 1 week ago

Unfortunately, the size of the "Full Block" isn't a very accurate indicator of where the descender bottoms out:

text_abg = Text("█abg", font_size=50, align=Align.MIN)

image

What might be more deterministic is finding the "baseline" (i.e. the bottom of the "a") and having an option to align vertically based on this baseline as described above with a new parameter alignmode=AlignMode.TEXT or alignmode=AlignMode.GEOMETRY. Would that solve this issue?