[MTMathListBuilder] enhanced \begin{cases} environment support

No Need to use the & character at line end. The previous notation is still supported.
This commit is contained in:
Nicolas Guillot
2025-09-30 10:03:17 +02:00
parent 5530f3910f
commit 225948c725
2 changed files with 123 additions and 6 deletions

View File

@@ -898,19 +898,21 @@ public class MTMathAtomFactory {
return table return table
} else if env == "cases" { } else if env == "cases" {
if table.numColumns != 2 { if table.numColumns != 1 && table.numColumns != 2 {
let message = "cases environment can only have 2 columns" let message = "cases environment can have 1 or 2 columns"
if error == nil { if error == nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message]) error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
} }
return nil return nil
} }
table.interRowAdditionalSpacing = 0 table.interRowAdditionalSpacing = 0
table.interColumnSpacing = 18 table.interColumnSpacing = 18
table.set(alignment: .left, forColumn: 0) table.set(alignment: .left, forColumn: 0)
table.set(alignment: .left, forColumn: 1) if table.numColumns == 2 {
table.set(alignment: .left, forColumn: 1)
}
let style = MTMathStyle(style: .text) let style = MTMathStyle(style: .text)
for i in 0..<table.cells.count { for i in 0..<table.cells.count {

View File

@@ -130,6 +130,111 @@ public struct MTMathListBuilder {
_ = readCommand() _ = readCommand()
} }
} }
// Analyze cases environment content to detect if & characters are present
mutating func analyzeCasesContent() -> Bool {
let savedIndex = currentCharIndex
var braceDepth = 0
var hasAmpersand = false
while currentCharIndex < string.endIndex {
let char = string[currentCharIndex]
currentCharIndex = string.index(after: currentCharIndex)
switch char {
case "{":
braceDepth += 1
case "}":
braceDepth -= 1
if braceDepth < 0 {
// Found unmatched closing brace, likely end of environment content
break
}
case "&":
if braceDepth == 0 {
// Found & at top level (not inside nested braces)
hasAmpersand = true
break
}
case "\\":
// Skip the next character if it's a command
if currentCharIndex < string.endIndex {
let nextChar = string[currentCharIndex]
if nextChar == "e" {
// Check if this might be \end{cases}
let remainingChars = string[currentCharIndex...]
if remainingChars.hasPrefix("end{cases}") {
break
}
}
currentCharIndex = string.index(after: currentCharIndex)
}
default:
break
}
}
// Restore position
currentCharIndex = savedIndex
return hasAmpersand
}
// Build single-column cases environment (no & separators)
mutating func buildSingleColumnCases(firstList: MTMathList?, isRow: Bool) -> MTMathAtom? {
let oldEnv = self.currentEnv
currentEnv = MTEnvProperties(name: "cases")
var rows = [[MTMathList]]()
var currentRow = 0
// Add first list if provided
if let first = firstList {
rows.append([first])
if isRow {
currentEnv!.numRows += 1
currentRow += 1
rows.append([MTMathList]())
}
} else {
rows.append([MTMathList]())
}
while !currentEnv!.ended && self.hasCharacters {
let list = self.buildInternal(false)
if list == nil {
return nil
}
if currentEnv!.numRows > currentRow {
// New row triggered by \\
currentRow = currentEnv!.numRows
rows.append([list!])
} else {
// Continue current row (should be single cell)
if rows[currentRow].isEmpty {
rows[currentRow].append(list!)
} else {
// Append to existing cell in single-column mode
rows[currentRow][0].append(list!)
}
}
}
if !currentEnv!.ended {
self.setError(.missingEnd, message: "Missing \\end")
return nil
}
var error: NSError? = self.error
let table = MTMathAtomFactory.table(withEnvironment: "cases", rows: rows, error: &error)
if table == nil && self.error == nil {
self.error = error
return nil
}
self.currentEnv = oldEnv
return table
}
mutating func expectCharacter(_ ch: Character) -> Bool { mutating func expectCharacter(_ ch: Character) -> Bool {
MTAssertNotSpace(ch) MTAssertNotSpace(ch)
@@ -970,9 +1075,19 @@ public struct MTMathListBuilder {
} }
mutating func buildTable(env: String?, firstList: MTMathList?, isRow: Bool) -> MTMathAtom? { mutating func buildTable(env: String?, firstList: MTMathList?, isRow: Bool) -> MTMathAtom? {
// Special handling for cases environment - detect single vs two column structure
if env == "cases" {
let hasAmpersand = analyzeCasesContent()
if !hasAmpersand {
// Parse as single-column cases environment
return buildSingleColumnCases(firstList: firstList, isRow: isRow)
}
// If ampersand found, continue with standard two-column parsing
}
// Save the current env till an new one gets built. // Save the current env till an new one gets built.
let oldEnv = self.currentEnv let oldEnv = self.currentEnv
currentEnv = MTEnvProperties(name: env) currentEnv = MTEnvProperties(name: env)
var currentRow = 0 var currentRow = 0