Trouble aligning baselines of NSLayoutManager

835 Views Asked by At

I'm developing a custom UIView that renders text using a number of separate NSLayoutManager objects. (This view has text positioning requirements beyond what the built-in text view classes can support.) I'm having a hard time getting the correct vertical position for one of the layouts when the text being laid out is Hebrew. I apologize for the length of this post, but I wanted to include as much relevant detail as possible.

The problem

Here's an example of what I'm trying to accomplish:

enter image description here

The baseline for the "2" is nicely aligned with the baseline of the Hebrew letter "ו" (which is rendered in a custom font). However, if I switch the text of the "2" to a Hebrew number (actually the Hebrew letter "ב", which has value 2 in numerical contexts), it comes out like this (where the "ב" is too high):

enter image description here

The only change here is to the text string. Instead of "2", it's "\u{05b1}". I want everything to be on a common baseline and would appreciate any suggestions for fixing the problem (which happens for all Hebrew numbers, not just "\u{05b1}").

The code

(I should mention that for technical reasons, I can't use a single NSLayoutManager for both the number and the main text.) I'm trying to align the baselines by vertically shifting the rendering position for the number by the difference in font ascents. Here's the code to compute the shift:

numberFont = UIFont.systemFont(ofSize: /* some size */)
offset = fullFont.ascender - numberFont.ascender

Here's how I'm creating the NSLayoutManager for the number:

var lbl = "2" // or "\u{05b1}"

let paraStyle = NSMutableParagraphStyle()
paraStyle.alignment = .center
let text = NSTextStorage(
    string: lbl,
    attributes: [NSFontAttributeName: numberFont,
                 NSParagraphStyleAttributeName: paraStyle]
)

let layout = NSLayoutManager()
text.addLayoutManager(layout)
let container = NSTextContainer(
    size: CGSize(width: /* some fixed width */, height: bounds.height - offset)
)
container.lineFragmentPadding = 0
layout.addTextContainer(container)

let x = ... // irrelevant
let origin = CGPoint(x: x, y: bounds.minY + offset)

Later, I render the text using:

let range = layout.glyphRange(for: layout.textContainers[0])
layout.drawGlyphs(forGlyphRange: range, at: origin)

The main text is handled using identical logic but with no offset (and a different font, of course). This works beautifully for Arabic numerals (e.g., "2"), but fails for Hebrew numbers (e.g., "ב").

Further information

For the images above, the font size (.pointSize) and ascent (.ascender) properties are:

sizes: text = 14.3890409469604 ; number = 8.63342465753425
ascents: text = 16.159567469731 ; number = 8.2203017979452

I also generating some diagnostic information about the layouts, but the numbers don't make a lot of sense to me. Here's the code for printing out the information:

size = text.size()
mgr = NSLayoutManager()
text.addLayoutManager(mgr)
container = NSTextContainer(
    size: CGSize( width: CGFloat.greatestFiniteMagnitude,
                 height: CGFloat.greatestFiniteMagnitude))
container.lineFragmentPadding = 0
mgr.addTextContainer(container)
mgr.ensureLayout(for: container)
range = mgr.glyphRange(for: container)

print("label:", lbl)
print("  text size:", size)
print("     bounds:", mgr.boundingRect(forGlyphRange: range, in: container))

And here are the results for the two texts ("2" and "\u{05d1}"):

label: 2
  text size: (5.3958904109589, 10.3027782534247)
    bounds: (0.0, 0.0, 5.3958904109589, 10.3027782534247)
label: ב
  text size: (4.67931616438356, 9.19459726027397)
    bounds: (-1.88208657534247, -0.578439452054793, 9.5054005479452, 9.77303671232876)

Several things about this puzzle me:

  • the bounds for the for the Hebrew text start at negative values for x and y. The negative x origin may have something to do with Hebrew being a right-to-left script, but what about the negative y origin?
  • the reported size of the layout is the algebraic sum of the height of the bounds and the y start of the bounds or (perhaps more precisely) the bottom of the text in the container, not the actual overall size. Is NSAttributedString.size() broken?
  • none of the numbers seem to explain the visual difference in the vertical position of the "2" and of the "\u{05d1}".
0

There are 0 best solutions below