GithubHelp home page GithubHelp logo

Comments (10)

radarhere avatar radarhere commented on July 28, 2024 1

I don't understand "backwards compatibility"

Pillow would like to continue working as expected when users upgrade to newer versions. If text changes position, this would break expectations.

Alternatively, we could

  • add new methods with the new line heights, deprecate the current methods and then after a period of at least a year remove the current methods
  • keep the current behaviour as the default, but add a setting to change the text position

Neither of these are very elegant though.

from pillow.

radarhere avatar radarhere commented on July 28, 2024 1

Shall I close this and let the problem be followed in #1646 then?

from pillow.

radarhere avatar radarhere commented on July 28, 2024

(even "A" is not the highest character!! ex."[", "}" ). line spacing can be different line by line

I think the unfortunate reality is that each font can set the tallest character to be whatever it wants. Here's a font where 'A' is taller.

Screenshot 2024-06-01 at 6 34 51 PM

It's also feasible that a font doesn't have the '[' character. 'A' was chosen in #1574

Textbox height is wrong

If I understand correctly, you're not saying that the co-ordinates returned by draw.textbbox() for multiline text don't match the co-ordinates of multiline text drawn by draw.text(). Rather, draw.textbbox() and draw.text() are both incorrect for multiline text because you're saying the line height is incorrect?

I suggest

def _multiline_spacing(self, font, spacing, stroke_width):
        return (
            self.textbbox((0, 0), "[", font, stroke_width=stroke_width)[3]
            - self.textbbox((0, 0), "[", font, stroke_width=stroke_width)[1]
            + spacing
        )

So your suggestion is to remove the vertical offset that the font chooses to apply to the text. I'm not sold on the idea that we should be ignoring the font author's idea about how much vertical gap should be above characters. If you'd like to do so in your code, that is fine.

from pillow.

nulano avatar nulano commented on July 28, 2024

So your suggestion is to remove the vertical offset that the font chooses to apply to the text. I'm not sold on the idea that we should be ignoring the font author's idea about how much vertical gap should be above characters. If you'd like to do so in your code, that is fine.

We already ignore the font author's idea about line spacing, see #1646 / #6469 (comment).

My suggestion from #6469 (comment) is:

def _multiline_spacing(self, font, spacing, stroke_width):
    return font.font.height

I just don't know how we could make this change without breaking backwards compatibility for a lot of people.

from pillow.

HERIUN avatar HERIUN commented on July 28, 2024

@radarhere

I understand "A" can be taller than "[".

If I understand correctly, you're not saying that the co-ordinates returned by draw.textbbox() for multiline text don't match the co-ordinates of multiline text drawn by draw.text(). Rather, draw.textbbox() and draw.text() are both incorrect for multiline text because you're saying the line height is incorrect?

Sorry for confusing. I think draw.textbbox() and draw.text() are both incorrect for multiline text. because def _multiline_spacing() add stroke_width 3 times (rather 2). ie.(self.textbbox(2) + stroke_width(1))

def _multiline_spacing(self, font, spacing, stroke_width):
        return (
            self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
            + stroke_width
            + spacing
        )

from pillow.

radarhere avatar radarhere commented on July 28, 2024

+ stroke_width was added #7080 for the sake of backwards compatibility. See #7059 (comment) for further info.

from pillow.

HERIUN avatar HERIUN commented on July 28, 2024

@nulano

I don't understand "backwards compatibility"

I suggest 2 method. (but only font.font.height is always tallest character's height)

def _multiline_spacing(self, font, spacing, stroke_width):
    return font.font.height + 2*stroke_width + spacing

I think spacing must be out of def _multiline_spacing, rather added for line in lines loop

def _multiline_spacing(self, font, stroke_width):
    return font.font.height + 2*stroke_width

Why i don't want use self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3], self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[0] can be minus value width stroke_width...

def _multiline_spacing(self, font, spacing, stroke_width):
    return font.font.height + 2*stroke_width

from pillow.

nulano avatar nulano commented on July 28, 2024

Sorry for confusing. I think draw.textbbox() and draw.text() are both incorrect for multiline text. because def _multiline_spacing() add stroke_width 3 times (rather 2). ie.(self.textbbox(2) + stroke_width(1))

No, textbbox returns font.getbbox with an offset ((0,0) in this case):

Pillow/src/PIL/ImageDraw.py

Lines 753 to 756 in 95a69ec

bbox = font.getbbox(
text, mode, direction, features, language, stroke_width, anchor
)
return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]

