Fix line width calculation for expressions with superscripts/subscripts
The typesetter was incorrectly measuring line width when expressions contained
superscripts or subscripts (e.g., b²). After rendering a superscript, the line
is split into multiple display segments, but the width checking code was only
measuring the current segment, not the total visual line width.
Key changes:
- Use currentPosition.x to track actual horizontal position across all segments
- Calculate visualLineWidth = currentPosition.x + currentSegmentWidth
- Pass remainingWidth (maxWidth - currentPosition.x) to findBestBreakPoint
- Apply fix to both interatom breaking and inline text breaking
This fixes truncation issues where content like "Δ=b²-4ac avec a=1..." was
being clipped instead of wrapped to a new line.
Before: Each segment checked in isolation → segments appeared to fit individually
but total visual width exceeded maxWidth → content truncated/clipped
After: Total visual width tracked correctly → line breaking triggered when
actual visual width exceeds maxWidth → content wraps properly
This commit is contained in:
@@ -278,24 +278,9 @@ public class MTMathUILabel : MTView {
|
||||
let effectiveWidth = _preferredMaxLayoutWidth > 0 ? _preferredMaxLayoutWidth : bounds.size.width
|
||||
let availableWidth = effectiveWidth - contentInsets.left - contentInsets.right
|
||||
|
||||
print("🔧 MTMathUILabel _layoutSubviews:")
|
||||
print(" preferredMaxLayoutWidth: \(_preferredMaxLayoutWidth)")
|
||||
print(" bounds.size.width: \(bounds.size.width)")
|
||||
print(" effectiveWidth: \(effectiveWidth)")
|
||||
print(" availableWidth: \(availableWidth)")
|
||||
print(" LaTeX: \(_latex.prefix(60))...")
|
||||
|
||||
// print("Pre list = \(_mathList!)")
|
||||
_displayList = MTTypesetter.createLineForMathList(_mathList, font: self.font, style: currentStyle, maxWidth: availableWidth)
|
||||
_displayList!.textColor = textColor
|
||||
|
||||
print(" Display subDisplays count: \(_displayList!.subDisplays.count)")
|
||||
for (index, subDisplay) in _displayList!.subDisplays.enumerated() {
|
||||
print(" Display \(index): type=\(type(of: subDisplay)), x=\(subDisplay.position.x), width=\(subDisplay.width)")
|
||||
if let lineDisplay = subDisplay as? MTCTLineDisplay {
|
||||
print(" Content: '\(lineDisplay.attributedString?.string ?? "")'")
|
||||
}
|
||||
}
|
||||
// print("Post list = \(_mathList!)")
|
||||
var textX = CGFloat(0)
|
||||
switch self.textAlignment {
|
||||
|
||||
@@ -557,7 +557,10 @@ class MTTypesetter {
|
||||
// starts with a letter, they're part of the same word - don't break!
|
||||
// Example: "...é" + "quivaut" should not break
|
||||
// But "...km " + "équivaut" can break (has space)
|
||||
if lastChar.isLetter && firstChar.isLetter {
|
||||
// IMPORTANT: Only apply this to multi-character atoms (text words), not single
|
||||
// letters (math variables). In math "4ac" splits as "4","a","c" - these are
|
||||
// separate and CAN be broken between.
|
||||
if lastChar.isLetter && firstChar.isLetter && atom.nucleus.count > 1 {
|
||||
// Don't break - this would split a word
|
||||
return false
|
||||
}
|
||||
@@ -565,9 +568,14 @@ class MTTypesetter {
|
||||
}
|
||||
|
||||
// Calculate what the width would be if we add this atom
|
||||
// IMPORTANT: Use currentPosition.x instead of getCurrentLineWidth()
|
||||
// because currentLine only measures the current text segment, but after
|
||||
// superscripts/subscripts, the line may be split into multiple segments.
|
||||
// currentPosition.x tracks the actual visual horizontal position.
|
||||
let currentLineWidth = getCurrentLineWidth()
|
||||
let visualLineWidth = currentPosition.x + currentLineWidth
|
||||
let atomWidth = calculateAtomWidth(atom, prevNode: prevNode)
|
||||
let projectedWidth = currentLineWidth + atomWidth
|
||||
let projectedWidth = visualLineWidth + atomWidth
|
||||
|
||||
// If we're well within the limit, no need to break
|
||||
if projectedWidth <= maxWidth {
|
||||
@@ -1213,14 +1221,22 @@ class MTTypesetter {
|
||||
let attrString = currentLine.mutableCopy() as! NSMutableAttributedString
|
||||
attrString.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value:styleFont.ctFont as Any, range:NSMakeRange(0, attrString.length))
|
||||
let ctLine = CTLineCreateWithAttributedString(attrString)
|
||||
let lineWidth = CGFloat(CTLineGetTypographicBounds(ctLine, nil, nil, nil))
|
||||
let segmentWidth = CGFloat(CTLineGetTypographicBounds(ctLine, nil, nil, nil))
|
||||
|
||||
if lineWidth > maxWidth {
|
||||
// IMPORTANT: Account for currentPosition.x to get the true visual line width
|
||||
// After superscripts/subscripts, currentPosition.x > 0 because previous segments
|
||||
// have been rendered and flushed
|
||||
let visualLineWidth = currentPosition.x + segmentWidth
|
||||
|
||||
if visualLineWidth > maxWidth {
|
||||
// Line is too wide - need to find a break point
|
||||
let currentText = currentLine.string
|
||||
|
||||
// Use Unicode-aware line breaking with number protection
|
||||
if let breakIndex = findBestBreakPoint(in: currentText, font: styleFont.ctFont, maxWidth: maxWidth) {
|
||||
// IMPORTANT: Use remaining width, not full maxWidth, because currentPosition.x
|
||||
// may be > 0 if we've already rendered segments on this visual line
|
||||
let remainingWidth = max(0, maxWidth - currentPosition.x)
|
||||
if let breakIndex = findBestBreakPoint(in: currentText, font: styleFont.ctFont, maxWidth: remainingWidth) {
|
||||
// Split the line at the suggested break point
|
||||
let breakOffset = currentText.distance(from: currentText.startIndex, to: breakIndex)
|
||||
|
||||
@@ -1228,14 +1244,14 @@ class MTTypesetter {
|
||||
let firstLine = NSMutableAttributedString(string: String(currentText.prefix(breakOffset)))
|
||||
firstLine.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value:styleFont.ctFont as Any, range:NSMakeRange(0, firstLine.length))
|
||||
|
||||
// Check if first line still exceeds maxWidth - need to find earlier break point
|
||||
// Check if first line still exceeds remaining width - need to find earlier break point
|
||||
let firstLineCT = CTLineCreateWithAttributedString(firstLine)
|
||||
let firstLineWidth = CGFloat(CTLineGetTypographicBounds(firstLineCT, nil, nil, nil))
|
||||
|
||||
if firstLineWidth > maxWidth {
|
||||
if firstLineWidth > remainingWidth {
|
||||
// Need to break earlier - find previous break point
|
||||
let firstLineText = firstLine.string
|
||||
if let earlierBreakIndex = findBestBreakPoint(in: firstLineText, font: styleFont.ctFont, maxWidth: maxWidth) {
|
||||
if let earlierBreakIndex = findBestBreakPoint(in: firstLineText, font: styleFont.ctFont, maxWidth: remainingWidth) {
|
||||
let earlierOffset = firstLineText.distance(from: firstLineText.startIndex, to: earlierBreakIndex)
|
||||
let earlierLine = NSMutableAttributedString(string: String(firstLineText.prefix(earlierOffset)))
|
||||
earlierLine.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value:styleFont.ctFont as Any, range:NSMakeRange(0, earlierLine.length))
|
||||
|
||||
Reference in New Issue
Block a user