multiline fix with square root on second line
This commit is contained in:
@@ -540,4 +540,202 @@ class MTMathUILabelLineWrappingTests: XCTestCase {
|
||||
XCTAssertNotNil(label.displayList, "Display list should be created")
|
||||
XCTAssertNil(label.error, "Should have no rendering error")
|
||||
}
|
||||
|
||||
// MARK: - Tests for Complex Math Expressions with Line Breaking
|
||||
|
||||
func testComplexExpressionWithRadicalWrapping() {
|
||||
// This is the reported issue: y=x^{2}+3x+4x+9x+8x+8+\sqrt{\dfrac{3x^{2}+5x}{\cos x}}
|
||||
// The sqrt part is displayed on the second line and overlaps the first line
|
||||
let label = MTMathUILabel()
|
||||
label.latex = "y=x^{2}+3x+4x+9x+8x+8+\\sqrt{\\dfrac{3x^{2}+5x}{\\cos x}}"
|
||||
label.font = MTFontManager.fontManager.defaultFont
|
||||
|
||||
// Get unconstrained size first
|
||||
let unconstrainedSize = label.intrinsicContentSize
|
||||
XCTAssertGreaterThan(unconstrainedSize.width, 0, "Unconstrained width should be > 0")
|
||||
XCTAssertGreaterThan(unconstrainedSize.height, 0, "Unconstrained height should be > 0")
|
||||
|
||||
// Now constrain the width to force wrapping
|
||||
label.preferredMaxLayoutWidth = 200
|
||||
let constrainedSize = label.intrinsicContentSize
|
||||
|
||||
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
|
||||
XCTAssertLessThanOrEqual(constrainedSize.width, 200, "Width should not exceed constraint")
|
||||
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
|
||||
|
||||
// Layout and check for overlapping
|
||||
label.frame = CGRect(origin: .zero, size: constrainedSize)
|
||||
#if os(macOS)
|
||||
label.layout()
|
||||
#else
|
||||
label.layoutSubviews()
|
||||
#endif
|
||||
|
||||
XCTAssertNotNil(label.displayList, "Display list should be created")
|
||||
XCTAssertNil(label.error, "Should have no rendering error")
|
||||
|
||||
// Check that displays don't overlap by examining positions
|
||||
// Group displays by line (similar y positions) and check for overlap between lines
|
||||
if let displayList = label.displayList {
|
||||
// Group displays by line based on their y position
|
||||
var lineGroups: [[MTDisplay]] = []
|
||||
var currentLineDisplays: [MTDisplay] = []
|
||||
var currentLineY: CGFloat? = nil
|
||||
let yTolerance: CGFloat = 15.0 // Displays within 15 units are considered on same line (accounts for superscripts/subscripts)
|
||||
|
||||
for display in displayList.subDisplays {
|
||||
if let lineY = currentLineY {
|
||||
if abs(display.position.y - lineY) < yTolerance {
|
||||
// Same line
|
||||
currentLineDisplays.append(display)
|
||||
} else {
|
||||
// New line
|
||||
lineGroups.append(currentLineDisplays)
|
||||
currentLineDisplays = [display]
|
||||
currentLineY = display.position.y
|
||||
}
|
||||
} else {
|
||||
// First display
|
||||
currentLineDisplays = [display]
|
||||
currentLineY = display.position.y
|
||||
}
|
||||
}
|
||||
if !currentLineDisplays.isEmpty {
|
||||
lineGroups.append(currentLineDisplays)
|
||||
}
|
||||
|
||||
// Check for overlap between consecutive lines
|
||||
for i in 1..<lineGroups.count {
|
||||
let previousLine = lineGroups[i-1]
|
||||
let currentLine = lineGroups[i]
|
||||
|
||||
// Find the minimum bottom edge of previous line (Y-up: bottom = pos - desc, smaller Y)
|
||||
let previousLineMinBottom = previousLine.map { $0.position.y - $0.descent }.min() ?? 0
|
||||
|
||||
// Find the maximum top edge of current line (Y-up: top = pos + asc, larger Y)
|
||||
let currentLineMaxTop = currentLine.map { $0.position.y + $0.ascent }.max() ?? 0
|
||||
|
||||
// Check for overlap: if current line's top > previous line's bottom, they overlap
|
||||
// (In Y-up coordinate system: positive Y is upward, negative Y is downward)
|
||||
// Allow 0.5 points tolerance for floating-point precision and small adjustments
|
||||
XCTAssertLessThanOrEqual(currentLineMaxTop, previousLineMinBottom + 0.5,
|
||||
"Line \(i) (top at \(currentLineMaxTop)) overlaps with line \(i-1) (bottom at \(previousLineMinBottom))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testRadicalWithFractionInsideWrapping() {
|
||||
// Simplified version: just a radical with a fraction inside
|
||||
let label = MTMathUILabel()
|
||||
label.latex = "x+y+z+\\sqrt{\\dfrac{a}{b}}"
|
||||
label.font = MTFontManager.fontManager.defaultFont
|
||||
|
||||
let unconstrainedSize = label.intrinsicContentSize
|
||||
|
||||
label.preferredMaxLayoutWidth = 100
|
||||
let constrainedSize = label.intrinsicContentSize
|
||||
|
||||
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
|
||||
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
|
||||
|
||||
label.frame = CGRect(origin: .zero, size: constrainedSize)
|
||||
#if os(macOS)
|
||||
label.layout()
|
||||
#else
|
||||
label.layoutSubviews()
|
||||
#endif
|
||||
|
||||
XCTAssertNotNil(label.displayList, "Display list should be created")
|
||||
XCTAssertNil(label.error, "Should have no rendering error")
|
||||
}
|
||||
|
||||
func testTallElementsOnSecondLine() {
|
||||
// Test case with tall fractions and radicals breaking to second line
|
||||
let label = MTMathUILabel()
|
||||
label.latex = "a+b+c+\\dfrac{x^2+y^2}{z^2}+\\sqrt{\\dfrac{p}{q}}"
|
||||
label.font = MTFontManager.fontManager.defaultFont
|
||||
|
||||
let unconstrainedSize = label.intrinsicContentSize
|
||||
|
||||
label.preferredMaxLayoutWidth = 150
|
||||
let constrainedSize = label.intrinsicContentSize
|
||||
|
||||
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
|
||||
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
|
||||
|
||||
label.frame = CGRect(origin: .zero, size: constrainedSize)
|
||||
#if os(macOS)
|
||||
label.layout()
|
||||
#else
|
||||
label.layoutSubviews()
|
||||
#endif
|
||||
|
||||
XCTAssertNotNil(label.displayList, "Display list should be created")
|
||||
XCTAssertNil(label.error, "Should have no rendering error")
|
||||
|
||||
// Verify no overlapping displays between lines
|
||||
if let displayList = label.displayList {
|
||||
// Group displays by line
|
||||
var lineGroups: [[MTDisplay]] = []
|
||||
var currentLineDisplays: [MTDisplay] = []
|
||||
var currentLineY: CGFloat? = nil
|
||||
let yTolerance: CGFloat = 15.0
|
||||
|
||||
for display in displayList.subDisplays {
|
||||
if let lineY = currentLineY {
|
||||
if abs(display.position.y - lineY) < yTolerance {
|
||||
currentLineDisplays.append(display)
|
||||
} else {
|
||||
lineGroups.append(currentLineDisplays)
|
||||
currentLineDisplays = [display]
|
||||
currentLineY = display.position.y
|
||||
}
|
||||
} else {
|
||||
currentLineDisplays = [display]
|
||||
currentLineY = display.position.y
|
||||
}
|
||||
}
|
||||
if !currentLineDisplays.isEmpty {
|
||||
lineGroups.append(currentLineDisplays)
|
||||
}
|
||||
|
||||
// Check for overlap between consecutive lines
|
||||
for i in 1..<lineGroups.count {
|
||||
let previousLine = lineGroups[i-1]
|
||||
let currentLine = lineGroups[i]
|
||||
|
||||
let previousLineMinBottom = previousLine.map { $0.position.y - $0.descent }.min() ?? 0
|
||||
let currentLineMaxTop = currentLine.map { $0.position.y + $0.ascent }.max() ?? 0
|
||||
|
||||
// Allow 0.5 points tolerance for floating-point precision
|
||||
XCTAssertLessThanOrEqual(currentLineMaxTop, previousLineMinBottom + 0.5,
|
||||
"Line \(i) overlaps with line \(i-1)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMultipleLinesWithVaryingHeights() {
|
||||
// Test expression that should wrap to multiple lines with different heights
|
||||
let label = MTMathUILabel()
|
||||
label.latex = "x+y+z+a+b+c+\\sqrt{d}+e+f+g+h+\\dfrac{i}{j}+k"
|
||||
label.font = MTFontManager.fontManager.defaultFont
|
||||
|
||||
let unconstrainedSize = label.intrinsicContentSize
|
||||
|
||||
label.preferredMaxLayoutWidth = 120
|
||||
let constrainedSize = label.intrinsicContentSize
|
||||
|
||||
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
|
||||
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
|
||||
|
||||
label.frame = CGRect(origin: .zero, size: constrainedSize)
|
||||
#if os(macOS)
|
||||
label.layout()
|
||||
#else
|
||||
label.layoutSubviews()
|
||||
#endif
|
||||
|
||||
XCTAssertNotNil(label.displayList, "Display list should be created")
|
||||
XCTAssertNil(label.error, "Should have no rendering error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user