From 7c9766f825b6abdeb9b8fbff3806001c54642272 Mon Sep 17 00:00:00 2001 From: Nicolas Guillot Date: Tue, 30 Sep 2025 10:17:08 +0200 Subject: [PATCH] [MTMathListBuilder][MTTypesetter] add \cfrac support --- Sources/SwiftMath/MathRender/MTMathList.swift | 6 +++ .../MathRender/MTMathListBuilder.swift | 52 ++++++++++++++++++- .../SwiftMath/MathRender/MTTypesetter.swift | 19 +++++-- 3 files changed, 73 insertions(+), 4 deletions(-) mode change 100755 => 100644 Sources/SwiftMath/MathRender/MTMathList.swift mode change 100755 => 100644 Sources/SwiftMath/MathRender/MTTypesetter.swift diff --git a/Sources/SwiftMath/MathRender/MTMathList.swift b/Sources/SwiftMath/MathRender/MTMathList.swift old mode 100755 new mode 100644 index 8c99949..de45252 --- a/Sources/SwiftMath/MathRender/MTMathList.swift +++ b/Sources/SwiftMath/MathRender/MTMathList.swift @@ -328,6 +328,10 @@ public class MTFraction: MTMathAtom { public var rightDelimiter = "" public var numerator: MTMathList? public var denominator: MTMathList? + + // Continued fraction properties + public var isContinuedFraction: Bool = false + public var alignment: String = "c" // "l", "r", "c" for left, right, center init(_ frac: MTFraction?) { super.init(frac) @@ -338,6 +342,8 @@ public class MTFraction: MTMathAtom { self.hasRule = frac.hasRule self.leftDelimiter = frac.leftDelimiter self.rightDelimiter = frac.rightDelimiter + self.isContinuedFraction = frac.isContinuedFraction + self.alignment = frac.alignment } } diff --git a/Sources/SwiftMath/MathRender/MTMathListBuilder.swift b/Sources/SwiftMath/MathRender/MTMathListBuilder.swift index 292a3bc..4460e13 100644 --- a/Sources/SwiftMath/MathRender/MTMathListBuilder.swift +++ b/Sources/SwiftMath/MathRender/MTMathListBuilder.swift @@ -508,7 +508,14 @@ public struct MTMathListBuilder { } if atom.type == .fraction { if let frac = atom as? MTFraction { - if frac.hasRule { + if frac.isContinuedFraction { + // Generate \cfrac with optional alignment + if frac.alignment != "c" { + str += "\\cfrac[\(frac.alignment)]{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}" + } else { + str += "\\cfrac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}" + } + } else if frac.hasRule { str += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}" } else { let command: String @@ -685,6 +692,28 @@ public struct MTMathListBuilder { } else if command == "frac" { // A fraction command has 2 arguments let frac = MTFraction() + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + return frac; + } else if command == "cfrac" { + // A continued fraction command with optional alignment and 2 arguments + let frac = MTFraction() + frac.isContinuedFraction = true + + // Parse optional alignment parameter [l], [r], [c] + skipSpaces() + if hasCharacters && string[currentCharIndex] == "[" { + _ = getNextCharacter() // consume '[' + let alignmentChar = getNextCharacter() + if alignmentChar == "l" || alignmentChar == "r" || alignmentChar == "c" { + frac.alignment = String(alignmentChar) + } + // Consume closing ']' + if hasCharacters && string[currentCharIndex] == "]" { + _ = getNextCharacter() + } + } + frac.numerator = self.buildInternal(true) frac.denominator = self.buildInternal(true) return frac; @@ -981,6 +1010,27 @@ public struct MTMathListBuilder { return accent } else if command == "frac" { let frac = MTFraction() + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + return frac + } else if command == "cfrac" { + let frac = MTFraction() + frac.isContinuedFraction = true + + // Parse optional alignment parameter [l], [r], [c] + skipSpaces() + if hasCharacters && string[currentCharIndex] == "[" { + _ = getNextCharacter() // consume '[' + let alignmentChar = getNextCharacter() + if alignmentChar == "l" || alignmentChar == "r" || alignmentChar == "c" { + frac.alignment = String(alignmentChar) + } + // Consume closing ']' + if hasCharacters && string[currentCharIndex] == "]" { + _ = getNextCharacter() + } + } + frac.numerator = self.buildInternal(true) frac.denominator = self.buildInternal(true) return frac diff --git a/Sources/SwiftMath/MathRender/MTTypesetter.swift b/Sources/SwiftMath/MathRender/MTTypesetter.swift old mode 100755 new mode 100644 index 609767d..6a6f0c8 --- a/Sources/SwiftMath/MathRender/MTTypesetter.swift +++ b/Sources/SwiftMath/MathRender/MTTypesetter.swift @@ -990,9 +990,22 @@ class MTTypesetter { func makeFraction(_ frac:MTFraction?) -> MTDisplay? { // lay out the parts of the fraction - let fractionStyle = self.fractionStyle; - let numeratorDisplay = MTTypesetter.createLineForMathList(frac!.numerator, font:font, style:fractionStyle(), cramped:false) - let denominatorDisplay = MTTypesetter.createLineForMathList(frac!.denominator, font:font, style:fractionStyle(), cramped:true) + let numeratorStyle: MTLineStyle + let denominatorStyle: MTLineStyle + + if frac!.isContinuedFraction { + // Continued fractions always use display style + numeratorStyle = .display + denominatorStyle = .display + } else { + // Regular fractions use adaptive style + let fractionStyle = self.fractionStyle; + numeratorStyle = fractionStyle() + denominatorStyle = fractionStyle() + } + + let numeratorDisplay = MTTypesetter.createLineForMathList(frac!.numerator, font:font, style:numeratorStyle, cramped:false) + let denominatorDisplay = MTTypesetter.createLineForMathList(frac!.denominator, font:font, style:denominatorStyle, cramped:true) // determine the location of the numerator var numeratorShiftUp = self.numeratorShiftUp(frac!.hasRule)