diff --git a/ALGORITHM.md b/ALGORITHM.md index 1ac0cafa..66a58304 100644 --- a/ALGORITHM.md +++ b/ALGORITHM.md @@ -77,6 +77,7 @@ Defined in `MTMathList.h` as `MTMathAtomType`. | `kMTMathAtomUnderline` | Under | Rule 10. | | `kMTMathAtomOverline` | Over | Rule 9. | | `kMTMathAtomAccent` | Acc | Rule 12. | +| `kMTMathAtomStack` | Acc (over/under extensible) | iosMath extension. Generic over/under atom used for `\overrightarrow`, `\overleftarrow`, `\overleftrightarrow`, `\underrightarrow`, `\underleftarrow`, `\underleftrightarrow`, `\overbrace`, `\underbrace`. Retyped to `displayClass` (default Ord) before Rule 16 spacing lookup. Renders via `MTTypesetter.makeStack:` → `MTStackDisplay`. Extensible over/under rows use OpenType `stretchStackGapAboveMin` / `stretchStackGapBelowMin` for vertical clearance. | | `kMTMathAtomBoundary` | (for \left, \right) | Cannot appear in a `MTMathList` directly; only on `MTInner.leftBoundary` / `.rightBoundary`. Rule 19 analogue is in `MTTypesetter.makeInner:atIndex:`. | | `kMTMathAtomSpace` | glue/kern | Rule 2. | | `kMTMathAtomStyle` | style item | Rule 3. | diff --git a/CLAUDE.md b/CLAUDE.md index 00d7fc79..b65476e2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,7 +42,7 @@ LaTeX string → **MTMathListBuilder** (parser) → **MTMathList** (atom tree) **`iosMath/lib/`** — Parsing and data model: - `MTMathListBuilder` — Parses LaTeX strings into an `MTMathList`. This is the entry point for all LaTeX input. - `MTMathList` / `MTMathAtom` — The intermediate representation. Math is modeled as a linked list of typed atoms (fraction, radical, inner, accent, etc.). -- `MTMathAtomFactory` — Maps LaTeX commands (e.g. `\sin`, `\alpha`, `\frac`) to their corresponding atoms and Unicode code points. +- `MTMathAtomFactory` — Maps LaTeX commands (e.g. `\sin`, `\alpha`, `\frac`) to their corresponding atoms and Unicode code points. Also owns the stack-command table that backs `MTMathStack` (the generic over/under atom used for `\overrightarrow`, `\overbrace`, and relatives). **`iosMath/render/`** — Layout and display: - `MTMathUILabel` — The public-facing `UIView`/`NSView` subclass. Users instantiate this and set its `latex` property. diff --git a/MathExamples.h b/MathExamples.h index a86dc1ed..61c71991 100644 --- a/MathExamples.h +++ b/MathExamples.h @@ -93,7 +93,7 @@ static inline NSArray* MathDemoFormulas(void) { ]; } -/// 56 formulae exercising specific typesetter features and edge cases. +/// 68 formulae exercising specific typesetter features and edge cases. static inline NSArray* MathTestFormulas(void) { return @[ // 0: Basic arithmetic @@ -213,6 +213,30 @@ static inline NSArray* MathTestFormulas(void) { @"\\bigl( \\sum_{i=1}^n x_i \\bigr) \\quad = \\quad \\left( \\sum_{i=1}^n x_i \\right)", // 55: Relation-class explicit delimiters @"a \\bigm| b \\quad \\Bigm\\| \\quad c \\biggm\\Vert d", + // 56: Narrow vector (single-glyph fast path) + @"\\overrightarrow{x}", + // 57: Two-letter vector + @"\\overrightarrow{AB}", + // 58: Leftward arrow + @"\\overleftarrow{x}", + // 59: Both-direction arrow + @"\\overleftrightarrow{ABC}", + // 60: Wide base — exercises arrow assembly + @"\\overrightarrow{ABCDEFGH}", + // 61: Overbrace stretchy + @"\\overbrace{a+b+c+d}", + // 62: Underbrace stretchy + @"\\underbrace{1+2+3+4+5}", + // 63: Brace with scripts on top/bottom + @"\\overbrace{a+b+c}^{n} + \\underbrace{d+e+f}_{m}", + // 64: Stack with scripts + @"\\overrightarrow{x}^{2} + \\overrightarrow{y}_{i}", + // 65: Nested stacks + @"\\overrightarrow{\\overleftarrow{x}}", + // 66: Mixed arrow directions + @"\\overrightarrow{x} \\cdot \\overleftarrow{y}", + // 67: Nested brace over arrow + @"\\overbrace{\\overrightarrow{AB}}^{\\text{unit}}", ]; } diff --git a/iosMath/fonts/latinmodern-math.plist b/iosMath/fonts/latinmodern-math.plist index 9db21a40..9934f857 100644 --- a/iosMath/fonts/latinmodern-math.plist +++ b/iosMath/fonts/latinmodern-math.plist @@ -5072,6 +5072,3325 @@ UpperLimitGapMin 200 + h_assembly + + arrowboth + + italic + 0 + parts + + + advance + 499 + endConnector + 166 + extender + + glyph + arrowboth.lft + startConnector + 0 + + + advance + 332 + endConnector + 332 + extender + + glyph + arrowboth.ex + startConnector + 332 + + + advance + 499 + endConnector + 0 + extender + + glyph + arrowboth.rt + startConnector + 166 + + + + arrowdblboth + + italic + 0 + parts + + + advance + 533 + endConnector + 178 + extender + + glyph + arrowdblboth.lft + startConnector + 0 + + + advance + 356 + endConnector + 356 + extender + + glyph + arrowdblboth.ex + startConnector + 356 + + + advance + 533 + endConnector + 0 + extender + + glyph + arrowdblboth.rt + startConnector + 178 + + + + arrowdblleft + + italic + 0 + parts + + + advance + 504 + endConnector + 168 + extender + + glyph + arrowdblleft.lft + startConnector + 0 + + + advance + 336 + endConnector + 336 + extender + + glyph + arrowdblleft.ex + startConnector + 336 + + + advance + 505 + endConnector + 0 + extender + + glyph + arrowdblleft.rt + startConnector + 168 + + + + arrowdblright + + italic + 0 + parts + + + advance + 505 + endConnector + 168 + extender + + glyph + arrowdblright.lft + startConnector + 0 + + + advance + 336 + endConnector + 336 + extender + + glyph + arrowdblright.ex + startConnector + 336 + + + advance + 504 + endConnector + 0 + extender + + glyph + arrowdblright.rt + startConnector + 168 + + + + arrowleft + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + arrowleft.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + arrowleft.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + arrowleft.rt + startConnector + 169 + + + + arrowright + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + arrowright.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + arrowright.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + arrowright.rt + startConnector + 169 + + + + dbllowlinecmb + + italic + 0 + parts + + + advance + 189 + endConnector + 95 + extender + + glyph + dbllowlinecmb.lft + startConnector + 0 + + + advance + 190 + endConnector + 190 + extender + + glyph + dbllowlinecmb.ex + startConnector + 190 + + + advance + 189 + endConnector + 0 + extender + + glyph + dbllowlinecmb.rt + startConnector + 95 + + + + dbloverlinecmb + + italic + 0 + parts + + + advance + 189 + endConnector + 95 + extender + + glyph + dbloverlinecmb.lft + startConnector + 0 + + + advance + 190 + endConnector + 190 + extender + + glyph + dbloverlinecmb.ex + startConnector + 190 + + + advance + 189 + endConnector + 0 + extender + + glyph + dbloverlinecmb.rt + startConnector + 95 + + + + equal + + italic + 0 + parts + + + advance + 222 + endConnector + 111 + extender + + glyph + equal.lft + startConnector + 0 + + + advance + 222 + endConnector + 222 + extender + + glyph + equal.ex + startConnector + 222 + + + advance + 222 + endConnector + 0 + extender + + glyph + equal.rt + startConnector + 111 + + + + equivalence + + italic + 0 + parts + + + advance + 222 + endConnector + 111 + extender + + glyph + equivalence.lft + startConnector + 0 + + + advance + 222 + endConnector + 222 + extender + + glyph + equivalence.ex + startConnector + 222 + + + advance + 222 + endConnector + 0 + extender + + glyph + equivalence.rt + startConnector + 111 + + + + lowlinecmb + + italic + 0 + parts + + + advance + 189 + endConnector + 95 + extender + + glyph + lowlinecmb.lft + startConnector + 0 + + + advance + 190 + endConnector + 190 + extender + + glyph + lowlinecmb.ex + startConnector + 190 + + + advance + 189 + endConnector + 0 + extender + + glyph + lowlinecmb.rt + startConnector + 95 + + + + minus + + italic + 0 + parts + + + advance + 222 + endConnector + 111 + extender + + glyph + minus.lft + startConnector + 0 + + + advance + 222 + endConnector + 222 + extender + + glyph + minus.ex + startConnector + 222 + + + advance + 222 + endConnector + 0 + extender + + glyph + minus.rt + startConnector + 111 + + + + overlinecmb + + italic + 0 + parts + + + advance + 189 + endConnector + 95 + extender + + glyph + overlinecmb.lft + startConnector + 0 + + + advance + 190 + endConnector + 190 + extender + + glyph + overlinecmb.ex + startConnector + 190 + + + advance + 189 + endConnector + 0 + extender + + glyph + overlinecmb.rt + startConnector + 95 + + + + uni034D + + italic + 0 + parts + + + advance + 226 + endConnector + 76 + extender + + glyph + uni034D.lft + startConnector + 0 + + + advance + 151 + endConnector + 151 + extender + + glyph + uni034D.ex + startConnector + 151 + + + advance + 226 + endConnector + 0 + extender + + glyph + uni034D.rt + startConnector + 76 + + + + uni20D0 + + italic + 0 + parts + + + advance + 208 + endConnector + 70 + extender + + glyph + uni20D0.lft + startConnector + 0 + + + advance + 139 + endConnector + 139 + extender + + glyph + uni20D0.ex + startConnector + 139 + + + advance + 208 + endConnector + 0 + extender + + glyph + uni20D0.rt + startConnector + 70 + + + + uni20D1 + + italic + 0 + parts + + + advance + 208 + endConnector + 70 + extender + + glyph + uni20D1.lft + startConnector + 0 + + + advance + 139 + endConnector + 139 + extender + + glyph + uni20D1.ex + startConnector + 139 + + + advance + 208 + endConnector + 0 + extender + + glyph + uni20D1.rt + startConnector + 70 + + + + uni20D6 + + italic + 0 + parts + + + advance + 205 + endConnector + 69 + extender + + glyph + uni20D6.lft + startConnector + 0 + + + advance + 137 + endConnector + 137 + extender + + glyph + uni20D6.ex + startConnector + 137 + + + advance + 205 + endConnector + 0 + extender + + glyph + uni20D6.rt + startConnector + 69 + + + + uni20D7 + + italic + 0 + parts + + + advance + 205 + endConnector + 69 + extender + + glyph + uni20D7.lft + startConnector + 0 + + + advance + 137 + endConnector + 137 + extender + + glyph + uni20D7.ex + startConnector + 137 + + + advance + 205 + endConnector + 0 + extender + + glyph + uni20D7.rt + startConnector + 69 + + + + uni20E1 + + italic + 0 + parts + + + advance + 226 + endConnector + 76 + extender + + glyph + uni20E1.lft + startConnector + 0 + + + advance + 151 + endConnector + 151 + extender + + glyph + uni20E1.ex + startConnector + 151 + + + advance + 226 + endConnector + 0 + extender + + glyph + uni20E1.rt + startConnector + 76 + + + + uni20E9 + + italic + 0 + parts + + + advance + 1493 + endConnector + 498 + extender + + glyph + uni23B4.lft + startConnector + 0 + + + advance + 995 + endConnector + 995 + extender + + glyph + uni23B4.ex + startConnector + 995 + + + advance + 1492 + endConnector + 0 + extender + + glyph + uni23B4.rt + startConnector + 498 + + + + uni20EC + + italic + 0 + parts + + + advance + 208 + endConnector + 70 + extender + + glyph + uni20EC.lft + startConnector + 0 + + + advance + 139 + endConnector + 139 + extender + + glyph + uni20EC.ex + startConnector + 139 + + + advance + 208 + endConnector + 0 + extender + + glyph + uni20EC.rt + startConnector + 70 + + + + uni20ED + + italic + 0 + parts + + + advance + 208 + endConnector + 70 + extender + + glyph + uni20ED.lft + startConnector + 0 + + + advance + 139 + endConnector + 139 + extender + + glyph + uni20ED.ex + startConnector + 139 + + + advance + 208 + endConnector + 0 + extender + + glyph + uni20ED.rt + startConnector + 70 + + + + uni20EE + + italic + 0 + parts + + + advance + 205 + endConnector + 69 + extender + + glyph + uni20EE.lft + startConnector + 0 + + + advance + 137 + endConnector + 137 + extender + + glyph + uni20EE.ex + startConnector + 137 + + + advance + 205 + endConnector + 0 + extender + + glyph + uni20EE.rt + startConnector + 69 + + + + uni20EF + + italic + 0 + parts + + + advance + 205 + endConnector + 69 + extender + + glyph + uni20EF.lft + startConnector + 0 + + + advance + 137 + endConnector + 137 + extender + + glyph + uni20EF.ex + startConnector + 137 + + + advance + 205 + endConnector + 0 + extender + + glyph + uni20EF.rt + startConnector + 69 + + + + uni219A + + italic + 0 + parts + + + advance + 386 + endConnector + 49 + extender + + glyph + uni219A.lft + startConnector + 0 + + + advance + 97 + endConnector + 97 + extender + + glyph + uni219A.ex + startConnector + 97 + + + advance + 386 + endConnector + 49 + extender + + glyph + uni219A.md + startConnector + 49 + + + advance + 97 + endConnector + 97 + extender + + glyph + uni219A.ex + startConnector + 97 + + + advance + 386 + endConnector + 0 + extender + + glyph + uni219A.rt + startConnector + 49 + + + + uni219B + + italic + 0 + parts + + + advance + 386 + endConnector + 48 + extender + + glyph + uni219B.lft + startConnector + 0 + + + advance + 96 + endConnector + 96 + extender + + glyph + uni219B.ex + startConnector + 96 + + + advance + 386 + endConnector + 48 + extender + + glyph + uni219B.md + startConnector + 48 + + + advance + 96 + endConnector + 96 + extender + + glyph + uni219B.ex + startConnector + 96 + + + advance + 386 + endConnector + 0 + extender + + glyph + uni219B.rt + startConnector + 48 + + + + uni219E + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni219E.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni219E.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni219E.rt + startConnector + 169 + + + + uni21A0 + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21A0.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21A0.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21A0.rt + startConnector + 169 + + + + uni21A2 + + italic + 0 + parts + + + advance + 580 + endConnector + 193 + extender + + glyph + uni21A2.lft + startConnector + 0 + + + advance + 386 + endConnector + 386 + extender + + glyph + uni21A2.ex + startConnector + 386 + + + advance + 580 + endConnector + 0 + extender + + glyph + uni21A2.rt + startConnector + 193 + + + + uni21A3 + + italic + 0 + parts + + + advance + 580 + endConnector + 193 + extender + + glyph + uni21A3.lft + startConnector + 0 + + + advance + 386 + endConnector + 386 + extender + + glyph + uni21A3.ex + startConnector + 386 + + + advance + 580 + endConnector + 0 + extender + + glyph + uni21A3.rt + startConnector + 193 + + + + uni21A4 + + italic + 0 + parts + + + advance + 499 + endConnector + 167 + extender + + glyph + uni21A4.lft + startConnector + 0 + + + advance + 333 + endConnector + 333 + extender + + glyph + uni21A4.ex + startConnector + 333 + + + advance + 499 + endConnector + 0 + extender + + glyph + uni21A4.rt + startConnector + 167 + + + + uni21A6 + + italic + 0 + parts + + + advance + 499 + endConnector + 167 + extender + + glyph + uni21A6.lft + startConnector + 0 + + + advance + 333 + endConnector + 333 + extender + + glyph + uni21A6.ex + startConnector + 333 + + + advance + 499 + endConnector + 0 + extender + + glyph + uni21A6.rt + startConnector + 167 + + + + uni21A9 + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21A9.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21A9.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21A9.rt + startConnector + 169 + + + + uni21AA + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21AA.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21AA.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21AA.rt + startConnector + 169 + + + + uni21AB + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21AB.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21AB.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21AB.rt + startConnector + 169 + + + + uni21AC + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21AC.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21AC.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21AC.rt + startConnector + 169 + + + + uni21AE + + italic + 0 + parts + + + advance + 380 + endConnector + 48 + extender + + glyph + uni21AE.lft + startConnector + 0 + + + advance + 95 + endConnector + 95 + extender + + glyph + uni21AE.ex + startConnector + 95 + + + advance + 380 + endConnector + 48 + extender + + glyph + uni21AE.md + startConnector + 48 + + + advance + 95 + endConnector + 95 + extender + + glyph + uni21AE.ex + startConnector + 95 + + + advance + 380 + endConnector + 0 + extender + + glyph + uni21AE.rt + startConnector + 48 + + + + uni21BC + + italic + 0 + parts + + + advance + 513 + endConnector + 171 + extender + + glyph + uni21BC.lft + startConnector + 0 + + + advance + 341 + endConnector + 341 + extender + + glyph + uni21BC.ex + startConnector + 341 + + + advance + 512 + endConnector + 0 + extender + + glyph + uni21BC.rt + startConnector + 171 + + + + uni21BD + + italic + 0 + parts + + + advance + 512 + endConnector + 171 + extender + + glyph + uni21BD.lft + startConnector + 0 + + + advance + 341 + endConnector + 341 + extender + + glyph + uni21BD.ex + startConnector + 341 + + + advance + 513 + endConnector + 0 + extender + + glyph + uni21BD.rt + startConnector + 171 + + + + uni21C0 + + italic + 0 + parts + + + advance + 512 + endConnector + 171 + extender + + glyph + uni21C0.lft + startConnector + 0 + + + advance + 341 + endConnector + 341 + extender + + glyph + uni21C0.ex + startConnector + 341 + + + advance + 513 + endConnector + 0 + extender + + glyph + uni21C0.rt + startConnector + 171 + + + + uni21C1 + + italic + 0 + parts + + + advance + 512 + endConnector + 171 + extender + + glyph + uni21C1.lft + startConnector + 0 + + + advance + 341 + endConnector + 341 + extender + + glyph + uni21C1.ex + startConnector + 341 + + + advance + 513 + endConnector + 0 + extender + + glyph + uni21C1.rt + startConnector + 171 + + + + uni21C4 + + italic + 0 + parts + + + advance + 515 + endConnector + 172 + extender + + glyph + uni21C4.lft + startConnector + 0 + + + advance + 343 + endConnector + 343 + extender + + glyph + uni21C4.ex + startConnector + 343 + + + advance + 514 + endConnector + 0 + extender + + glyph + uni21C4.rt + startConnector + 172 + + + + uni21C6 + + italic + 0 + parts + + + advance + 514 + endConnector + 172 + extender + + glyph + uni21C6.lft + startConnector + 0 + + + advance + 343 + endConnector + 343 + extender + + glyph + uni21C6.ex + startConnector + 343 + + + advance + 515 + endConnector + 0 + extender + + glyph + uni21C6.rt + startConnector + 172 + + + + uni21C7 + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21C7.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21C7.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21C7.rt + startConnector + 169 + + + + uni21C9 + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21C9.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21C9.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21C9.rt + startConnector + 169 + + + + uni21CB + + italic + 0 + parts + + + advance + 515 + endConnector + 172 + extender + + glyph + uni21CB.lft + startConnector + 0 + + + advance + 343 + endConnector + 343 + extender + + glyph + uni21CB.ex + startConnector + 343 + + + advance + 514 + endConnector + 0 + extender + + glyph + uni21CB.rt + startConnector + 172 + + + + uni21CC + + italic + 0 + parts + + + advance + 514 + endConnector + 172 + extender + + glyph + uni21CC.lft + startConnector + 0 + + + advance + 343 + endConnector + 343 + extender + + glyph + uni21CC.ex + startConnector + 343 + + + advance + 515 + endConnector + 0 + extender + + glyph + uni21CC.rt + startConnector + 172 + + + + uni21CD + + italic + 0 + parts + + + advance + 384 + endConnector + 49 + extender + + glyph + uni21CD.lft + startConnector + 0 + + + advance + 97 + endConnector + 97 + extender + + glyph + uni21CD.ex + startConnector + 97 + + + advance + 384 + endConnector + 49 + extender + + glyph + uni21CD.md + startConnector + 49 + + + advance + 97 + endConnector + 97 + extender + + glyph + uni21CD.ex + startConnector + 97 + + + advance + 384 + endConnector + 0 + extender + + glyph + uni21CD.rt + startConnector + 49 + + + + uni21CE + + italic + 0 + parts + + + advance + 406 + endConnector + 51 + extender + + glyph + uni21CE.lft + startConnector + 0 + + + advance + 102 + endConnector + 102 + extender + + glyph + uni21CE.ex + startConnector + 102 + + + advance + 406 + endConnector + 51 + extender + + glyph + uni21CE.md + startConnector + 51 + + + advance + 102 + endConnector + 102 + extender + + glyph + uni21CE.ex + startConnector + 102 + + + advance + 406 + endConnector + 0 + extender + + glyph + uni21CE.rt + startConnector + 51 + + + + uni21CF + + italic + 0 + parts + + + advance + 384 + endConnector + 49 + extender + + glyph + uni21CF.lft + startConnector + 0 + + + advance + 97 + endConnector + 97 + extender + + glyph + uni21CF.ex + startConnector + 97 + + + advance + 384 + endConnector + 49 + extender + + glyph + uni21CF.md + startConnector + 49 + + + advance + 97 + endConnector + 97 + extender + + glyph + uni21CF.ex + startConnector + 97 + + + advance + 384 + endConnector + 0 + extender + + glyph + uni21CF.rt + startConnector + 49 + + + + uni21DA + + italic + 0 + parts + + + advance + 506 + endConnector + 169 + extender + + glyph + uni21DA.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21DA.ex + startConnector + 337 + + + advance + 506 + endConnector + 0 + extender + + glyph + uni21DA.rt + startConnector + 169 + + + + uni21DB + + italic + 0 + parts + + + advance + 506 + endConnector + 169 + extender + + glyph + uni21DB.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21DB.ex + startConnector + 337 + + + advance + 506 + endConnector + 0 + extender + + glyph + uni21DB.rt + startConnector + 169 + + + + uni21E6 + + italic + 0 + parts + + + advance + 519 + endConnector + 173 + extender + + glyph + uni21E6.lft + startConnector + 0 + + + advance + 346 + endConnector + 346 + extender + + glyph + uni21E6.ex + startConnector + 346 + + + advance + 519 + endConnector + 0 + extender + + glyph + uni21E6.rt + startConnector + 173 + + + + uni21E8 + + italic + 0 + parts + + + advance + 519 + endConnector + 173 + extender + + glyph + uni21E8.lft + startConnector + 0 + + + advance + 346 + endConnector + 346 + extender + + glyph + uni21E8.ex + startConnector + 346 + + + advance + 519 + endConnector + 0 + extender + + glyph + uni21E8.rt + startConnector + 173 + + + + uni21F6 + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni21F6.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni21F6.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni21F6.rt + startConnector + 169 + + + + uni2263 + + italic + 0 + parts + + + advance + 222 + endConnector + 111 + extender + + glyph + uni2263.lft + startConnector + 0 + + + advance + 222 + endConnector + 222 + extender + + glyph + uni2263.ex + startConnector + 222 + + + advance + 222 + endConnector + 0 + extender + + glyph + uni2263.rt + startConnector + 111 + + + + uni23B4 + + italic + 0 + parts + + + advance + 1493 + endConnector + 498 + extender + + glyph + uni23B4.lft + startConnector + 0 + + + advance + 995 + endConnector + 995 + extender + + glyph + uni23B4.ex + startConnector + 995 + + + advance + 1492 + endConnector + 0 + extender + + glyph + uni23B4.rt + startConnector + 498 + + + + uni23B5 + + italic + 0 + parts + + + advance + 1493 + endConnector + 498 + extender + + glyph + uni23B5.lft + startConnector + 0 + + + advance + 995 + endConnector + 995 + extender + + glyph + uni23B5.ex + startConnector + 995 + + + advance + 1492 + endConnector + 0 + extender + + glyph + uni23B5.rt + startConnector + 498 + + + + uni23DC + + italic + 0 + parts + + + advance + 2016 + endConnector + 497 + extender + + glyph + uni23DC.lft + startConnector + 0 + + + advance + 994 + endConnector + 994 + extender + + glyph + uni23DC.ex + startConnector + 994 + + + advance + 2016 + endConnector + 0 + extender + + glyph + uni23DC.rt + startConnector + 497 + + + + uni23DD + + italic + 0 + parts + + + advance + 2016 + endConnector + 497 + extender + + glyph + uni23DD.lft + startConnector + 0 + + + advance + 994 + endConnector + 994 + extender + + glyph + uni23DD.ex + startConnector + 994 + + + advance + 2016 + endConnector + 0 + extender + + glyph + uni23DD.rt + startConnector + 497 + + + + uni23DE + + italic + 0 + parts + + + advance + 1002 + endConnector + 497 + extender + + glyph + uni23DE.lft + startConnector + 0 + + + advance + 994 + endConnector + 994 + extender + + glyph + uni23DE.ex + startConnector + 994 + + + advance + 2003 + endConnector + 497 + extender + + glyph + uni23DE.md + startConnector + 497 + + + advance + 994 + endConnector + 994 + extender + + glyph + uni23DE.ex + startConnector + 994 + + + advance + 1001 + endConnector + 0 + extender + + glyph + uni23DE.rt + startConnector + 497 + + + + uni23DF + + italic + 0 + parts + + + advance + 1002 + endConnector + 497 + extender + + glyph + uni23DF.lft + startConnector + 0 + + + advance + 994 + endConnector + 994 + extender + + glyph + uni23DF.ex + startConnector + 994 + + + advance + 2003 + endConnector + 497 + extender + + glyph + uni23DF.md + startConnector + 497 + + + advance + 994 + endConnector + 994 + extender + + glyph + uni23DF.ex + startConnector + 994 + + + advance + 1001 + endConnector + 0 + extender + + glyph + uni23DF.rt + startConnector + 497 + + + + uni23E0 + + italic + 0 + parts + + + advance + 2041 + endConnector + 680 + extender + + glyph + uni23E0.lft + startConnector + 0 + + + advance + 1360 + endConnector + 1360 + extender + + glyph + uni23E0.ex + startConnector + 1360 + + + advance + 2041 + endConnector + 0 + extender + + glyph + uni23E0.rt + startConnector + 680 + + + + uni23E1 + + italic + 0 + parts + + + advance + 2041 + endConnector + 680 + extender + + glyph + uni23E1.lft + startConnector + 0 + + + advance + 1360 + endConnector + 1360 + extender + + glyph + uni23E1.ex + startConnector + 1360 + + + advance + 2041 + endConnector + 0 + extender + + glyph + uni23E1.rt + startConnector + 680 + + + + uni27A1 + + italic + 0 + parts + + + advance + 492 + endConnector + 164 + extender + + glyph + uni27A1.lft + startConnector + 0 + + + advance + 327 + endConnector + 327 + extender + + glyph + uni27A1.ex + startConnector + 327 + + + advance + 492 + endConnector + 0 + extender + + glyph + uni27A1.rt + startConnector + 164 + + + + uni2906 + + italic + 0 + parts + + + advance + 497 + endConnector + 166 + extender + + glyph + uni2906.lft + startConnector + 0 + + + advance + 331 + endConnector + 331 + extender + + glyph + uni2906.ex + startConnector + 331 + + + advance + 497 + endConnector + 0 + extender + + glyph + uni2906.rt + startConnector + 166 + + + + uni2907 + + italic + 0 + parts + + + advance + 497 + endConnector + 166 + extender + + glyph + uni2907.lft + startConnector + 0 + + + advance + 331 + endConnector + 331 + extender + + glyph + uni2907.ex + startConnector + 331 + + + advance + 497 + endConnector + 0 + extender + + glyph + uni2907.rt + startConnector + 166 + + + + uni2B04 + + italic + 0 + parts + + + advance + 524 + endConnector + 175 + extender + + glyph + uni2B04.lft + startConnector + 0 + + + advance + 349 + endConnector + 349 + extender + + glyph + uni2B04.ex + startConnector + 349 + + + advance + 523 + endConnector + 0 + extender + + glyph + uni2B04.rt + startConnector + 175 + + + + uni2B05 + + italic + 0 + parts + + + advance + 492 + endConnector + 164 + extender + + glyph + uni2B05.lft + startConnector + 0 + + + advance + 327 + endConnector + 327 + extender + + glyph + uni2B05.ex + startConnector + 327 + + + advance + 492 + endConnector + 0 + extender + + glyph + uni2B05.rt + startConnector + 164 + + + + uni2B0C + + italic + 0 + parts + + + advance + 484 + endConnector + 161 + extender + + glyph + uni2B0C.lft + startConnector + 0 + + + advance + 322 + endConnector + 322 + extender + + glyph + uni2B0C.ex + startConnector + 322 + + + advance + 484 + endConnector + 0 + extender + + glyph + uni2B0C.rt + startConnector + 161 + + + + uni2B31 + + italic + 0 + parts + + + advance + 507 + endConnector + 169 + extender + + glyph + uni2B31.lft + startConnector + 0 + + + advance + 337 + endConnector + 337 + extender + + glyph + uni2B31.ex + startConnector + 337 + + + advance + 507 + endConnector + 0 + extender + + glyph + uni2B31.rt + startConnector + 169 + + + + h_variants arrowboth @@ -5203,19 +8522,16 @@ dbloverlinecmb.h1 equal - - + equivalence - - + lowlinecmb lowlinecmb lowlinecmb.h1 minus - - + overlinecmb overlinecmb @@ -5485,8 +8801,7 @@ uni21F6.h1 uni2263 - - + uni23B4 uni23B4 @@ -10152,6 +13467,6 @@ version - 1.3 + 1.4 diff --git a/iosMath/fonts/math_table_to_plist.py b/iosMath/fonts/math_table_to_plist.py index 1831cb19..59b0cf1c 100644 --- a/iosMath/fonts/math_table_to_plist.py +++ b/iosMath/fonts/math_table_to_plist.py @@ -14,16 +14,18 @@ def process_font(font_file, out_file): italic_c = get_italic_correction(math_table) v_variants = get_v_variants(math_table) h_variants = get_h_variants(math_table) - assembly = get_v_assembly(math_table) + v_assembly = get_v_assembly(math_table) + h_assembly = get_h_assembly(math_table) accents = get_accent_attachments(math_table) pl = { - "version" : "1.3", + "version" : "1.4", "constants": constants, "v_variants" : v_variants, "h_variants" : h_variants, "italic" : italic_c, "accents" : accents, - "v_assembly" : assembly } + "v_assembly" : v_assembly, + "h_assembly" : h_assembly } ofile = open(out_file, 'w+b') plistlib.dump(pl, ofile) ofile.close() @@ -181,7 +183,25 @@ def get_v_assembly(math_table): # There is an assembly for this glyph italic = assembly.ItalicsCorrection.Value parts = [part_dict(part) for part in assembly.PartRecords] - assembly_dict[name] = { + assembly_dict[name] = { + "italic" : assembly.ItalicsCorrection.Value, + "parts" : parts } + return assembly_dict + +def get_h_assembly(math_table): + variants = math_table.MathVariants + hglyphs = variants.HorizGlyphCoverage.glyphs + hconstruction = variants.HorizGlyphConstruction + count = variants.HorizGlyphCount + assembly_dict = {} + for i in range(count): + name = hglyphs[i] + record = hconstruction[i] + assembly = record.GlyphAssembly + if assembly is not None: + italic = assembly.ItalicsCorrection.Value + parts = [part_dict(part) for part in assembly.PartRecords] + assembly_dict[name] = { "italic" : assembly.ItalicsCorrection.Value, "parts" : parts } return assembly_dict diff --git a/iosMath/fonts/texgyretermes-math.plist b/iosMath/fonts/texgyretermes-math.plist index 1b43a01a..2bdecf00 100644 --- a/iosMath/fonts/texgyretermes-math.plist +++ b/iosMath/fonts/texgyretermes-math.plist @@ -4004,6 +4004,3325 @@ UpperLimitGapMin 100 + h_assembly + + arrowboth + + italic + 0 + parts + + + advance + 450 + endConnector + 150 + extender + + glyph + arrowboth.lft + startConnector + 0 + + + advance + 300 + endConnector + 300 + extender + + glyph + arrowboth.ex + startConnector + 300 + + + advance + 450 + endConnector + 0 + extender + + glyph + arrowboth.rt + startConnector + 150 + + + + arrowdblboth + + italic + 0 + parts + + + advance + 450 + endConnector + 150 + extender + + glyph + arrowdblboth.lft + startConnector + 0 + + + advance + 300 + endConnector + 300 + extender + + glyph + arrowdblboth.ex + startConnector + 300 + + + advance + 450 + endConnector + 0 + extender + + glyph + arrowdblboth.rt + startConnector + 150 + + + + arrowdblleft + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + arrowdblleft.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + arrowdblleft.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + arrowdblleft.rt + startConnector + 126 + + + + arrowdblright + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + arrowdblright.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + arrowdblright.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + arrowdblright.rt + startConnector + 126 + + + + arrowleft + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + arrowleft.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + arrowleft.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + arrowleft.rt + startConnector + 126 + + + + arrowright + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + arrowright.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + arrowright.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + arrowright.rt + startConnector + 126 + + + + dbllowlinecmb + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + dbllowlinecmb.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + dbllowlinecmb.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + dbllowlinecmb.rt + startConnector + 83 + + + + dbloverlinecmb + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + dbloverlinecmb.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + dbloverlinecmb.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + dbloverlinecmb.rt + startConnector + 83 + + + + equal + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + equal.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + equal.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + equal.rt + startConnector + 83 + + + + equivalence + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + equivalence.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + equivalence.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + equivalence.rt + startConnector + 83 + + + + lowlinecmb + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + lowlinecmb.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + lowlinecmb.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + lowlinecmb.rt + startConnector + 83 + + + + minus + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + minus.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + minus.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + minus.rt + startConnector + 83 + + + + overlinecmb + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + overlinecmb.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + overlinecmb.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + overlinecmb.rt + startConnector + 83 + + + + uni034D + + italic + 0 + parts + + + advance + 195 + endConnector + 65 + extender + + glyph + uni034D.lft + startConnector + 0 + + + advance + 130 + endConnector + 130 + extender + + glyph + uni034D.ex + startConnector + 130 + + + advance + 195 + endConnector + 0 + extender + + glyph + uni034D.rt + startConnector + 65 + + + + uni20D0 + + italic + 0 + parts + + + advance + 164 + endConnector + 55 + extender + + glyph + uni20D0.lft + startConnector + 0 + + + advance + 109 + endConnector + 109 + extender + + glyph + uni20D0.ex + startConnector + 109 + + + advance + 165 + endConnector + 0 + extender + + glyph + uni20D0.rt + startConnector + 55 + + + + uni20D1 + + italic + 0 + parts + + + advance + 164 + endConnector + 55 + extender + + glyph + uni20D1.lft + startConnector + 0 + + + advance + 109 + endConnector + 109 + extender + + glyph + uni20D1.ex + startConnector + 109 + + + advance + 165 + endConnector + 0 + extender + + glyph + uni20D1.rt + startConnector + 55 + + + + uni20D6 + + italic + 0 + parts + + + advance + 168 + endConnector + 56 + extender + + glyph + uni20D6.lft + startConnector + 0 + + + advance + 112 + endConnector + 112 + extender + + glyph + uni20D6.ex + startConnector + 112 + + + advance + 167 + endConnector + 0 + extender + + glyph + uni20D6.rt + startConnector + 56 + + + + uni20D7 + + italic + 0 + parts + + + advance + 168 + endConnector + 56 + extender + + glyph + uni20D7.lft + startConnector + 0 + + + advance + 112 + endConnector + 112 + extender + + glyph + uni20D7.ex + startConnector + 112 + + + advance + 168 + endConnector + 0 + extender + + glyph + uni20D7.rt + startConnector + 56 + + + + uni20E1 + + italic + 0 + parts + + + advance + 195 + endConnector + 65 + extender + + glyph + uni20E1.lft + startConnector + 0 + + + advance + 130 + endConnector + 130 + extender + + glyph + uni20E1.ex + startConnector + 130 + + + advance + 195 + endConnector + 0 + extender + + glyph + uni20E1.rt + startConnector + 65 + + + + uni20E9 + + italic + 0 + parts + + + advance + 1313 + endConnector + 438 + extender + + glyph + uni23B4.lft + startConnector + 0 + + + advance + 875 + endConnector + 875 + extender + + glyph + uni23B4.ex + startConnector + 875 + + + advance + 1312 + endConnector + 0 + extender + + glyph + uni23B4.rt + startConnector + 438 + + + + uni20EC + + italic + 0 + parts + + + advance + 164 + endConnector + 55 + extender + + glyph + uni20EC.lft + startConnector + 0 + + + advance + 109 + endConnector + 109 + extender + + glyph + uni20EC.ex + startConnector + 109 + + + advance + 165 + endConnector + 0 + extender + + glyph + uni20EC.rt + startConnector + 55 + + + + uni20ED + + italic + 0 + parts + + + advance + 164 + endConnector + 55 + extender + + glyph + uni20ED.lft + startConnector + 0 + + + advance + 109 + endConnector + 109 + extender + + glyph + uni20ED.ex + startConnector + 109 + + + advance + 165 + endConnector + 0 + extender + + glyph + uni20ED.rt + startConnector + 55 + + + + uni20EE + + italic + 0 + parts + + + advance + 168 + endConnector + 56 + extender + + glyph + uni20EE.lft + startConnector + 0 + + + advance + 112 + endConnector + 112 + extender + + glyph + uni20EE.ex + startConnector + 112 + + + advance + 167 + endConnector + 0 + extender + + glyph + uni20EE.rt + startConnector + 56 + + + + uni20EF + + italic + 0 + parts + + + advance + 168 + endConnector + 56 + extender + + glyph + uni20EF.lft + startConnector + 0 + + + advance + 112 + endConnector + 112 + extender + + glyph + uni20EF.ex + startConnector + 112 + + + advance + 168 + endConnector + 0 + extender + + glyph + uni20EF.rt + startConnector + 56 + + + + uni219A + + italic + 0 + parts + + + advance + 289 + endConnector + 36 + extender + + glyph + uni219A.lft + startConnector + 0 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni219A.ex + startConnector + 72 + + + advance + 288 + endConnector + 36 + extender + + glyph + uni219A.md + startConnector + 36 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni219A.ex + startConnector + 72 + + + advance + 289 + endConnector + 0 + extender + + glyph + uni219A.rt + startConnector + 36 + + + + uni219B + + italic + 0 + parts + + + advance + 289 + endConnector + 36 + extender + + glyph + uni219B.lft + startConnector + 0 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni219B.ex + startConnector + 72 + + + advance + 288 + endConnector + 36 + extender + + glyph + uni219B.md + startConnector + 36 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni219B.ex + startConnector + 72 + + + advance + 289 + endConnector + 0 + extender + + glyph + uni219B.rt + startConnector + 36 + + + + uni219E + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni219E.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni219E.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni219E.rt + startConnector + 149 + + + + uni21A0 + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni21A0.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni21A0.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni21A0.rt + startConnector + 149 + + + + uni21A2 + + italic + 0 + parts + + + advance + 450 + endConnector + 150 + extender + + glyph + uni21A2.lft + startConnector + 0 + + + advance + 300 + endConnector + 300 + extender + + glyph + uni21A2.ex + startConnector + 300 + + + advance + 450 + endConnector + 0 + extender + + glyph + uni21A2.rt + startConnector + 150 + + + + uni21A3 + + italic + 0 + parts + + + advance + 450 + endConnector + 150 + extender + + glyph + uni21A3.lft + startConnector + 0 + + + advance + 300 + endConnector + 300 + extender + + glyph + uni21A3.ex + startConnector + 300 + + + advance + 450 + endConnector + 0 + extender + + glyph + uni21A3.rt + startConnector + 150 + + + + uni21A4 + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + uni21A4.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + uni21A4.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + uni21A4.rt + startConnector + 126 + + + + uni21A6 + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + uni21A6.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + uni21A6.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + uni21A6.rt + startConnector + 126 + + + + uni21A9 + + italic + 0 + parts + + + advance + 388 + endConnector + 130 + extender + + glyph + uni21A9.lft + startConnector + 0 + + + advance + 259 + endConnector + 259 + extender + + glyph + uni21A9.ex + startConnector + 259 + + + advance + 389 + endConnector + 0 + extender + + glyph + uni21A9.rt + startConnector + 130 + + + + uni21AA + + italic + 0 + parts + + + advance + 389 + endConnector + 130 + extender + + glyph + uni21AA.lft + startConnector + 0 + + + advance + 259 + endConnector + 259 + extender + + glyph + uni21AA.ex + startConnector + 259 + + + advance + 388 + endConnector + 0 + extender + + glyph + uni21AA.rt + startConnector + 130 + + + + uni21AB + + italic + 0 + parts + + + advance + 388 + endConnector + 130 + extender + + glyph + uni21AB.lft + startConnector + 0 + + + advance + 259 + endConnector + 259 + extender + + glyph + uni21AB.ex + startConnector + 259 + + + advance + 389 + endConnector + 0 + extender + + glyph + uni21AB.rt + startConnector + 130 + + + + uni21AC + + italic + 0 + parts + + + advance + 389 + endConnector + 130 + extender + + glyph + uni21AC.lft + startConnector + 0 + + + advance + 259 + endConnector + 259 + extender + + glyph + uni21AC.ex + startConnector + 259 + + + advance + 388 + endConnector + 0 + extender + + glyph + uni21AC.rt + startConnector + 130 + + + + uni21AE + + italic + 0 + parts + + + advance + 343 + endConnector + 43 + extender + + glyph + uni21AE.lft + startConnector + 0 + + + advance + 86 + endConnector + 86 + extender + + glyph + uni21AE.ex + startConnector + 86 + + + advance + 342 + endConnector + 43 + extender + + glyph + uni21AE.md + startConnector + 43 + + + advance + 86 + endConnector + 86 + extender + + glyph + uni21AE.ex + startConnector + 86 + + + advance + 343 + endConnector + 0 + extender + + glyph + uni21AE.rt + startConnector + 43 + + + + uni21BC + + italic + 0 + parts + + + advance + 375 + endConnector + 125 + extender + + glyph + uni21BC.lft + startConnector + 0 + + + advance + 250 + endConnector + 250 + extender + + glyph + uni21BC.ex + startConnector + 250 + + + advance + 375 + endConnector + 0 + extender + + glyph + uni21BC.rt + startConnector + 125 + + + + uni21BD + + italic + 0 + parts + + + advance + 375 + endConnector + 125 + extender + + glyph + uni21BD.lft + startConnector + 0 + + + advance + 250 + endConnector + 250 + extender + + glyph + uni21BD.ex + startConnector + 250 + + + advance + 375 + endConnector + 0 + extender + + glyph + uni21BD.rt + startConnector + 125 + + + + uni21C0 + + italic + 0 + parts + + + advance + 375 + endConnector + 125 + extender + + glyph + uni21C0.lft + startConnector + 0 + + + advance + 250 + endConnector + 250 + extender + + glyph + uni21C0.ex + startConnector + 250 + + + advance + 375 + endConnector + 0 + extender + + glyph + uni21C0.rt + startConnector + 125 + + + + uni21C1 + + italic + 0 + parts + + + advance + 375 + endConnector + 125 + extender + + glyph + uni21C1.lft + startConnector + 0 + + + advance + 250 + endConnector + 250 + extender + + glyph + uni21C1.ex + startConnector + 250 + + + advance + 375 + endConnector + 0 + extender + + glyph + uni21C1.rt + startConnector + 125 + + + + uni21C4 + + italic + 0 + parts + + + advance + 383 + endConnector + 128 + extender + + glyph + uni21C4.lft + startConnector + 0 + + + advance + 255 + endConnector + 255 + extender + + glyph + uni21C4.ex + startConnector + 255 + + + advance + 382 + endConnector + 0 + extender + + glyph + uni21C4.rt + startConnector + 128 + + + + uni21C6 + + italic + 0 + parts + + + advance + 383 + endConnector + 128 + extender + + glyph + uni21C6.lft + startConnector + 0 + + + advance + 255 + endConnector + 255 + extender + + glyph + uni21C6.ex + startConnector + 255 + + + advance + 382 + endConnector + 0 + extender + + glyph + uni21C6.rt + startConnector + 128 + + + + uni21C7 + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + uni21C7.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + uni21C7.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + uni21C7.rt + startConnector + 126 + + + + uni21C9 + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + uni21C9.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + uni21C9.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + uni21C9.rt + startConnector + 126 + + + + uni21CB + + italic + 0 + parts + + + advance + 375 + endConnector + 125 + extender + + glyph + uni21CB.lft + startConnector + 0 + + + advance + 250 + endConnector + 250 + extender + + glyph + uni21CB.ex + startConnector + 250 + + + advance + 375 + endConnector + 0 + extender + + glyph + uni21CB.rt + startConnector + 125 + + + + uni21CC + + italic + 0 + parts + + + advance + 375 + endConnector + 125 + extender + + glyph + uni21CC.lft + startConnector + 0 + + + advance + 250 + endConnector + 250 + extender + + glyph + uni21CC.ex + startConnector + 250 + + + advance + 375 + endConnector + 0 + extender + + glyph + uni21CC.rt + startConnector + 125 + + + + uni21CD + + italic + 0 + parts + + + advance + 289 + endConnector + 36 + extender + + glyph + uni21CD.lft + startConnector + 0 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni21CD.ex + startConnector + 72 + + + advance + 288 + endConnector + 36 + extender + + glyph + uni21CD.md + startConnector + 36 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni21CD.ex + startConnector + 72 + + + advance + 289 + endConnector + 0 + extender + + glyph + uni21CD.rt + startConnector + 36 + + + + uni21CE + + italic + 0 + parts + + + advance + 343 + endConnector + 43 + extender + + glyph + uni21CE.lft + startConnector + 0 + + + advance + 86 + endConnector + 86 + extender + + glyph + uni21CE.ex + startConnector + 86 + + + advance + 342 + endConnector + 43 + extender + + glyph + uni21CE.md + startConnector + 43 + + + advance + 86 + endConnector + 86 + extender + + glyph + uni21CE.ex + startConnector + 86 + + + advance + 343 + endConnector + 0 + extender + + glyph + uni21CE.rt + startConnector + 43 + + + + uni21CF + + italic + 0 + parts + + + advance + 289 + endConnector + 36 + extender + + glyph + uni21CF.lft + startConnector + 0 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni21CF.ex + startConnector + 72 + + + advance + 288 + endConnector + 36 + extender + + glyph + uni21CF.md + startConnector + 36 + + + advance + 72 + endConnector + 72 + extender + + glyph + uni21CF.ex + startConnector + 72 + + + advance + 289 + endConnector + 0 + extender + + glyph + uni21CF.rt + startConnector + 36 + + + + uni21DA + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni21DA.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni21DA.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni21DA.rt + startConnector + 149 + + + + uni21DB + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni21DB.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni21DB.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni21DB.rt + startConnector + 149 + + + + uni21E6 + + italic + 0 + parts + + + advance + 463 + endConnector + 154 + extender + + glyph + uni21E6.lft + startConnector + 0 + + + advance + 308 + endConnector + 308 + extender + + glyph + uni21E6.ex + startConnector + 308 + + + advance + 462 + endConnector + 0 + extender + + glyph + uni21E6.rt + startConnector + 154 + + + + uni21E8 + + italic + 0 + parts + + + advance + 463 + endConnector + 154 + extender + + glyph + uni21E8.lft + startConnector + 0 + + + advance + 308 + endConnector + 308 + extender + + glyph + uni21E8.ex + startConnector + 308 + + + advance + 462 + endConnector + 0 + extender + + glyph + uni21E8.rt + startConnector + 154 + + + + uni21F6 + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + uni21F6.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + uni21F6.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + uni21F6.rt + startConnector + 126 + + + + uni2263 + + italic + 0 + parts + + + advance + 167 + endConnector + 83 + extender + + glyph + uni2263.lft + startConnector + 0 + + + advance + 166 + endConnector + 166 + extender + + glyph + uni2263.ex + startConnector + 166 + + + advance + 167 + endConnector + 0 + extender + + glyph + uni2263.rt + startConnector + 83 + + + + uni23B4 + + italic + 0 + parts + + + advance + 1313 + endConnector + 438 + extender + + glyph + uni23B4.lft + startConnector + 0 + + + advance + 875 + endConnector + 875 + extender + + glyph + uni23B4.ex + startConnector + 875 + + + advance + 1312 + endConnector + 0 + extender + + glyph + uni23B4.rt + startConnector + 438 + + + + uni23B5 + + italic + 0 + parts + + + advance + 1313 + endConnector + 438 + extender + + glyph + uni23B5.lft + startConnector + 0 + + + advance + 875 + endConnector + 875 + extender + + glyph + uni23B5.ex + startConnector + 875 + + + advance + 1312 + endConnector + 0 + extender + + glyph + uni23B5.rt + startConnector + 438 + + + + uni23DC + + italic + 0 + parts + + + advance + 1757 + endConnector + 437 + extender + + glyph + uni23DC.lft + startConnector + 0 + + + advance + 874 + endConnector + 874 + extender + + glyph + uni23DC.ex + startConnector + 874 + + + advance + 1757 + endConnector + 0 + extender + + glyph + uni23DC.rt + startConnector + 437 + + + + uni23DD + + italic + 0 + parts + + + advance + 1757 + endConnector + 437 + extender + + glyph + uni23DD.lft + startConnector + 0 + + + advance + 874 + endConnector + 874 + extender + + glyph + uni23DD.ex + startConnector + 874 + + + advance + 1757 + endConnector + 0 + extender + + glyph + uni23DD.rt + startConnector + 437 + + + + uni23DE + + italic + 0 + parts + + + advance + 880 + endConnector + 437 + extender + + glyph + uni23DE.lft + startConnector + 0 + + + advance + 874 + endConnector + 874 + extender + + glyph + uni23DE.ex + startConnector + 874 + + + advance + 1759 + endConnector + 437 + extender + + glyph + uni23DE.md + startConnector + 437 + + + advance + 874 + endConnector + 874 + extender + + glyph + uni23DE.ex + startConnector + 874 + + + advance + 880 + endConnector + 0 + extender + + glyph + uni23DE.rt + startConnector + 437 + + + + uni23DF + + italic + 0 + parts + + + advance + 880 + endConnector + 437 + extender + + glyph + uni23DF.lft + startConnector + 0 + + + advance + 874 + endConnector + 874 + extender + + glyph + uni23DF.ex + startConnector + 874 + + + advance + 1759 + endConnector + 437 + extender + + glyph + uni23DF.md + startConnector + 437 + + + advance + 874 + endConnector + 874 + extender + + glyph + uni23DF.ex + startConnector + 874 + + + advance + 880 + endConnector + 0 + extender + + glyph + uni23DF.rt + startConnector + 437 + + + + uni23E0 + + italic + 0 + parts + + + advance + 1792 + endConnector + 597 + extender + + glyph + uni23E0.lft + startConnector + 0 + + + advance + 1194 + endConnector + 1194 + extender + + glyph + uni23E0.ex + startConnector + 1194 + + + advance + 1792 + endConnector + 0 + extender + + glyph + uni23E0.rt + startConnector + 597 + + + + uni23E1 + + italic + 0 + parts + + + advance + 1792 + endConnector + 597 + extender + + glyph + uni23E1.lft + startConnector + 0 + + + advance + 1194 + endConnector + 1194 + extender + + glyph + uni23E1.ex + startConnector + 1194 + + + advance + 1792 + endConnector + 0 + extender + + glyph + uni23E1.rt + startConnector + 597 + + + + uni27A1 + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni27A1.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni27A1.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni27A1.rt + startConnector + 149 + + + + uni2906 + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni2906.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni2906.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni2906.rt + startConnector + 149 + + + + uni2907 + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni2907.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni2907.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni2907.rt + startConnector + 149 + + + + uni2B04 + + italic + 0 + parts + + + advance + 469 + endConnector + 157 + extender + + glyph + uni2B04.lft + startConnector + 0 + + + advance + 313 + endConnector + 313 + extender + + glyph + uni2B04.ex + startConnector + 313 + + + advance + 469 + endConnector + 0 + extender + + glyph + uni2B04.rt + startConnector + 157 + + + + uni2B05 + + italic + 0 + parts + + + advance + 446 + endConnector + 149 + extender + + glyph + uni2B05.lft + startConnector + 0 + + + advance + 298 + endConnector + 298 + extender + + glyph + uni2B05.ex + startConnector + 298 + + + advance + 446 + endConnector + 0 + extender + + glyph + uni2B05.rt + startConnector + 149 + + + + uni2B0C + + italic + 0 + parts + + + advance + 450 + endConnector + 150 + extender + + glyph + uni2B0C.lft + startConnector + 0 + + + advance + 300 + endConnector + 300 + extender + + glyph + uni2B0C.ex + startConnector + 300 + + + advance + 450 + endConnector + 0 + extender + + glyph + uni2B0C.rt + startConnector + 150 + + + + uni2B31 + + italic + 0 + parts + + + advance + 379 + endConnector + 126 + extender + + glyph + uni2B31.lft + startConnector + 0 + + + advance + 252 + endConnector + 252 + extender + + glyph + uni2B31.ex + startConnector + 252 + + + advance + 379 + endConnector + 0 + extender + + glyph + uni2B31.rt + startConnector + 126 + + + + h_variants arrowboth @@ -4127,19 +7446,16 @@ dbloverlinecmb.h1 equal - - + equivalence - - + lowlinecmb lowlinecmb lowlinecmb.h1 minus - - + overlinecmb overlinecmb @@ -4406,8 +7722,7 @@ uni21F6.h1 uni2263 - - + uni23B4 uni23B4 @@ -9102,6 +12417,6 @@ version - 1.3 + 1.4 diff --git a/iosMath/fonts/xits-math.plist b/iosMath/fonts/xits-math.plist index c6edcd27..04449121 100644 --- a/iosMath/fonts/xits-math.plist +++ b/iosMath/fonts/xits-math.plist @@ -2778,6 +2778,1301 @@ UpperLimitGapMin 150 + h_assembly + + uni0305 + + italic + 0 + parts + + + advance + 1000 + endConnector + 1000 + extender + + glyph + uni0305.size1 + startConnector + 0 + + + advance + 1000 + endConnector + 0 + extender + + glyph + uni0305.size1 + startConnector + 1000 + + + + uni0332 + + italic + 0 + parts + + + advance + 1000 + endConnector + 1000 + extender + + glyph + uni0332.size1 + startConnector + 0 + + + advance + 1000 + endConnector + 0 + extender + + glyph + uni0332.size1 + startConnector + 1000 + + + + uni034D + + italic + 0 + parts + + + advance + 436 + endConnector + 350 + extender + + glyph + uni20EE + startConnector + 0 + + + advance + 300 + endConnector + 300 + extender + + glyph + uni20EE.ex + startConnector + 300 + + + advance + 436 + endConnector + 0 + extender + + glyph + uni20EF + startConnector + 350 + + + + uni20D0 + + italic + 0 + parts + + + advance + 436 + endConnector + 350 + extender + + glyph + uni20D0 + startConnector + 0 + + + advance + 300 + endConnector + 0 + extender + + glyph + uni20D6.ex + startConnector + 300 + + + + uni20D1 + + italic + 0 + parts + + + advance + 300 + endConnector + 300 + extender + + glyph + uni20D6.ex + startConnector + 0 + + + advance + 436 + endConnector + 0 + extender + + glyph + uni20D1 + startConnector + 350 + + + + uni20D6 + + italic + 0 + parts + + + advance + 436 + endConnector + 350 + extender + + glyph + uni20D6 + startConnector + 0 + + + advance + 300 + endConnector + 0 + extender + + glyph + uni20D6.ex + startConnector + 300 + + + + uni20D7 + + italic + 0 + parts + + + advance + 300 + endConnector + 300 + extender + + glyph + uni20D6.ex + startConnector + 0 + + + advance + 436 + endConnector + 0 + extender + + glyph + uni20D7 + startConnector + 350 + + + + uni20E1 + + italic + 0 + parts + + + advance + 436 + endConnector + 350 + extender + + glyph + uni20D6 + startConnector + 0 + + + advance + 300 + endConnector + 300 + extender + + glyph + uni20D6.ex + startConnector + 300 + + + advance + 436 + endConnector + 0 + extender + + glyph + uni20D7 + startConnector + 350 + + + + uni20E9 + + italic + 0 + parts + + + advance + 154 + endConnector + 50 + extender + + glyph + uni20E9.lt + startConnector + 0 + + + advance + 200 + endConnector + 50 + extender + + glyph + uni20E9.ex + startConnector + 50 + + + advance + 154 + endConnector + 0 + extender + + glyph + uni20E9.rt + startConnector + 50 + + + + uni20EC + + italic + 0 + parts + + + advance + 300 + endConnector + 300 + extender + + glyph + uni20EE.ex + startConnector + 0 + + + advance + 436 + endConnector + 0 + extender + + glyph + uni20EC + startConnector + 350 + + + + uni20ED + + italic + 0 + parts + + + advance + 436 + endConnector + 350 + extender + + glyph + uni20ED + startConnector + 0 + + + advance + 300 + endConnector + 0 + extender + + glyph + uni20EE.ex + startConnector + 300 + + + + uni20EE + + italic + 0 + parts + + + advance + 436 + endConnector + 350 + extender + + glyph + uni20EE + startConnector + 0 + + + advance + 300 + endConnector + 0 + extender + + glyph + uni20EE.ex + startConnector + 300 + + + + uni20EF + + italic + 0 + parts + + + advance + 300 + endConnector + 300 + extender + + glyph + uni20EE.ex + startConnector + 0 + + + advance + 436 + endConnector + 0 + extender + + glyph + uni20EF + startConnector + 350 + + + + uni2190 + + italic + 0 + parts + + + advance + 786 + endConnector + 262 + extender + + glyph + uni2190 + startConnector + 262 + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + + uni2192 + + italic + 0 + parts + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + advance + 786 + endConnector + 262 + extender + + glyph + uni2192 + startConnector + 262 + + + + uni2194 + + italic + 0 + parts + + + advance + 786 + endConnector + 262 + extender + + glyph + uni2190 + startConnector + 262 + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + advance + 786 + endConnector + 262 + extender + + glyph + uni2192 + startConnector + 262 + + + + uni21A4 + + italic + 0 + parts + + + advance + 786 + endConnector + 262 + extender + + glyph + uni2190 + startConnector + 262 + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + advance + 1464 + endConnector + 488 + extender + + glyph + uni27DE + startConnector + 488 + + + + uni21A6 + + italic + 0 + parts + + + advance + 1464 + endConnector + 488 + extender + + glyph + uni27DD + startConnector + 488 + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + advance + 786 + endConnector + 262 + extender + + glyph + uni2192 + startConnector + 262 + + + + uni21BC + + italic + 0 + parts + + + advance + 847 + endConnector + 282 + extender + + glyph + uni21BC + startConnector + 282 + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + + uni21BD + + italic + 0 + parts + + + advance + 847 + endConnector + 282 + extender + + glyph + uni21BD + startConnector + 282 + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + + uni21C0 + + italic + 0 + parts + + + advance + 315 + endConnector + 105 + extender + + glyph + uni2190.ex + startConnector + 105 + + + advance + 847 + endConnector + 282 + extender + + glyph + uni21C0 + startConnector + 282 + + + + uni21D0 + + italic + 0 + parts + + + advance + 806 + endConnector + 269 + extender + + glyph + uni21D0 + startConnector + 269 + + + advance + 435 + endConnector + 145 + extender + + glyph + uni21D0.ex + startConnector + 145 + + + + uni21D2 + + italic + 0 + parts + + + advance + 435 + endConnector + 145 + extender + + glyph + uni21D0.ex + startConnector + 145 + + + advance + 806 + endConnector + 269 + extender + + glyph + uni21D2 + startConnector + 269 + + + + uni21D4 + + italic + 0 + parts + + + advance + 806 + endConnector + 269 + extender + + glyph + uni21D0 + startConnector + 269 + + + advance + 435 + endConnector + 145 + extender + + glyph + uni21D0.ex + startConnector + 145 + + + advance + 806 + endConnector + 269 + extender + + glyph + uni21D2 + startConnector + 269 + + + + uni21DA + + italic + 0 + parts + + + advance + 806 + endConnector + 50 + extender + + glyph + uni21DA + startConnector + 0 + + + advance + 315 + endConnector + 0 + extender + + glyph + uni21DA.ex + startConnector + 50 + + + + uni21DB + + italic + 0 + parts + + + advance + 315 + endConnector + 50 + extender + + glyph + uni21DA.ex + startConnector + 0 + + + advance + 806 + endConnector + 0 + extender + + glyph + uni21DB + startConnector + 50 + + + + uni23B4 + + italic + 0 + parts + + + advance + 1855 + endConnector + 618 + extender + + glyph + uni23B4.lt + startConnector + 0 + + + advance + 635 + endConnector + 212 + extender + + glyph + uni23B4.ex + startConnector + 212 + + + advance + 1855 + endConnector + 0 + extender + + glyph + uni23B4.rt + startConnector + 618 + + + + uni23B5 + + italic + 0 + parts + + + advance + 1855 + endConnector + 618 + extender + + glyph + uni23B5.lt + startConnector + 0 + + + advance + 635 + endConnector + 212 + extender + + glyph + uni23B5.ex + startConnector + 212 + + + advance + 1855 + endConnector + 0 + extender + + glyph + uni23B5.rt + startConnector + 618 + + + + uni23DC + + italic + 0 + parts + + + advance + 1855 + endConnector + 618 + extender + + glyph + uni23DC.lt + startConnector + 0 + + + advance + 635 + endConnector + 212 + extender + + glyph + uni23DC.ex + startConnector + 212 + + + advance + 1855 + endConnector + 0 + extender + + glyph + uni23DC.rt + startConnector + 618 + + + + uni23DD + + italic + 0 + parts + + + advance + 1855 + endConnector + 618 + extender + + glyph + uni23DD.lt + startConnector + 0 + + + advance + 635 + endConnector + 212 + extender + + glyph + uni23DD.ex + startConnector + 212 + + + advance + 1855 + endConnector + 0 + extender + + glyph + uni23DD.rt + startConnector + 618 + + + + uni23DE + + italic + 0 + parts + + + advance + 933 + endConnector + 311 + extender + + glyph + uni23DE.lt + startConnector + 0 + + + advance + 327 + endConnector + 109 + extender + + glyph + uni23DE.ex + startConnector + 109 + + + advance + 1864 + endConnector + 621 + extender + + glyph + uni23DE.mid + startConnector + 621 + + + advance + 327 + endConnector + 109 + extender + + glyph + uni23DE.ex + startConnector + 109 + + + advance + 933 + endConnector + 0 + extender + + glyph + uni23DE.rt + startConnector + 311 + + + + uni23DF + + italic + 0 + parts + + + advance + 933 + endConnector + 311 + extender + + glyph + uni23DF.lt + startConnector + 0 + + + advance + 327 + endConnector + 109 + extender + + glyph + uni23DF.ex + startConnector + 109 + + + advance + 1864 + endConnector + 621 + extender + + glyph + uni23DF.mid + startConnector + 621 + + + advance + 327 + endConnector + 109 + extender + + glyph + uni23DF.ex + startConnector + 109 + + + advance + 933 + endConnector + 0 + extender + + glyph + uni23DF.rt + startConnector + 311 + + + + uni2B45 + + italic + 0 + parts + + + advance + 818 + endConnector + 50 + extender + + glyph + uni2B45 + startConnector + 0 + + + advance + 315 + endConnector + 0 + extender + + glyph + uni2B45.ex + startConnector + 50 + + + + uni2B46 + + italic + 0 + parts + + + advance + 315 + endConnector + 50 + extender + + glyph + uni2B45.ex + startConnector + 0 + + + advance + 818 + endConnector + 0 + extender + + glyph + uni2B46 + startConnector + 50 + + + + h_variants uni0302 @@ -2888,44 +4183,31 @@ uni20EF uni2190 - - + uni2192 - - + uni2194 - - + uni21A4 - - + uni21A6 - - + uni21BC - - + uni21BD - - + uni21C0 - - + uni21D0 - - + uni21D2 - - + uni21D4 - - + uni21DA - - + uni21DB - - + uni23B4 uni23B4 @@ -2999,11 +4281,9 @@ uni23E1.size5 uni2B45 - - + uni2B46 - - + italic @@ -5887,35 +7167,25 @@ uni2140.rtlm.display uni2191 - - + uni2193 - - + uni2195 - - + uni21BE - - + uni21BF - - + uni21C2 - - + uni21C3 - - + uni21D1 - - + uni21D3 - - + uni21D5 - - + uni220F uni220F @@ -5975,11 +7245,9 @@ uni221C.rtlm.size3 uni2223 - - + uni2225 - - + uni222B uni222B @@ -6239,17 +7507,13 @@ uni27EF uni27F0 - - + uni27F1 - - + uni290A - - + uni290B - - + uni2980 uni2980 @@ -6612,8 +7876,7 @@ uni2A1C.up.display uni2AF4 - - + uni2AFC uni2AFC @@ -6626,6 +7889,6 @@ version - 1.3 + 1.4 diff --git a/iosMath/lib/MTMathAtomFactory.h b/iosMath/lib/MTMathAtomFactory.h index b1574799..67c7f066 100644 --- a/iosMath/lib/MTMathAtomFactory.h +++ b/iosMath/lib/MTMathAtomFactory.h @@ -112,6 +112,20 @@ FOUNDATION_EXPORT NSString *const MTSymbolDegree; function. */ +(NSString*) accentName:(MTAccent*) accent; +/** Returns a pre-configured MTMathStack for one of the eight supported over/under arrow + or brace commands: overrightarrow, overleftarrow, overleftrightarrow, underrightarrow, + underleftarrow, underleftrightarrow, overbrace, underbrace. Returns nil for any other name. + The returned atom's innerList is nil; the caller must set it (typically via buildInternal:). + To add a new command, add a row to the static stack commands dictionary in MTMathAtomFactory.m. */ ++ (nullable MTMathStack*) stackAtomForCommand:(NSString*)command + NS_SWIFT_NAME(stackAtom(forCommand:)); + +/** Returns the LaTeX command name for the given stack atom, or nil if the atom does not + match any entry in the stack commands table (e.g. a programmatically-built stack with + non-canonical constructions). This is the inverse of stackAtomForCommand:. */ ++ (nullable NSString*) stackCommandForStack:(MTMathStack*)stack + NS_SWIFT_NAME(stackCommand(for:)); + /** Creates a new boundary atom for the given delimiter name. If the delimiter name is not recognized it returns nil. A delimiter name can be a single character such as '(' or a latex command such as 'uparrow'. diff --git a/iosMath/lib/MTMathAtomFactory.m b/iosMath/lib/MTMathAtomFactory.m index 5ea92b35..7a9adff3 100644 --- a/iosMath/lib/MTMathAtomFactory.m +++ b/iosMath/lib/MTMathAtomFactory.m @@ -26,6 +26,30 @@ NSString *const MTSymbolAngle = @"\u2220"; // \angle NSString *const MTSymbolDegree = @"\u00B0"; // \circ +/// File-private value type carrying the over/under spec and displayClass for one command. +@interface MTMathStackCommandSpec : NSObject +@property (nonatomic, readonly, nullable) MTMathStackConstruction* overConstruction; +@property (nonatomic, readonly, nullable) MTMathStackConstruction* underConstruction; +@property (nonatomic, readonly) MTMathAtomType displayClass; +- (instancetype)initWithOver:(nullable MTMathStackConstruction*)over + under:(nullable MTMathStackConstruction*)under + displayClass:(MTMathAtomType)displayClass; +@end + +@implementation MTMathStackCommandSpec +- (instancetype)initWithOver:(nullable MTMathStackConstruction*)over + under:(nullable MTMathStackConstruction*)under + displayClass:(MTMathAtomType)displayClass { + self = [super init]; + if (self) { + _overConstruction = over; + _underConstruction = under; + _displayClass = displayClass; + } + return self; +} +@end + @implementation MTMathAtomFactory + (MTMathAtom *)times @@ -890,4 +914,90 @@ + (NSDictionary*) delimValueToName return fontStyles; } +#pragma mark - Stack commands + ++ (NSDictionary*) stackCommands +{ + static NSDictionary* stackCommands = nil; + if (!stackCommands) { + // Each command maps to a single stretchy cap glyph (a Unicode codepoint). The + // typesetter walks the cap's OpenType h_variants first; if no variant is wide + // enough, it falls back to the font's HorizontalGlyphAssembly (parts + connector + // overlaps). The cap codepoint must therefore be one whose OpenType MATH table + // provides assembly data — the arrow caps carry their own .lft/.ex/.rt parts. + MTMathStackConstruction* rightArrow = [MTMathStackConstruction extensibleWithGlyph:@"\u2192"]; + MTMathStackConstruction* leftArrow = [MTMathStackConstruction extensibleWithGlyph:@"\u2190"]; + MTMathStackConstruction* leftRightArrow = [MTMathStackConstruction extensibleWithGlyph:@"\u2194"]; + MTMathStackConstruction* overBrace = [MTMathStackConstruction extensibleWithGlyph:@"\u23DE"]; + MTMathStackConstruction* underBrace = [MTMathStackConstruction extensibleWithGlyph:@"\u23DF"]; + + stackCommands = @{ + @"overrightarrow": [[MTMathStackCommandSpec alloc] initWithOver:rightArrow under:nil displayClass:kMTMathAtomOrdinary], + @"overleftarrow": [[MTMathStackCommandSpec alloc] initWithOver:leftArrow under:nil displayClass:kMTMathAtomOrdinary], + @"overleftrightarrow": [[MTMathStackCommandSpec alloc] initWithOver:leftRightArrow under:nil displayClass:kMTMathAtomOrdinary], + @"underrightarrow": [[MTMathStackCommandSpec alloc] initWithOver:nil under:rightArrow displayClass:kMTMathAtomOrdinary], + @"underleftarrow": [[MTMathStackCommandSpec alloc] initWithOver:nil under:leftArrow displayClass:kMTMathAtomOrdinary], + @"underleftrightarrow":[[MTMathStackCommandSpec alloc] initWithOver:nil under:leftRightArrow displayClass:kMTMathAtomOrdinary], + @"overbrace": [[MTMathStackCommandSpec alloc] initWithOver:overBrace under:nil displayClass:kMTMathAtomOrdinary], + @"underbrace": [[MTMathStackCommandSpec alloc] initWithOver:nil under:underBrace displayClass:kMTMathAtomOrdinary], + }; + } + return stackCommands; +} + ++ (nullable MTMathStack*) stackAtomForCommand:(NSString*)command +{ + MTMathStackCommandSpec* spec = [self stackCommands][command]; + if (!spec) { + return nil; + } + MTMathStack* stack = [[MTMathStack alloc] init]; + stack.over = spec.overConstruction; + stack.under = spec.underConstruction; + stack.displayClass = spec.displayClass; + return stack; +} + +/// Returns a canonical key string encoding the over/under glyphs plus displayClass. +/// Used as the key for the reverse-lookup dictionary built in stackCommandForStack:. +static NSString* StackCommandKey(MTMathStackConstruction* _Nullable over, + MTMathStackConstruction* _Nullable under, + MTMathAtomType displayClass) +{ + NSString* og = over && over.glyph ? over.glyph : @""; + NSString* ug = under && under.glyph ? under.glyph : @""; + return [NSString stringWithFormat:@"%@|%@|%lu", + og, ug, (unsigned long)displayClass]; +} + ++ (NSDictionary*) stackCommandReverseTable +{ + static NSDictionary* reverseTable = nil; + if (!reverseTable) { + NSDictionary* forward = [self stackCommands]; + NSMutableDictionary* mutable = [NSMutableDictionary dictionaryWithCapacity:forward.count]; + for (NSString* cmd in forward) { + MTMathStackCommandSpec* spec = forward[cmd]; + NSString* key = StackCommandKey(spec.overConstruction, spec.underConstruction, spec.displayClass); + mutable[key] = cmd; + } + reverseTable = [mutable copy]; + } + return reverseTable; +} + ++ (nullable NSString*) stackCommandForStack:(MTMathStack*)stack +{ + // Only Extensible constructions are in the Phase-1 table; non-Extensible + // (MathList / Rule) or constructions with non-canonical field values won't match. + if (stack.over && stack.over.kind != kMTMathStackConstructionExtensible) { + return nil; + } + if (stack.under && stack.under.kind != kMTMathStackConstructionExtensible) { + return nil; + } + NSString* key = StackCommandKey(stack.over, stack.under, stack.displayClass); + return [self stackCommandReverseTable][key]; +} + @end diff --git a/iosMath/lib/MTMathList.h b/iosMath/lib/MTMathList.h index cd8cff47..52c03f6d 100644 --- a/iosMath/lib/MTMathList.h +++ b/iosMath/lib/MTMathList.h @@ -58,7 +58,11 @@ typedef NS_ENUM(NSUInteger, MTMathAtomType) kMTMathAtomOverline, /// An accented atom - Accent in TeX kMTMathAtomAccent, - + /// A generic over/under stack atom. Supports \overrightarrow, \overleftarrow, + /// \overleftrightarrow, \underrightarrow, \underleftarrow, \underleftrightarrow, + /// \overbrace, \underbrace. Future: \stackrel, \overset, \underset. + kMTMathAtomStack, + // Atoms after this point do not support subscripts or superscripts /// A left atom - Left & Right in TeX. We don't need two since we track boundaries separately. @@ -373,6 +377,86 @@ typedef NS_ENUM(unsigned int, MTLineStyle) { @end +/** + @typedef MTMathStackConstructionKind + @brief Describes how an over/under row in an `MTMathStack` is produced. + */ +typedef NS_ENUM(NSUInteger, MTMathStackConstructionKind) { + /// An extensible horizontal construction: a single stretchy cap glyph. The typesetter + /// walks the cap's OpenType h_variants and falls back to the font's + /// HorizontalGlyphAssembly to cover wide bases. + kMTMathStackConstructionExtensible, + /// A math list typeset at a specified style (for \stackrel / \overset / \underset). + kMTMathStackConstructionMathList, + /// A horizontal rule (reserved for future \overline / \underline migration; unused in Phase 1). + kMTMathStackConstructionRule, +}; + +/** + An immutable value object describing how one row (over or under) of an `MTMathStack` is produced. + + For the `Extensible` kind, `glyph` holds a single Unicode codepoint string identifying the + stretchy cap glyph. The typesetter picks the smallest preset h_variant whose width covers the + base; if none is wide enough it falls back to the font's OpenType HorizontalGlyphAssembly + (lft + ex×N + md? + rt) with per-part connector overlaps. The `Rule` kind is reserved for a + future migration of \overline / \underline and is not used in Phase 1. + */ +@interface MTMathStackConstruction : NSObject + +@property (nonatomic, readonly) MTMathStackConstructionKind kind; + +// Extensible fields (kind == kMTMathStackConstructionExtensible) +/// The stretchy cap glyph — a single Unicode codepoint string. +@property (nonatomic, readonly, nullable) NSString* glyph; + +// MathList fields (kind == kMTMathStackConstructionMathList) +@property (nonatomic, readonly, nullable) MTMathList* list; +@property (nonatomic, readonly) MTLineStyle listStyle; +@property (nonatomic, readonly) BOOL listCramped; + +// Rule fields (kind == kMTMathStackConstructionRule) +/// Rule thickness in points. 0 means use the font's default. +@property (nonatomic, readonly) CGFloat ruleThickness; + ++ (instancetype)extensibleWithGlyph:(NSString*)glyph; ++ (instancetype)mathListWithList:(MTMathList*)list + style:(MTLineStyle)style + cramped:(BOOL)cramped; ++ (instancetype)ruleWithThickness:(CGFloat)thickness; + +@end + +/** + A generic over/under stack atom. Carries a base inner list with optional over and under + constructions. Supports \overrightarrow, \overleftarrow, \overleftrightarrow, + \underrightarrow, \underleftarrow, \underleftrightarrow, \overbrace, \underbrace. + + The `displayClass` controls inter-element spacing (Rule 16 re-classification). The default + is `kMTMathAtomOrdinary` which matches \overline / \underline / accents. \stackrel sets it + to `kMTMathAtomRelation`. + + Scripts (super/subscript) are allowed because `kMTMathAtomStack` is below + `kMTMathAtomBoundary` in the enum. + */ +@interface MTMathStack : MTMathAtom + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/// The base math list whose width drives the construction width. +@property (nonatomic, nullable) MTMathList* innerList; + +/// The over-row construction, or nil if there is no over row. +@property (nonatomic, nullable) MTMathStackConstruction* over; + +/// The under-row construction, or nil if there is no under row. +@property (nonatomic, nullable) MTMathStackConstruction* under; + +/// The math class used for inter-element spacing after typesetting. +/// Default: kMTMathAtomOrdinary. +@property (nonatomic) MTMathAtomType displayClass; + +@end + /** An atom representing an color element. @note None of the usual fields of the `MTMathAtom` apply even though this class inherits from `MTMathAtom`. i.e. it is meaningless to have a value diff --git a/iosMath/lib/MTMathList.m b/iosMath/lib/MTMathList.m index 9db103af..26480f66 100644 --- a/iosMath/lib/MTMathList.m +++ b/iosMath/lib/MTMathList.m @@ -60,6 +60,8 @@ static BOOL isNotBinaryOperator(MTMathAtom* prevNode) return @"Overline"; case kMTMathAtomAccent: return @"Accent"; + case kMTMathAtomStack: + return @"Stack"; case kMTMathAtomBoundary: return @"Boundary"; case kMTMathAtomSpace: @@ -117,7 +119,10 @@ + (instancetype)atomWithType:(MTMathAtomType)type value:(NSString *)value case kMTMathAtomAccent: return [[MTAccent alloc] initWithValue:value]; - + + case kMTMathAtomStack: + return [[MTMathStack alloc] init]; + case kMTMathAtomSpace: return [[MTMathSpace alloc] initWithSpace:0]; @@ -923,6 +928,108 @@ - (NSUInteger) numRows @end +#pragma mark - MTMathStackConstruction + +@implementation MTMathStackConstruction + ++ (instancetype)extensibleWithGlyph:(NSString*)glyph +{ + NSParameterAssert(glyph); + MTMathStackConstruction* c = [[self alloc] init]; + c->_kind = kMTMathStackConstructionExtensible; + c->_glyph = [glyph copy]; + return c; +} + ++ (instancetype)mathListWithList:(MTMathList*)list + style:(MTLineStyle)style + cramped:(BOOL)cramped +{ + NSParameterAssert(list); + MTMathStackConstruction* c = [[self alloc] init]; + c->_kind = kMTMathStackConstructionMathList; + c->_list = [list copy]; + c->_listStyle = style; + c->_listCramped = cramped; + return c; +} + ++ (instancetype)ruleWithThickness:(CGFloat)thickness +{ + MTMathStackConstruction* c = [[self alloc] init]; + c->_kind = kMTMathStackConstructionRule; + c->_ruleThickness = thickness; + return c; +} + +- (id)copyWithZone:(NSZone*)zone +{ + MTMathStackConstruction* copy = [[MTMathStackConstruction allocWithZone:zone] init]; + copy->_kind = _kind; + copy->_glyph = [_glyph copyWithZone:zone]; + copy->_list = [_list copyWithZone:zone]; + copy->_listStyle = _listStyle; + copy->_listCramped = _listCramped; + copy->_ruleThickness = _ruleThickness; + return copy; +} + +@end + +#pragma mark - MTMathStack + +@implementation MTMathStack + +- (instancetype)init +{ + self = [super initWithType:kMTMathAtomStack value:@""]; + if (self) { + _displayClass = kMTMathAtomOrdinary; + } + return self; +} + +- (instancetype)initWithType:(MTMathAtomType)type value:(NSString *)value +{ + if (type == kMTMathAtomStack) { + return [self init]; + } + @throw [NSException exceptionWithName:@"InvalidMethod" + reason:@"[MTMathStack initWithType:value:] cannot be called. Use [MTMathStack init] instead." + userInfo:nil]; +} + +- (id)copyWithZone:(NSZone*)zone +{ + MTMathStack* copy = [super copyWithZone:zone]; + copy.innerList = [self.innerList copyWithZone:zone]; + copy.over = [self.over copyWithZone:zone]; + copy.under = [self.under copyWithZone:zone]; + copy->_displayClass = self.displayClass; + return copy; +} + +- (instancetype)finalized +{ + MTMathStack* newStack = [super finalized]; + newStack.innerList = newStack.innerList.finalized; + if (newStack.over && newStack.over.kind == kMTMathStackConstructionMathList) { + MTMathStackConstruction* overConst = newStack.over; + newStack.over = [MTMathStackConstruction mathListWithList:overConst.list.finalized + style:overConst.listStyle + cramped:overConst.listCramped]; + } + if (newStack.under && newStack.under.kind == kMTMathStackConstructionMathList) { + MTMathStackConstruction* underConst = newStack.under; + newStack.under = [MTMathStackConstruction mathListWithList:underConst.list.finalized + style:underConst.listStyle + cramped:underConst.listCramped]; + } + return newStack; +} + +@end + #pragma mark - MTMathList @implementation MTMathList { diff --git a/iosMath/lib/MTMathListBuilder.m b/iosMath/lib/MTMathListBuilder.m index 940e1455..8c04bffa 100644 --- a/iosMath/lib/MTMathListBuilder.m +++ b/iosMath/lib/MTMathListBuilder.m @@ -501,7 +501,14 @@ - (MTMathAtom*) atomForCommand:(NSString*) command MTUnderLine* under = [MTUnderLine new]; under.innerList = [self buildInternal:true]; return under; - } else if ([command isEqualToString:@"begin"]) { + } else { + MTMathStack* stack = [MTMathAtomFactory stackAtomForCommand:command]; + if (stack) { + stack.innerList = [self buildInternal:true]; + return stack; + } + } + if ([command isEqualToString:@"begin"]) { NSString* env = [self readEnvironment]; if (!env) { return nil; @@ -894,6 +901,15 @@ + (NSString *)mathListToString:(MTMathList *)ml } else if (atom.type == kMTMathAtomAccent) { MTAccent* accent = (MTAccent*) atom; [str appendFormat:@"\\%@{%@}", [MTMathAtomFactory accentName:accent], [self mathListToString:accent.innerList]]; + } else if (atom.type == kMTMathAtomStack) { + MTMathStack* s = (MTMathStack*) atom; + NSString* cmd = [MTMathAtomFactory stackCommandForStack:s]; + if (cmd) { + [str appendFormat:@"\\%@{%@}", cmd, [self mathListToString:s.innerList]]; + } else { + // Programmatically-built stack with non-canonical constructions — emit only the inner list. + [str appendString:[self mathListToString:s.innerList]]; + } } else if (atom.type == kMTMathAtomLargeOperator) { MTLargeOperator* op = (MTLargeOperator*) atom; NSString* command = [MTMathAtomFactory latexSymbolNameForAtom:atom]; diff --git a/iosMath/render/MTMathListDisplay.h b/iosMath/render/MTMathListDisplay.h index 3d00b1e0..c501ea56 100644 --- a/iosMath/render/MTMathListDisplay.h +++ b/iosMath/render/MTMathListDisplay.h @@ -171,6 +171,28 @@ typedef NS_ENUM(unsigned int, MTLinePosition) { @end +/** + Rendering of a generic over/under stack as an MTDisplay. + + Produced by `MTTypesetter` for `MTMathStack` atoms (`\overrightarrow`, `\overleftarrow`, + `\overbrace`, `\underbrace`, and similar commands). The `base` display is positioned at the + stack's baseline; `over` and `under` are pre-positioned above and below it by the typesetter. + */ +@interface MTStackDisplay : MTDisplay + +- (instancetype)init NS_UNAVAILABLE; + +/// The base (inner-list) display. Its baseline is the stack's baseline. +@property (nonatomic, readonly) MTMathListDisplay* base; + +/// The over-row display, or nil if there is no over row. +@property (nonatomic, readonly, nullable) MTDisplay* over; + +/// The under-row display, or nil if there is no under row. +@property (nonatomic, readonly, nullable) MTDisplay* under; + +@end + /// Rendering an accent as a display @interface MTAccentDisplay : MTDisplay diff --git a/iosMath/render/MTMathListDisplay.m b/iosMath/render/MTMathListDisplay.m index 43e89902..e944fcca 100644 --- a/iosMath/render/MTMathListDisplay.m +++ b/iosMath/render/MTMathListDisplay.m @@ -799,6 +799,113 @@ - (void)draw:(CGContextRef)context } @end +#pragma mark - MTStackDisplay + +@implementation MTStackDisplay + +- (instancetype)initWithBase:(MTMathListDisplay*)base + over:(MTDisplay*)over + under:(MTDisplay*)under + range:(NSRange)range +{ + self = [super init]; + if (self) { + _base = base; + _over = over; + _under = under; + self.range = range; + } + return self; +} + +- (void)setPosition:(CGPoint)position +{ + // Shift all children by the delta so their pre-computed relative offsets are preserved. + CGPoint delta = CGPointMake(position.x - self.position.x, position.y - self.position.y); + super.position = position; + _base.position = CGPointMake(_base.position.x + delta.x, _base.position.y + delta.y); + if (_over) { + _over.position = CGPointMake(_over.position.x + delta.x, _over.position.y + delta.y); + } + if (_under) { + _under.position = CGPointMake(_under.position.x + delta.x, _under.position.y + delta.y); + } +} + +- (void)setTextColor:(MTColor *)textColor +{ + [super setTextColor:textColor]; + _base.textColor = textColor; + _over.textColor = textColor; + _under.textColor = textColor; +} + +- (void)draw:(CGContextRef)context +{ + [super draw:context]; + [_base draw:context]; + [_over draw:context]; + [_under draw:context]; +} + +@end + +#pragma mark - MTHorizontalGlyphAssemblyDisplay + +@implementation MTHorizontalGlyphAssemblyDisplay { + CGGlyph *_glyphs; + CGPoint *_positions; + MTFont* _font; + NSInteger _numGlyphs; +} + +- (instancetype)initWithGlyphs:(NSArray*)glyphs + positions:(NSArray*)positions + font:(MTFont*)font + range:(NSRange)range +{ + self = [super init]; + if (self) { + NSAssert(glyphs.count == positions.count, @"Glyphs and positions need to match"); + _numGlyphs = glyphs.count; + _glyphs = malloc(sizeof(CGGlyph) * _numGlyphs); + _positions = malloc(sizeof(CGPoint) * _numGlyphs); + for (int i = 0; i < _numGlyphs; i++) { + _glyphs[i] = glyphs[i].shortValue; + CGPoint pt; + [positions[i] getValue:&pt]; + _positions[i] = pt; + } + _font = font; + self.range = range; + self.position = CGPointZero; + } + return self; +} + +- (void)draw:(CGContextRef)context +{ + [super draw:context]; + CGContextSaveGState(context); + + [self.textColor setFill]; + + CGContextTranslateCTM(context, self.position.x, self.position.y); + CGContextSetTextPosition(context, 0, 0); + + CTFontDrawGlyphs(_font.ctFont, _glyphs, _positions, _numGlyphs, context); + + CGContextRestoreGState(context); +} + +- (void)dealloc +{ + free(_glyphs); + free(_positions); +} + +@end + #pragma mark - MTInnerDisplay @implementation MTInnerDisplay { diff --git a/iosMath/render/internal/MTFontMathTable.h b/iosMath/render/internal/MTFontMathTable.h index afe5d33f..4eaba4cb 100644 --- a/iosMath/render/internal/MTFontMathTable.h +++ b/iosMath/render/internal/MTFontMathTable.h @@ -119,6 +119,12 @@ @property (nonatomic, readonly) CGFloat overbarRuleThickness; // \xi_8 in TeX @property (nonatomic, readonly) CGFloat overbarExtraAscender; // \xi_8 in TeX +#pragma mark Stretch Stack +@property (nonatomic, readonly) CGFloat stretchStackTopShiftUp; +@property (nonatomic, readonly) CGFloat stretchStackBottomShiftDown; +@property (nonatomic, readonly) CGFloat stretchStackGapAboveMin; +@property (nonatomic, readonly) CGFloat stretchStackGapBelowMin; + #pragma mark Constants @property (nonatomic, readonly) CGFloat axisHeight; // \sigma_22 in TeX @@ -165,4 +171,8 @@ of this glyph. If there is no glyph assembly defined, returns nil. */ - (nullable NSArray*) getVerticalGlyphAssemblyForGlyph:(CGGlyph) glyph; +/** Returns an array of the glyph parts to be used for constructing horizontal variants + of this glyph. If there is no glyph assembly defined, returns nil. */ +- (nullable NSArray*) getHorizontalGlyphAssemblyForGlyph:(CGGlyph) glyph; + @end diff --git a/iosMath/render/internal/MTFontMathTable.m b/iosMath/render/internal/MTFontMathTable.m index 338a0c1c..3b66bf48 100644 --- a/iosMath/render/internal/MTFontMathTable.m +++ b/iosMath/render/internal/MTFontMathTable.m @@ -52,7 +52,7 @@ - (instancetype)initWithFont:(nonnull MTFont*) font mathTable:(nonnull NSDiction _unitsPerEm = CTFontGetUnitsPerEm(font.ctFont); _fontSize = font.fontSize; _mathTable = mathTable; - if (![@"1.3" isEqualToString:_mathTable[@"version"]]) { + if (![@"1.4" isEqualToString:_mathTable[@"version"]]) { // Invalid version @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"Invalid version of math table plist: %@", _mathTable[@"version"]] @@ -499,20 +499,19 @@ - (CGFloat)minConnectorOverlap } static NSString* const kVertAssembly = @"v_assembly"; +static NSString* const kHorizAssembly = @"h_assembly"; static NSString* const kAssemblyParts = @"parts"; -- (NSArray *)getVerticalGlyphAssemblyForGlyph:(CGGlyph)glyph +- (NSArray *)getGlyphAssemblyFromTable:(NSString*)tableKey forGlyph:(CGGlyph)glyph { - NSDictionary* assemblyTable = (NSDictionary*) _mathTable[kVertAssembly]; + NSDictionary* assemblyTable = (NSDictionary*) _mathTable[tableKey]; NSString* glyphName = [self.font getGlyphName:glyph]; NSDictionary* assemblyInfo = (NSDictionary*) assemblyTable[glyphName]; if (!assemblyInfo) { - // No vertical assembly defined for glyph return nil; } NSArray* parts = (NSArray*) assemblyInfo[kAssemblyParts]; if (!parts) { - // parts should always have been defined, but if it isn't return nil return nil; } NSMutableArray* rv = [NSMutableArray array]; @@ -526,12 +525,22 @@ - (CGFloat)minConnectorOverlap part.startConnectorLength = [self fontUnitsToPt:start.intValue]; NSNumber* ext = (NSNumber*) partInfo[@"extender"]; part.isExtender = ext.boolValue; - NSString* glyphName = (NSString*) partInfo[@"glyph"]; - part.glyph = [self.font getGlyphWithName:glyphName]; - + NSString* partGlyphName = (NSString*) partInfo[@"glyph"]; + part.glyph = [self.font getGlyphWithName:partGlyphName]; + [rv addObject:part]; } return rv; } +- (NSArray *)getVerticalGlyphAssemblyForGlyph:(CGGlyph)glyph +{ + return [self getGlyphAssemblyFromTable:kVertAssembly forGlyph:glyph]; +} + +- (NSArray *)getHorizontalGlyphAssemblyForGlyph:(CGGlyph)glyph +{ + return [self getGlyphAssemblyFromTable:kHorizAssembly forGlyph:glyph]; +} + @end diff --git a/iosMath/render/internal/MTMathListDisplayInternal.h b/iosMath/render/internal/MTMathListDisplayInternal.h index 836fc626..b451bbcd 100644 --- a/iosMath/render/internal/MTMathListDisplayInternal.h +++ b/iosMath/render/internal/MTMathListDisplayInternal.h @@ -114,6 +114,33 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface MTStackDisplay () + +- (instancetype)initWithBase:(MTMathListDisplay*) base + over:(nullable MTDisplay*) over + under:(nullable MTDisplay*) under + range:(NSRange) range NS_DESIGNATED_INITIALIZER; + +@end + +/// Horizontal assembled glyph display — horizontal twin of `MTGlyphConstructionDisplay`. +/// Used to render OpenType HorizontalGlyphAssembly: [lft, ex×N, (md, ex×N), rt] with per-part +/// connector overlaps honoring MinConnectorOverlap. +@interface MTHorizontalGlyphAssemblyDisplay : MTDisplay + +- (instancetype)init NS_UNAVAILABLE; + +/// @param glyphs Array of CGGlyph values (as NSNumber shortValue). +/// @param positions Array of CGPoint values (as NSValue CGPointValue); x=horizontal offset, y=0. +/// @param font The font used to draw the glyphs. +/// @param range Source range in the parent math list. +- (instancetype)initWithGlyphs:(NSArray*) glyphs + positions:(NSArray*) positions + font:(MTFont*) font + range:(NSRange) range NS_DESIGNATED_INITIALIZER; + +@end + @interface MTInnerDisplay () diff --git a/iosMath/render/internal/MTTypesetter.m b/iosMath/render/internal/MTTypesetter.m index 1aa2af80..251f55e1 100644 --- a/iosMath/render/internal/MTTypesetter.m +++ b/iosMath/render/internal/MTTypesetter.m @@ -780,7 +780,24 @@ - (void) createDisplayAtoms:(NSArray*) preprocessed } break; } - + + case kMTMathAtomStack: { + if (_currentLine.length > 0) { + [self addDisplayLine]; + } + MTMathStack* stack = (MTMathStack*) atom; + // Stack is treated as displayClass (default Ord) for inter-element spacing (Rule 16). + [self addInterElementSpace:prevNode currentType:stack.displayClass]; + atom.type = stack.displayClass; + MTDisplay* display = [self makeStack:stack]; + [_displayAtoms addObject:display]; + _currentPosition.x += display.width; + if (atom.subScript || atom.superScript) { + [self makeScripts:atom display:display index:atom.indexRange.location delta:0]; + } + break; + } + case kMTMathAtomTable: { // stash the existing layout if (_currentLine.length > 0) { @@ -1379,6 +1396,50 @@ - (MTGlyphConstructionDisplay*) constructGlyph:(CGGlyph) glyph withHeight:(CGFlo return display; } +// Horizontal twin of -constructGlyph:withHeight:. Builds an MTHorizontalGlyphAssemblyDisplay +// from the font's HorizontalGlyphAssembly parts (left part + N extenders + optional middle + +// N extenders + right part), with overlaps honoring per-part connector lengths and the +// font-wide MinConnectorOverlap. Returns nil if no horizontal assembly is defined for the glyph. +- (MTHorizontalGlyphAssemblyDisplay*) constructHorizontalGlyph:(CGGlyph) glyph withWidth:(CGFloat) glyphWidth range:(NSRange) range +{ + NSArray* parts = [_styleFont.mathTable getHorizontalGlyphAssemblyForGlyph:glyph]; + if (parts.count == 0) { + return nil; + } + NSArray* glyphs, *offsets; + CGFloat width; + [self constructGlyphWithParts:parts height:glyphWidth glyphs:&glyphs offsets:&offsets height:&width]; + + // Convert 1D offsets (along the construction axis) into 2D positions for the horizontal display. + NSMutableArray* positions = [NSMutableArray arrayWithCapacity:offsets.count]; + for (NSNumber* offset in offsets) { + CGPoint pt = CGPointMake(offset.floatValue, 0); + [positions addObject:[NSValue value:&pt withObjCType:@encode(CGPoint)]]; + } + + // Compute combined ascent/descent from the parts' bounding boxes. + NSUInteger n = glyphs.count; + CGGlyph cgGlyphs[n]; + for (NSUInteger i = 0; i < n; i++) cgGlyphs[i] = glyphs[i].shortValue; + CGRect bboxes[n]; + CTFontGetBoundingRectsForGlyphs(_styleFont.ctFont, kCTFontOrientationDefault, cgGlyphs, bboxes, n); + CGFloat maxAsc = 0, maxDes = 0; + for (NSUInteger i = 0; i < n; i++) { + CGFloat a, d; + getBboxDetails(bboxes[i], &a, &d); + if (a > maxAsc) maxAsc = a; + if (d > maxDes) maxDes = d; + } + + MTHorizontalGlyphAssemblyDisplay* display = + [[MTHorizontalGlyphAssemblyDisplay alloc] initWithGlyphs:glyphs positions:positions font:_styleFont range:range]; + display.ascent = maxAsc; + display.descent = maxDes; + display.width = width; + display.position = CGPointZero; + return display; +} + - (void) constructGlyphWithParts:(NSArray*) parts height:(CGFloat) glyphHeight glyphs:(NSArray**) glyphs offsets:(NSArray**) offsets height:(CGFloat*) height { NSParameterAssert(glyphs); @@ -1723,10 +1784,181 @@ - (MTDisplay*) makeAccent:(MTAccent*) accent CGFloat ascent = accentee.ascent - delta + glyphAscent; display.ascent = MAX(accentee.ascent, ascent); display.position = _currentPosition; - + return display; } +#pragma mark - Stack + +// Role parameter distinguishes over vs under so per-role metric choices can be added later. +typedef NS_ENUM(NSUInteger, MTStackRole) { + kMTStackRoleOver, + kMTStackRoleUnder, +}; + +// Find the smallest horizontal variant whose width >= minWidth, or the largest available if none qualify. +// Not a reuse of -findVariantGlyph:withMaxWidth: (which picks the largest variant <= maxWidth). +// This helper is used for stretchy constructions that must cover a target width. +- (CGGlyph) findStretchyVariantGlyph:(CGGlyph)glyph + withMinWidth:(CGFloat)minWidth + glyphAscent:(CGFloat*)glyphAscent + glyphDescent:(CGFloat*)glyphDescent + glyphWidth:(CGFloat*)glyphWidth +{ + NSArray* variants = [_styleFont.mathTable getHorizontalVariantsForGlyph:glyph]; + CFIndex numVariants = variants.count; + NSAssert(numVariants > 0, @"A glyph is always its own variant, so numVariants should be > 0"); + CGGlyph glyphs[numVariants]; + for (CFIndex i = 0; i < numVariants; i++) { + glyphs[i] = [variants[i] shortValue]; + } + + CGRect bboxes[numVariants]; + CGSize advances[numVariants]; + CTFontGetBoundingRectsForGlyphs(_styleFont.ctFont, kCTFontOrientationDefault, glyphs, bboxes, numVariants); + CTFontGetAdvancesForGlyphs(_styleFont.ctFont, kCTFontOrientationDefault, glyphs, advances, numVariants); + + // Walk variants in order (smallest to largest); return the first one that meets minWidth. + CGGlyph bestGlyph = glyphs[numVariants - 1]; + *glyphAscent = 0; + *glyphDescent = 0; + *glyphWidth = advances[numVariants - 1].width; + CGFloat asc, des; + getBboxDetails(bboxes[numVariants - 1], &asc, &des); + *glyphAscent = asc; + *glyphDescent = des; + + for (CFIndex i = 0; i < numVariants; i++) { + CGFloat w = advances[i].width; + getBboxDetails(bboxes[i], &asc, &des); + if (w >= minWidth) { + bestGlyph = glyphs[i]; + *glyphAscent = asc; + *glyphDescent = des; + *glyphWidth = w; + break; + } + // Track the largest seen so far in case no variant meets minWidth (saturation). + bestGlyph = glyphs[i]; + *glyphAscent = asc; + *glyphDescent = des; + *glyphWidth = w; + } + return bestGlyph; +} + +- (MTDisplay*) buildHorizontalExtensibleDisplay:(MTMathStackConstruction*)construction + forWidth:(CGFloat)targetWidth + range:(NSRange)range +{ + // Single stretchy cap glyph (arrows: \overrightarrow etc., braces: \overbrace, \underbrace). + // Pipeline (mirrors -getRadicalGlyphWithHeight: for vertical glyphs): + // 1. Try preset h_variants of the cap; pick smallest variant whose width >= targetWidth. + // 2. If no variant is wide enough, try the font's HorizontalGlyphAssembly to build + // [lft, ex×N, (md, ex×N), rt] with overlaps honored per the OpenType spec. + // 3. If neither covers the width, saturate to the largest available variant. + NSString* capStr = construction.glyph; + if (!capStr) { + return nil; + } + CGGlyph capGlyph = [self findGlyphForCharacterAtIndex:capStr.length - 1 inString:capStr]; + if (capGlyph == 0) { + return nil; + } + CGFloat asc, des, w; + CGGlyph bestGlyph = [self findStretchyVariantGlyph:capGlyph + withMinWidth:targetWidth + glyphAscent:&asc + glyphDescent:&des + glyphWidth:&w]; + if (w >= targetWidth) { + MTGlyphDisplay* glyphDisplay = [[MTGlyphDisplay alloc] initWithGlpyh:bestGlyph range:range font:_styleFont]; + glyphDisplay.ascent = asc; + glyphDisplay.descent = des; + glyphDisplay.width = w; + glyphDisplay.position = CGPointZero; + return glyphDisplay; + } + // No variant covers the width; try font-supplied horizontal glyph assembly. + MTHorizontalGlyphAssemblyDisplay* assembled = [self constructHorizontalGlyph:capGlyph withWidth:targetWidth range:range]; + if (assembled) { + return assembled; + } + // Saturation: use the largest available variant. + MTGlyphDisplay* glyphDisplay = [[MTGlyphDisplay alloc] initWithGlpyh:bestGlyph range:range font:_styleFont]; + glyphDisplay.ascent = asc; + glyphDisplay.descent = des; + glyphDisplay.width = w; + glyphDisplay.position = CGPointZero; + return glyphDisplay; +} + +- (MTDisplay*) buildStackConstruction:(MTMathStackConstruction*)construction + forWidth:(CGFloat)targetWidth + role:(MTStackRole)role + range:(NSRange)range +{ + switch (construction.kind) { + case kMTMathStackConstructionExtensible: + return [self buildHorizontalExtensibleDisplay:construction forWidth:targetWidth range:range]; + + case kMTMathStackConstructionMathList: { + return [MTTypesetter createLineForMathList:construction.list + font:_font + style:construction.listStyle + cramped:construction.listCramped]; + } + + case kMTMathStackConstructionRule: + NSAssert(NO, @"Rule construction not yet implemented (reserved for \\overline migration)."); + return nil; + } + return nil; +} + +- (MTDisplay*) makeStack:(MTMathStack*)stack +{ + // TODO: Honor stretchStackTopShiftUp/BottomShiftDown when the over/under + // construction is a single-glyph stretchy shape whose vertical ink is bounded. + // Current Phase-1 behavior uses the gap metrics uniformly for all construction + // kinds; revisit if brace/accent-like constructions need tighter clearance. + MTMathListDisplay* baseDisplay = + [MTTypesetter createLineForMathList:stack.innerList font:_font style:_style cramped:_cramped]; + CGFloat targetWidth = baseDisplay.width; + + MTDisplay* overDisp = stack.over ? [self buildStackConstruction:stack.over forWidth:targetWidth role:kMTStackRoleOver range:stack.indexRange] : nil; + MTDisplay* underDisp = stack.under ? [self buildStackConstruction:stack.under forWidth:targetWidth role:kMTStackRoleUnder range:stack.indexRange] : nil; + + CGFloat overGap = _styleFont.mathTable.stretchStackGapAboveMin; + CGFloat underGap = _styleFont.mathTable.stretchStackGapBelowMin; + + CGFloat totalWidth = MAX(baseDisplay.width, + MAX(overDisp ? overDisp.width : 0, + underDisp ? underDisp.width : 0)); + + baseDisplay.position = CGPointMake((totalWidth - baseDisplay.width) / 2.0, 0); + if (overDisp) { + overDisp.position = CGPointMake((totalWidth - overDisp.width) / 2.0, + baseDisplay.ascent + overGap + overDisp.descent); + } + if (underDisp) { + underDisp.position = CGPointMake((totalWidth - underDisp.width) / 2.0, + -(baseDisplay.descent + underGap + underDisp.ascent)); + } + + MTStackDisplay* d = [[MTStackDisplay alloc] initWithBase:baseDisplay + over:overDisp + under:underDisp + range:stack.indexRange]; + d.position = _currentPosition; + d.width = totalWidth; + d.ascent = baseDisplay.ascent + + (overDisp ? overGap + overDisp.ascent + overDisp.descent : 0); + d.descent = baseDisplay.descent + + (underDisp ? underGap + underDisp.ascent + underDisp.descent : 0); + return d; +} + #pragma mark - Table static const CGFloat kBaseLineSkipMultiplier = 1.2; // default base line stretch is 12 pt for 10pt font. diff --git a/iosMathTests/MTMathListBuilderTest.m b/iosMathTests/MTMathListBuilderTest.m index a1f7dc22..0f18ad1f 100644 --- a/iosMathTests/MTMathListBuilderTest.m +++ b/iosMathTests/MTMathListBuilderTest.m @@ -1524,4 +1524,92 @@ - (void) testLargeDelimiterSerializationCanonicalDelimiters } } +- (void) testStackCommands +{ + // Each entry: command, overGlyph, underGlyph. + // Each cap is the stretchy cap glyph; the typesetter walks its OpenType h_variants + // and falls back to HorizontalGlyphAssembly to cover wide bases. + NSArray* cases = @[ + @[@"overrightarrow", @"\u2192", [NSNull null]], + @[@"overleftarrow", @"\u2190", [NSNull null]], + @[@"overleftrightarrow", @"\u2194", [NSNull null]], + @[@"underrightarrow", [NSNull null], @"\u2192"], + @[@"underleftarrow", [NSNull null], @"\u2190"], + @[@"underleftrightarrow",[NSNull null], @"\u2194"], + @[@"overbrace", @"\u23DE", [NSNull null]], + @[@"underbrace", [NSNull null], @"\u23DF"], + ]; + + for (NSArray* row in cases) { + NSString* cmd = row[0]; + id overGlyph = row[1]; + id underGlyph = row[2]; + + NSString* latex = [NSString stringWithFormat:@"\\%@{x}", cmd]; + MTMathList* list = [MTMathListBuilder buildFromString:latex]; + XCTAssertNotNil(list, @"nil list for \\%@", cmd); + XCTAssertEqual(list.atoms.count, 1u, @"atom count for \\%@", cmd); + + MTMathStack* stack = list.atoms[0]; + XCTAssertEqual(stack.type, kMTMathAtomStack, @"type for \\%@", cmd); + XCTAssertEqual(stack.displayClass, kMTMathAtomOrdinary, @"displayClass for \\%@", cmd); + XCTAssertNotNil(stack.innerList, @"innerList for \\%@", cmd); + XCTAssertEqual(stack.innerList.atoms.count, 1u, @"innerList count for \\%@", cmd); + + if (![overGlyph isKindOfClass:[NSNull class]]) { + XCTAssertNotNil(stack.over, @"over for \\%@", cmd); + XCTAssertEqual(stack.over.kind, kMTMathStackConstructionExtensible, @"over.kind for \\%@", cmd); + XCTAssertEqualObjects(stack.over.glyph, overGlyph, @"over.glyph for \\%@", cmd); + } else { + XCTAssertNil(stack.over, @"over should be nil for \\%@", cmd); + } + + if (![underGlyph isKindOfClass:[NSNull class]]) { + XCTAssertNotNil(stack.under, @"under for \\%@", cmd); + XCTAssertEqual(stack.under.kind, kMTMathStackConstructionExtensible, @"under.kind for \\%@", cmd); + XCTAssertEqualObjects(stack.under.glyph, underGlyph, @"under.glyph for \\%@", cmd); + } else { + XCTAssertNil(stack.under, @"under should be nil for \\%@", cmd); + } + + // Round-trip serialization. + NSString* roundTrip = [MTMathListBuilder mathListToString:list]; + NSString* expectedLatex = [NSString stringWithFormat:@"\\%@{x}", cmd]; + XCTAssertEqualObjects(roundTrip, expectedLatex, @"round-trip for \\%@", cmd); + } +} + +- (void) testStackRoundTripNested +{ + NSString* input = @"\\overrightarrow{\\frac{a}{b}}"; + MTMathList* list = [MTMathListBuilder buildFromString:input]; + XCTAssertNotNil(list); + NSString* latex = [MTMathListBuilder mathListToString:list]; + XCTAssertEqualObjects(latex, @"\\overrightarrow{\\frac{a}{b}}"); +} + +- (void) testStackUnknownCommandFallthrough +{ + // \overfoo is not a known stack command — should produce a parse error. + NSError* error = nil; + MTMathList* list = [MTMathListBuilder buildFromString:@"\\overfoo{x}" error:&error]; + XCTAssertNil(list); + XCTAssertNotNil(error); +} + +- (void) testStackNonCanonicalSerializesInnerOnly +{ + // A programmatically-built stack with non-canonical fields (leftCap = "Z") cannot + // round-trip to a command name; serialization should emit only the inner list. + MTMathStack* stack = [[MTMathStack alloc] init]; + stack.over = [MTMathStackConstruction extensibleWithGlyph:@"Z"]; + MTMathList* inner = [MTMathList new]; + [inner addAtom:[MTMathAtomFactory atomForCharacter:'x']]; + stack.innerList = inner; + MTMathList* list = [MTMathList new]; + [list addAtom:stack]; + NSString* latex = [MTMathListBuilder mathListToString:list]; + XCTAssertEqualObjects(latex, @"x"); +} + @end diff --git a/iosMathTests/MTTypesetterTest.m b/iosMathTests/MTTypesetterTest.m index 65c11d96..a7628b31 100644 --- a/iosMathTests/MTTypesetterTest.m +++ b/iosMathTests/MTTypesetterTest.m @@ -12,6 +12,7 @@ #import "MTFont+Internal.h" #import "MTFontManager.h" #import "MTMathListDisplay.h" +#import "MTMathListDisplayInternal.h" #import "MTMathAtomFactory.h" #import "MTMathListBuilder.h" @@ -1683,5 +1684,227 @@ - (void)testLargeDelimiterSupportsScripts XCTAssertEqual(superscript.type, kMTLinePositionSuperscript); } +#pragma mark - Stack tests + +- (void)testOverrightarrowNarrow +{ + MTMathListDisplay* display = [self displayForLaTeX:@"\\overrightarrow{x}"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.type, kMTLinePositionRegular); + XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); + XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); + XCTAssertFalse(display.hasScript); + XCTAssertEqual(display.subDisplays.count, 1u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + MTStackDisplay* stack = (MTStackDisplay*)sub0; + XCTAssertTrue(NSEqualRanges(stack.range, NSMakeRange(0, 1))); + XCTAssertFalse(stack.hasScript); + XCTAssertTrue(CGPointEqualToPoint(stack.position, CGPointZero)); + + XCTAssertNotNil(stack.base); + XCTAssertNotNil(stack.over); + XCTAssertNil(stack.under); + + // For narrow 'x' the assembled-path fast-path fires: widest rightarrow variant covers x. + XCTAssertTrue([stack.over isKindOfClass:[MTGlyphDisplay class]]); + + // Base is at y=0 (baseline aligned to stack origin). + XCTAssertEqualWithAccuracy(stack.base.position.y, 0, 0.01); + + // Dimension assertions using dynamic font metrics. + CGFloat gapAbove = self.font.mathTable.stretchStackGapAboveMin; + CGFloat expectedAscent = stack.base.ascent + gapAbove + stack.over.ascent + stack.over.descent; + XCTAssertEqualWithAccuracy(display.ascent, expectedAscent, 0.01); + XCTAssertEqualWithAccuracy(display.descent, stack.base.descent, 0.01); + + // Width accommodates both base and over-row. + XCTAssertGreaterThanOrEqual(display.width + 0.01, stack.base.width); + XCTAssertGreaterThanOrEqual(display.width + 0.01, stack.over.width); +} + +- (void)testOverrightarrowWide +{ + MTMathListDisplay* display = [self displayForLaTeX:@"\\overrightarrow{ABCD}"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.subDisplays.count, 1u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + MTStackDisplay* stack = (MTStackDisplay*)sub0; + + XCTAssertNotNil(stack.over); + XCTAssertNil(stack.under); + + // Wide base forces assembly: over-row must be a horizontal glyph assembly. + XCTAssertTrue([stack.over isKindOfClass:[MTHorizontalGlyphAssemblyDisplay class]]); + + // Over must span the full base width. + XCTAssertGreaterThanOrEqual(stack.over.width + 0.01, stack.base.width); + + CGFloat gapAbove = self.font.mathTable.stretchStackGapAboveMin; + CGFloat expectedAscent = stack.base.ascent + gapAbove + stack.over.ascent + stack.over.descent; + XCTAssertEqualWithAccuracy(display.ascent, expectedAscent, 0.01); + XCTAssertEqualWithAccuracy(display.descent, stack.base.descent, 0.01); + XCTAssertGreaterThanOrEqual(display.width + 0.01, stack.base.width); +} + +- (void)testOverleftrightarrow +{ + MTMathListDisplay* display = [self displayForLaTeX:@"\\overleftrightarrow{xyz}"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.subDisplays.count, 1u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + MTStackDisplay* stack = (MTStackDisplay*)sub0; + + XCTAssertNotNil(stack.over); + XCTAssertNil(stack.under); + + // \overleftrightarrow uses U+2194 (single cap, stretchy). For 'xyz' (wider than the largest preset variant), uses horizontal glyph assembly. + XCTAssertTrue([stack.over isKindOfClass:[MTHorizontalGlyphAssemblyDisplay class]]); + + // Over must cover the base. + XCTAssertGreaterThanOrEqual(stack.over.width + 0.01, stack.base.width); +} + +- (void)testOverbrace +{ + MTMathListDisplay* display = [self displayForLaTeX:@"\\overbrace{ABC}"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.subDisplays.count, 1u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + MTStackDisplay* stack = (MTStackDisplay*)sub0; + + XCTAssertNotNil(stack.over); + XCTAssertNil(stack.under); + + // For a narrow base, the smallest preset h_variant of the brace cap covers the width -> plain MTGlyphDisplay. + XCTAssertTrue([stack.over isKindOfClass:[MTGlyphDisplay class]]); + + // Selected variant must be wide enough to cover the base. + XCTAssertGreaterThanOrEqual(stack.over.width + 0.01, stack.base.width); + + CGFloat gapAbove = self.font.mathTable.stretchStackGapAboveMin; + CGFloat expectedAscent = stack.base.ascent + gapAbove + stack.over.ascent + stack.over.descent; + XCTAssertEqualWithAccuracy(display.ascent, expectedAscent, 0.01); + XCTAssertEqualWithAccuracy(display.descent, stack.base.descent, 0.01); +} + +- (void)testUnderbrace +{ + MTMathListDisplay* display = [self displayForLaTeX:@"\\underbrace{ABC}"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.subDisplays.count, 1u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + MTStackDisplay* stack = (MTStackDisplay*)sub0; + + XCTAssertNil(stack.over); + XCTAssertNotNil(stack.under); + + // Brace uses single-stretchy variant path -> plain MTGlyphDisplay. + XCTAssertTrue([stack.under isKindOfClass:[MTGlyphDisplay class]]); + + // Selected variant must be wide enough to cover the base. + XCTAssertGreaterThanOrEqual(stack.under.width + 0.01, stack.base.width); + + CGFloat gapBelow = self.font.mathTable.stretchStackGapBelowMin; + CGFloat expectedDescent = stack.base.descent + gapBelow + stack.under.ascent + stack.under.descent; + XCTAssertEqualWithAccuracy(display.descent, expectedDescent, 0.01); + XCTAssertEqualWithAccuracy(display.ascent, stack.base.ascent, 0.01); +} + +- (void)testUnderrightarrow +{ + MTMathListDisplay* display = [self displayForLaTeX:@"\\underrightarrow{x}"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.subDisplays.count, 1u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + MTStackDisplay* stack = (MTStackDisplay*)sub0; + + XCTAssertNil(stack.over); + XCTAssertNotNil(stack.under); + + XCTAssertEqualWithAccuracy(display.ascent, stack.base.ascent, 0.01); + + CGFloat gapBelow = self.font.mathTable.stretchStackGapBelowMin; + CGFloat expectedDescent = stack.base.descent + gapBelow + stack.under.ascent + stack.under.descent; + XCTAssertEqualWithAccuracy(display.descent, expectedDescent, 0.01); +} + +- (void)testStackScripts +{ + // \overrightarrow{x}^2: the superscript should attach to the whole stack, not just the base. + MTMathListDisplay* display = [self displayForLaTeX:@"\\overrightarrow{x}^2"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.subDisplays.count, 2u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + + MTDisplay* sub1 = display.subDisplays[1]; + XCTAssertTrue([sub1 isKindOfClass:[MTMathListDisplay class]]); + MTMathListDisplay* superscript = (MTMathListDisplay*)sub1; + XCTAssertEqual(superscript.type, kMTLinePositionSuperscript); + + // Superscript must sit above the baseline and to the right of the stack. + XCTAssertGreaterThan(superscript.position.y, 0.0); + XCTAssertGreaterThan(superscript.position.x, 0.0); + + // Superscript must be positioned at or above the stack's full ascent + // (it should not overlap with the over-row). + MTStackDisplay* stack = (MTStackDisplay*)sub0; + XCTAssertGreaterThanOrEqual(superscript.position.y + superscript.descent + 0.01, + stack.base.ascent); +} + +- (void)testStackNestedStacks +{ + // \overrightarrow{\overleftarrow{x}}: outer base is itself an MTStackDisplay. + MTMathListDisplay* display = [self displayForLaTeX:@"\\overrightarrow{\\overleftarrow{x}}"]; + XCTAssertNotNil(display); + XCTAssertEqual(display.subDisplays.count, 1u); + + MTDisplay* sub0 = display.subDisplays[0]; + XCTAssertTrue([sub0 isKindOfClass:[MTStackDisplay class]]); + MTStackDisplay* outerStack = (MTStackDisplay*)sub0; + + XCTAssertNotNil(outerStack.base); + XCTAssertEqual(outerStack.base.subDisplays.count, 1u); + MTDisplay* innerSub0 = outerStack.base.subDisplays[0]; + XCTAssertTrue([innerSub0 isKindOfClass:[MTStackDisplay class]]); + + // Outer ascent must be greater than the base's ascent (it adds the outer over-row). + XCTAssertGreaterThan(display.ascent, outerStack.base.ascent); +} + +- (void)testWideStackBaseFallsBackToHorizontalAssembly +{ + // Both braces and arrows take the same path: try preset h_variants of the cap first, + // and fall back to OpenType HorizontalGlyphAssembly when no preset variant is wide enough. + // For a sufficiently wide base, both must produce MTHorizontalGlyphAssemblyDisplay. + MTMathListDisplay* braceDisplay = [self displayForLaTeX:@"\\overbrace{ABCDEF}"]; + XCTAssertEqual(braceDisplay.subDisplays.count, 1u); + MTStackDisplay* braceStack = (MTStackDisplay*)braceDisplay.subDisplays[0]; + XCTAssertTrue([braceStack isKindOfClass:[MTStackDisplay class]]); + XCTAssertTrue([braceStack.over isKindOfClass:[MTHorizontalGlyphAssemblyDisplay class]]); + XCTAssertGreaterThanOrEqual(braceStack.over.width + 0.01, braceStack.base.width); + + MTMathListDisplay* arrowDisplay = [self displayForLaTeX:@"\\overrightarrow{ABCDEF}"]; + XCTAssertEqual(arrowDisplay.subDisplays.count, 1u); + MTStackDisplay* arrowStack = (MTStackDisplay*)arrowDisplay.subDisplays[0]; + XCTAssertTrue([arrowStack isKindOfClass:[MTStackDisplay class]]); + XCTAssertTrue([arrowStack.over isKindOfClass:[MTHorizontalGlyphAssemblyDisplay class]]); + XCTAssertGreaterThanOrEqual(arrowStack.over.width + 0.01, arrowStack.base.width); +} + @end