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) {
|
override public func draw(_ dirtyRect: MTRect) {
|
||||||
super.draw(dirtyRect)
|
super.draw(dirtyRect)
|
||||||
if self.mathList == nil { return }
|
if self.mathList == nil { return }
|
||||||
|
if self.font == nil { return }
|
||||||
|
|
||||||
// drawing code
|
// drawing code
|
||||||
let context = MTGraphicsGetCurrentContext()!
|
let context = MTGraphicsGetCurrentContext()!
|
||||||
@@ -253,48 +254,64 @@ public class MTMathUILabel : MTView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _layoutSubviews() {
|
func _layoutSubviews() {
|
||||||
if _mathList != nil {
|
guard _mathList != nil && self.font != nil else {
|
||||||
// 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 {
|
|
||||||
_displayList = nil
|
_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
|
errorLabel?.frame = self.bounds
|
||||||
self.setNeedsDisplay()
|
self.setNeedsDisplay()
|
||||||
}
|
}
|
||||||
@@ -305,6 +322,17 @@ public class MTMathUILabel : MTView {
|
|||||||
return CGSize(width: -1, height: -1)
|
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
|
// Determine the maximum width to use
|
||||||
var maxWidth: CGFloat = 0
|
var maxWidth: CGFloat = 0
|
||||||
if _preferredMaxLayoutWidth > 0 {
|
if _preferredMaxLayoutWidth > 0 {
|
||||||
@@ -314,7 +342,7 @@ public class MTMathUILabel : MTView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var displayList:MTMathListDisplay? = nil
|
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 {
|
guard displayList != nil else {
|
||||||
// Failed to create display list
|
// 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.
|
// They have the same spacing as ordinary except with ordinary.
|
||||||
return 8;
|
return 8;
|
||||||
} else {
|
} else {
|
||||||
assert(false, "Interelement space undefined for radical on the right. Treat radical as ordinary.")
|
// Treat radical as ordinary on the right side
|
||||||
return Int.max
|
return 0
|
||||||
}
|
}
|
||||||
default:
|
// Numbers, variables, and unary operators are treated as ordinary
|
||||||
assert(false, "Interelement space undefined for type \(type)")
|
case .number, .variable, .unaryOperator:
|
||||||
return Int.max
|
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()
|
self.addDisplayLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the large operator display to check if we need line breaking
|
// Add inter-element spacing before operator
|
||||||
let op = atom as! MTLargeOperator?
|
self.addInterElementSpace(prevNode, currentType:atom.type)
|
||||||
|
|
||||||
// Save state before creating display (makeLargeOp may add scripts to displayAtoms)
|
// Create and position the large operator display
|
||||||
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)
|
|
||||||
// makeLargeOp sets position, advances currentPosition.x, and adds scripts
|
// makeLargeOp sets position, advances currentPosition.x, and adds scripts
|
||||||
|
let op = atom as! MTLargeOperator?
|
||||||
let display = self.makeLargeOp(op)
|
let display = self.makeLargeOp(op)
|
||||||
displayAtoms.append(display!)
|
displayAtoms.append(display!)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user