TrueMyst / PillowFontFallback

A script that handles Pillow Font Fallback, a feature that we all wanted.
MIT License
4 stars 0 forks source link

Better support for `anchor` #1

Open bradenhilton opened 3 months ago

bradenhilton commented 3 months ago

First I just wanted to say thank you for this. I can do almost exactly what I want with it.

I'm finding that providing a value for anchor gives unexpected results.

For example, I'd expect that an anchor of mm and an xy of (width/2, height/2) would center the entire text/sentence as a whole both horizontally and vertically, but it doesn't, it centers the first chunk as if it were the only chunk, and subsequent chunks are offset incorrectly.

My current usage is batch annotating a set of images by reading annotations from a corresponding text file. Some of these annotations contain emoji. I'm using draw_text_v2 to create the annotation separately and then just pasting the image and annotation together into a finished image.

For testing purposes, I set the annotation text to foo bar baz πŸ˜€πŸ˜ƒπŸ˜„πŸ˜πŸ˜†, and I placed lines on the annotation section to show its center point.

Here is what I get when saving the annotation image:

1

And here a quick Photoshop mock up of roughly how I would expect it to look when passing mm for anchor (excuse the lack of color in the emoji):

2

I think my exact requirements would be easy enough to implement (centering the whole sentence horizontally and vertically), for example, I could do something like get the total width of the text, start at center - (total_width / 2) and just provide None for anchor, but I can't think of a solution for all possible anchor values and text directions at the moment.

I've been experimenting with this draw_text_v3:

def draw_text_v3(
    draw: ImageDraw.ImageDraw,
    xy: Tuple[int, int],
    text: str,
    color: Tuple[int, int, int],
    fonts: Dict[str, TTFont],
    size: int,
    anchor: Optional[str] = None,
    align: Literal["left", "center", "right"] = "left",
    direction: Literal["rtl", "ltr", "ttb"] = "ltr",
) -> None:
    """
    Draws text on an image at given coordinates, using specified size, color, and fonts.
    """

    sentence = merge_chunks(text, fonts)

    chunk_data = []
    for text_chunk, font_path in sentence:
        font = ImageFont.truetype(font_path, size)
        chunk_data.append({
            'text': text_chunk,
            'font': font,
            'bbox': font.getbbox(text_chunk, direction=direction, anchor=anchor)
        })

    x_offset = sum(chunk['bbox'][0] for chunk in chunk_data)
    max_top = max(chunk['bbox'][1] for chunk in chunk_data)

    for chunk in chunk_data:
        draw.text(
            xy=((xy[0] + x_offset), (xy[1] + max_top)),
            text=chunk['text'],
            fill=color,
            font=chunk['font'],
            anchor=None,
            align=align,
            direction=direction,
            embedded_color=True,
        )
        x_offset += chunk['bbox'][2] - chunk['bbox'][0]

Which is quite close:

3

But I haven't tested it exhaustively yet.

TrueMyst commented 3 months ago

Hey @bradenhilton,

First off, thank you so much for taking the time to create an issueβ€”I really appreciate it. I'm aware of this problem, and to be honest, I never initially intended to support emojis due to my specific use cases. However, I was curious if it was possible, so I experimented with several solutions. At one point, I even considered pasting images of emojis directly into the text, but it became quite complicated to fix.

Due to the lack of interest, I shifted my focus to another project, BeatPrints, which does use the PillowFontFallback. Despite this, I feel inclined to revisit this project now, especially since it seems we're making progress. I'll update the existing code, as it's quite outdated, and from there, we can see what we can achieve.

Thanks again for your interest and help!