Fix assert failures for unhandled atom types in inter-element spacing
Replace fatal asserts with explicit handling for all MTMathAtomType cases in getInterElementSpaceArrayIndexForType(). Previously, unhandled types (accent, number, variable, unaryOperator, underline, overline, boundary, space, style, table) would trigger assert failures and return Int.max, causing array out-of-bounds crashes.
This commit is contained in:
@@ -244,6 +244,7 @@ public class MTMathUILabel : MTView {
|
||||
override public func draw(_ dirtyRect: MTRect) {
|
||||
super.draw(dirtyRect)
|
||||
if self.mathList == nil { return }
|
||||
if self.font == nil { return }
|
||||
|
||||
// drawing code
|
||||
let context = MTGraphicsGetCurrentContext()!
|
||||
@@ -253,48 +254,64 @@ public class MTMathUILabel : MTView {
|
||||
}
|
||||
|
||||
func _layoutSubviews() {
|
||||
if _mathList != nil {
|
||||
// Use the effective width for layout
|
||||
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: 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 {
|
||||
case .left: textX = contentInsets.left
|
||||
case .center: textX = (bounds.size.width - contentInsets.left - contentInsets.right - _displayList!.width) / 2 + contentInsets.left
|
||||
case .right: textX = bounds.size.width - _displayList!.width - contentInsets.right
|
||||
}
|
||||
let availableHeight = bounds.size.height - contentInsets.bottom - contentInsets.top
|
||||
|
||||
// center things vertically
|
||||
var height = _displayList!.ascent + _displayList!.descent
|
||||
if height < fontSize/2 {
|
||||
height = fontSize/2 // set height to half the font size
|
||||
}
|
||||
let textY = (availableHeight - height) / 2 + _displayList!.descent + contentInsets.bottom
|
||||
_displayList!.position = CGPointMake(textX, textY)
|
||||
} else {
|
||||
guard _mathList != nil && self.font != nil else {
|
||||
_displayList = nil
|
||||
errorLabel?.frame = self.bounds
|
||||
self.setNeedsDisplay()
|
||||
return
|
||||
}
|
||||
// Ensure we have a valid font before attempting to typeset
|
||||
if self.font == nil {
|
||||
// No valid font - try to get default font
|
||||
if let defaultFont = MTFontManager.fontManager.defaultFont {
|
||||
self._font = defaultFont
|
||||
} else {
|
||||
// Cannot typeset without a font, clear display list
|
||||
_displayList = nil
|
||||
errorLabel?.frame = self.bounds
|
||||
self.setNeedsDisplay()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Use the effective width for layout
|
||||
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 {
|
||||
case .left: textX = contentInsets.left
|
||||
case .center: textX = (bounds.size.width - contentInsets.left - contentInsets.right - _displayList!.width) / 2 + contentInsets.left
|
||||
case .right: textX = bounds.size.width - _displayList!.width - contentInsets.right
|
||||
}
|
||||
let availableHeight = bounds.size.height - contentInsets.bottom - contentInsets.top
|
||||
|
||||
// center things vertically
|
||||
var height = _displayList!.ascent + _displayList!.descent
|
||||
if height < fontSize/2 {
|
||||
height = fontSize/2 // set height to half the font size
|
||||
}
|
||||
let textY = (availableHeight - height) / 2 + _displayList!.descent + contentInsets.bottom
|
||||
_displayList!.position = CGPointMake(textX, textY)
|
||||
errorLabel?.frame = self.bounds
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
@@ -305,6 +322,17 @@ public class MTMathUILabel : MTView {
|
||||
return CGSize(width: -1, height: -1)
|
||||
}
|
||||
|
||||
// Ensure we have a valid font before attempting to typeset
|
||||
if self.font == nil {
|
||||
// No valid font - try to get default font
|
||||
if let defaultFont = MTFontManager.fontManager.defaultFont {
|
||||
self._font = defaultFont
|
||||
} else {
|
||||
// Cannot typeset without a font
|
||||
return CGSize(width: -1, height: -1)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the maximum width to use
|
||||
var maxWidth: CGFloat = 0
|
||||
if _preferredMaxLayoutWidth > 0 {
|
||||
@@ -314,7 +342,7 @@ public class MTMathUILabel : MTView {
|
||||
}
|
||||
|
||||
var displayList:MTMathListDisplay? = nil
|
||||
displayList = MTTypesetter.createLineForMathList(_mathList, font: font, style: currentStyle, maxWidth: maxWidth)
|
||||
displayList = MTTypesetter.createLineForMathList(_mathList, font: self.font, style: currentStyle, maxWidth: maxWidth)
|
||||
|
||||
guard displayList != nil else {
|
||||
// Failed to create display list
|
||||
|
||||
@@ -73,12 +73,18 @@ func getInterElementSpaceArrayIndexForType(_ type:MTMathAtomType, row:Bool) -> I
|
||||
// They have the same spacing as ordinary except with ordinary.
|
||||
return 8;
|
||||
} else {
|
||||
assert(false, "Interelement space undefined for radical on the right. Treat radical as ordinary.")
|
||||
return Int.max
|
||||
// Treat radical as ordinary on the right side
|
||||
return 0
|
||||
}
|
||||
default:
|
||||
assert(false, "Interelement space undefined for type \(type)")
|
||||
return Int.max
|
||||
// Numbers, variables, and unary operators are treated as ordinary
|
||||
case .number, .variable, .unaryOperator:
|
||||
return 0
|
||||
// Decorative types (accent, underline, overline) are treated as ordinary
|
||||
case .accent, .underline, .overline:
|
||||
return 0
|
||||
// Special types that don't typically participate in spacing are treated as ordinary
|
||||
case .boundary, .space, .style, .table:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -958,30 +964,12 @@ class MTTypesetter {
|
||||
self.addDisplayLine()
|
||||
}
|
||||
|
||||
// Create the large operator display to check if we need line breaking
|
||||
let op = atom as! MTLargeOperator?
|
||||
// Add inter-element spacing before operator
|
||||
self.addInterElementSpace(prevNode, currentType:atom.type)
|
||||
|
||||
// Save state before creating display (makeLargeOp may add scripts to displayAtoms)
|
||||
let savedDisplayAtomsCount = displayAtoms.count
|
||||
let savedPosition = currentPosition
|
||||
let tempDisplay = self.makeLargeOp(op)
|
||||
let tempIsTooTall = (tempDisplay!.ascent + tempDisplay!.descent) > styleFont.fontSize * 2.5
|
||||
let tempIsTooWide = shouldBreakBeforeDisplay(tempDisplay!, prevNode: prevNode, displayType: atom.type)
|
||||
let shouldBreak = tempIsTooTall || tempIsTooWide
|
||||
|
||||
// Restore state (remove any scripts that were added)
|
||||
displayAtoms.removeLast(displayAtoms.count - savedDisplayAtomsCount)
|
||||
currentPosition = savedPosition
|
||||
|
||||
// Perform line break if needed
|
||||
if shouldBreak {
|
||||
performLineBreak()
|
||||
} else {
|
||||
self.addInterElementSpace(prevNode, currentType:atom.type)
|
||||
}
|
||||
|
||||
// Now create the display at the correct position (after spacing/line break)
|
||||
// Create and position the large operator display
|
||||
// makeLargeOp sets position, advances currentPosition.x, and adds scripts
|
||||
let op = atom as! MTLargeOperator?
|
||||
let display = self.makeLargeOp(op)
|
||||
displayAtoms.append(display!)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user