Implement early-exit optimization to avoid expensive width calculations when
we can determine that all remaining content will definitely fit on the current line.
The typesetter was incorrectly measuring line width when expressions contained
superscripts or subscripts (e.g., b²). After rendering a superscript, the line
is split into multiple display segments, but the width checking code was only
measuring the current segment, not the total visual line width.
Key changes:
- Use currentPosition.x to track actual horizontal position across all segments
- Calculate visualLineWidth = currentPosition.x + currentSegmentWidth
- Pass remainingWidth (maxWidth - currentPosition.x) to findBestBreakPoint
- Apply fix to both interatom breaking and inline text breaking
This fixes truncation issues where content like "Δ=b²-4ac avec a=1..." was
being clipped instead of wrapped to a new line.
Before: Each segment checked in isolation → segments appeared to fit individually
but total visual width exceeded maxWidth → content truncated/clipped
After: Total visual width tracked correctly → line breaking triggered when
actual visual width exceeds maxWidth → content wraps properly
Fixed line breaking that would split words like "équivaut" into "é" on one
line and "quivaut" on the next line, even though they're part of the same word.
Root cause analysis (from debug logging):
When text contains accented characters in decomposed form (e + combining
accent), the system processes them as separate atoms:
1. "é" is processed as an accent atom, composed, and added to currentLine
2. "quivaut " is processed as the next ordinary atom
3. Before adding "quivaut ", checkAndPerformInteratomLineBreak() is called
4. This function sees that adding "quivaut " would exceed maxWidth
5. It breaks and flushes the line with "é" at the end
6. "quivaut " starts on a new line
Result: "é" appears alone at the end of one line, "quivaut " on the next.
The fix:
Modified checkAndPerformInteratomLineBreak() in MTTypesetter.swift to detect
when we're about to break in the middle of a word.
This commit addresses three issues with math rendering:
1. Large operator limits positioning (continued from previous commit)
Modified makeLargeOp() and addLimitsToDisplay() to show limits above/below
in both display and text (inline) modes:
- Changed: op.limits && style == .display
- To: op.limits && (style == .display || style == .text)
This enables operators like \lim, \sum, and \prod to show subscripts/
superscripts above and below even in inline mode \(...\), not just in
display mode \[...\].
2. Fraction font size issue
Fixed fractions appearing too small in inline mode. Previously, fractions
used one style level smaller than their parent (standard LaTeX behavior):
- Display mode → fractions use text style (acceptable)
- Text mode →
Root cause:
Inline delimiters \(...\) insert \textstyle, forcing text mode. In text
mode, fractionStyle() returned style.inc(), making numerator/denominator
use script style (two levels smaller than display). This made fraction
numbers tiny compared to surrounding text in expressions like:
\(\frac{a}{b} = c\) - a, b were script-sized while c was text-sized
Solution:
Modified fractionStyle() to return the SAME style instead of incrementing:
func fractionStyle() -> MTLineStyle {
return style // Was: return style.inc()
}
This keeps fraction numerators/denominators at the same font size as
regular text, preventing them from becoming too small. Spacing and
positioning (numeratorShiftUp, etc.) still vary by parent style.
3. Non-regression fixes
Updated test expectations to match new fraction sizing behavior
Replace fixed fontSize × 1.5 spacing with adaptive height calculation based
on actual line content (ascent + descent), providing better visual spacing
for expressions with varying content heights.
Implement aesthetic break point selection to prefer natural break locations
(e.g., after operators) rather than arbitrary positions when line wrapping
mathematical expressions.