Ad

Our DNA is written in Swift
Jump

Radar: “CoreText Line Spacing Bug”

I finally got around to report an annoying bug in CoreText that has been bugging us in DTCoreText until I wrote a method to correct line origins as a workaround. rdar://10810114

The annoying thing about this bug is that it adds visual noise to otherwise pristinely rendered text. Especially on larger font sizes you see that additional space appears before each CTLine that ends with a paragraph break (\n).

UPDATE: This is a duplicate of rdar://9931615.

CoreText Line Spacing Bug

Summary

CoreText inserts too much space before any line that ends with a \n. This extra space depends on the font and font size. On large print this causes visual noise by not being uniform.

Steps to Reproduce

Create a CTFrame from a CTFrameSetter with a string that is long enough to wrap and that contains paragraph breaks. Use a non-UI font, like for example AriaMT.

Expected Results

Line origins should be spaced by exactly the same distance for identical text and identical attributes.

Actual Results

Each line that ends with a paragraph break is shifted down. With the system UI font, size 54 baselines are spaced exactly 64 pixels apart. With ArialMT, size 54, baseline spacing differs between 62 and 65.

Regression

This has been a bug since before iOS 4.3.

Notes

This does not occur with all fonts, Using a system font the spacing is precisely correct. I have attached a project to demonstrate the issue. See TextView.m.

It appears that the text metrics for an (invisible) paragraph glyph are miscalculated. Since the glyph is not visible you’d expect neither and ascender nor descender value. But instead the descender is too large. If you walk through the entire line and get the maximum ascenders and descenders the value is correct if you omit the \n in this calculation.

In short: A trailing \n messes up the font metrics for the entire CTLine.

Attachment: CoreTextLineOrigins Demo Project


Categories: Bug Reports

17 Comments »

  1. Bravo! This has been a longtime thorn in my side, as well. Though, it never occurred to me to file a bug report (I’m predisposed to believe that most bugs spawn from my ignorance). My hack has always been to replace the offending glyph with another invisible glyph of non-offending height.

  2. So what’s your workaround? replace \n with space? But how does CoreText then know where the paragraph breaks are (for applying the paragraph spacing)?

  3. I have exactly the same problem.
    Has anyone a work around for that ?

  4. My workaround in DTCoreText is to adjust the baseline origins based on the corrected measurements. Check it out!

  5. Thanks for your Answer.
    Can you describe how you adjust the baseline ?
    What are the correct measurements ?
    Its hard to find exactly that fix in DTCoreText, because its has so much more nice features.

  6. DTCoreTextLayoutFrame.m method correctLineOrigins.

  7. Thanks for the hint.
    What about the first line? You don’t correct the first line in this Method.

  8. I don’t? Have to check. But the problem only is visible between lines if the spacing differs. So you wouldn’t notice it for the first line.

    I plan to use my own typesetter soon and there I will position each line individually.

    PS: Why don’t you use DTCoreText?

  9. Hey, i don’t use DTCoreText to make my own experience with CT.
    I notice it for the first line :D.
    Is a \n in the first line, the text begins a bit further down.
    Normally it begins at y = 0,
    but when the first line contains a \n, the text starts at y = 4.
    Do you have an Idea, why the first line is lower when it contains a \n.

    I’am looking forward to your reply.

  10. For the same reason that all CTLines that have a \n are too low.

  11. Damn, i don’t get it to work for the first line.
    Do you have a hint, how i can calculate the origin correction.

    this is the best solution, but not works for other text is

    if firstline contains \n
    currentOrigin.y = lineOrigin.y + leading;

  12. I think the correct baseline origin for the first line should be page_frame_origin.y + line.leading + line.ascender.

  13. this seems to be the best for me:

    page_frame_origin.y + ascent – (leading / 2.0)

    but in one box, the text is truncated…