And font.getbbox only adds stroke_width once:

Pillow/src/PIL/ImageFont.py

Lines 402 to 404 in 95a69ec

left, top = offset[0] - stroke_width, offset[1] - stroke_width
width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width
return left, top, left + width, top + height

Removing all except the last return value, we are left with:

top = offset[1] - stroke_width 
height = size[1] + 2 * stroke_width 
return ..., top + height 

Inline the variables:

return ..., (offset[1] - stroke_width) + (size[1] + 2 * stroke_width)

Rearrange:

return ..., (offset[1] + size[1]) + (2 * stroke_width - stroke_width)

Simplify:

return ..., (offset[1] + size[1] + stroke_width)

but only font.font.height is always tallest character's height

No, font.font.height is the font designer's intended line spacing, it has nothing to do with the height of the glyphs.


keep the current behaviour as the default, but add a setting to change the text position

Yeah, that is what I've been leaning towards, using the version from #8092 (comment) (but perhaps as a font object member function, since we'll need a different API if we ever want to finish #6926), but I suspect the API might get quite ugly for this.

from pillow.

HERIUN avatar HERIUN commented on July 28, 2024

Appreciate for explaination. I understand "backwards compatibility", "font.font.height is not height of the glyphs", "font.getbbox() add stroke_width once"

But, if stroke_width > offset[1]. top coords of font.getbbox() can be minus value. so, draw.textbbox() left,top can be minus value. and finally def _multiline_spacing() is not what we

def _multiline_spacing(self, font, spacing, stroke_width):
        return (
            self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
            + stroke_width
            + spacing
        )

== offset[1] + size[1] + 2*stroke_width + spacing

It doesn't mean line_spacing to me,

I expected, def _multiline_spacing() output is

def _multiline_spacing(self, font, stroke_width, spacing):
    size, offset = font.font.getsize('A')
    return (
        max(offset[1], stroke_width) # upside 
        + size[1]
        + stroke_width # downside
        + spacing

or consider font.font.height

def _multiline_spacing(self, font, stroke_width, spacing):
    size, offset = font.font.getsize('A')
    return (
        max(font.font.height,
          max(offset[1], stroke_width) # upside 
          + size[1]
          + stroke_width # downside
          + spacing
        )
    )

from pillow.

nulano avatar nulano commented on July 28, 2024

Have a look at this diagram: https://pillow.readthedocs.io/en/stable/handbook/text-anchors.html#quick-reference

We currently calculate the line spacing as the sum of:

  • height of the letter 'A' as measured from the ascender line (global font property) to the bottom line (specific to the letter 'A'),
  • 2x stroke width
  • user-specified spacing value

It is not a good idea to use the max function as it is not predictable here, instead I think you meant the following:

def _multiline_spacing(self, font, stroke_width, spacing):
    size, offset = font.font.getsize('A')
    return (
        offset[1] + stroke_width # top
        + size[1] + stroke_width # bottom
        + spacing

This would be similar to what we currently use, but instead of measuring the letter 'A' from the ascender line, it would be measuring it from the top line (specific to the letter 'A'), so even less predictable than currently.

The value font.font.height is equal to the sum of:

  • the distance between the ascender line and the descender line (of any text, it doesn't change),
  • the line_gap value as specified by the font author - this cannot be obtained in any other way IIRC

I believe this would be better than what we use since:

  • it measures between font lines that do not depend on the given text,
  • stroke is just adding a background so I find it surprising that it affects the line spacing,

However, as stated above, we can't easily make this change without breaking backwards compatibility.

from pillow.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.