feat(math): caret-driven suppression, the draw pass, and the slice acceptance
The two halves that touch live rendering, landed together because the acceptance criteria that make either honest need both. Suppression (Q#MS3/MS4/MS5/MS11). Detection runs in the per-line chunk builder — the chunk-build path, never the edit path — and substitutes each suppressed span's source bytes with ONE spacer chunk BEFORE tab expansion, so a literal tab inside a span vanishes with it while tabs outside keep their SourceTab provenance. The gate reads the EFFECTIVE caret (own_cursor, which optimistic edits predict forward — F4's no-flap requirement holds by construction) plus both own-selection endpoints. Three motion paths can flip a gate without a content change, and each now re-runs the per-line chunk compare, gated on a one-scan "does the visible slice hold a $" check: the CursorByte arm, finish_optimistic_edit (the text re-chunks under the OLD caret there; without the hook a typed char rendered one keystroke stale), and the Decorations arm — whose "no decoration change needs a reshape" premise acquires exactly one exception, the Selection endpoints Q#MS11 made suppression inputs. The line-reuse predicate (acceptance 11, the #120 edge). Per-line math state is cached in lockstep with line_chunk_cache: every detected span with the gate bit it was built under. The scroll-reuse path refuses a retained line whose cached bits disagree with the CURRENT caret/selection — content is unchanged on every reuse path, so the cached span set is authoritative and the gate bits are the only variable. The acceptance test drives the stale-gate case through rebuild_lines_reusing_scroll directly and fails if the gate is removed from the predicate. The hit map (B1'). hit_test_source_byte rebuilds its runs from a whole-slice chunk walk, so it now reads the substitutions BACK from the per-line caches — never re-planned under a possibly-newer caret — keeping the map and the shaped glyphs one source of truth. The draw pass (Q#MS6/MS7). Every MathItem::Glyph draws from its own mini-buffer with Attrs pinned to the bundled math family (F8b), placed at layout's exact x and the shaped line's REAL baseline; the mini-buffer itself is positioned by the line_y cosmic-text actually produced for it, so no font-metric rederivation can drift. Fraction rules ride the bg quad batch after the decoration washes and under the glyphs. Wash geometry gains Q#MS11's intersection rule: a wash touching a suppressed span widens to the box's whole reserved rectangle (a match strictly inside the span produced a zero-width interval before), while the round-3 exclusive-end fix keeps a non-intersecting wash off the box. Acceptance (framing §5). Criteria 5-11 and 14-16 run on real pixels through render_to_view: drawn ink where a literal-spacer control renders none, with the before-region pixel-identical; the fraction rule as a full-width run with operand ink both sides; caret-inside rendering EXACTLY as math-disabled (driven through the real CursorByte arm, which owns the refresh — a direct helper call would not have pinned the wiring); every failure mode (unbalanced, unknown command, $$, uncoverable glyph) pixel-equal to disabled; box clicks snapping to the span start with the trailing edge landing after the span; the scroll-reuse stale-gate bite; reflow confined to the affected line with the after-text shifted by exactly the quantized projection difference; selection gating and the whole-rectangle wash; and the licence provenance pair. Criterion 17 is discharged differentially: cargo tree -e features output for ttf-parser is byte-identical with and without this crate's dependency line. Also folded in, per the round-3 close-out: the F6 documenting test ($a$$b$ is eaten by the $$-opaque rule; one separating character restores both spans), the depth-search bound raised 6 -> 8 so a metric shift cannot make the "floor is dead code" expect fire with a misleading message, the MathBox { end, .. } pattern nit, and the active-work.md lane entry. Named v0 approximations, deliberate: the peer-caret half of acceptance 14 is pinned at the mapping level (unit tests), not pixels; a soft-wrapped spacer draws its box whole at the first run's origin (the one-rectangle model); the fit budget reads the bundled code face even under a custom set_font family — the draw anchors to the real shaped baseline either way, so only the fit margin is approximate. Clippy is CLEAN across the workspace at -D warnings for the first time on this branch: the draw pass consumed every formerly-dead item, and the three lints it could not fix (a test-only accessor, one doc string, one manual midpoint) are fixed here. Gates: cargo fmt --check; cargo clippy --workspace --all-targets -- -D warnings; 1,815 default + 1,992 CRDT library tests; M4 121 (basedpyright skipped); 199 pmacs-gpu tests under PMACS_REQUIRE_GPU=1; workspace sweep 3,131 across 88 suites (isolated XDG_CONFIG_HOME); git diff --check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cbf7782726
commit
14c1c01043
|
|
@ -54,6 +54,41 @@ git status --short --branch
|
|||
The `git log` command must expose `0dd16a5` or a newer intentional main.
|
||||
If it does not, stop and repair the remote/fetch configuration.
|
||||
|
||||
## Inline-math slice lane — IN FLIGHT
|
||||
|
||||
- Portable branch: `githubsucks/inline-math-slice`; worktree
|
||||
`../pmacs-math-slice`.
|
||||
- Framing: `docs/inline-math-slice-framing.md` rev 3, approved after two
|
||||
review rounds; parent arc framing merged as #154.
|
||||
- State: parser, font bundle (GUST licence), MATH-table layout with the
|
||||
measured height budget, currency-guarded detection, and the
|
||||
`ChunkSource::MathBox` spacer substrate are implemented and
|
||||
round-3-reviewed (review fixes at `cbf7782`: the exclusive-`end`
|
||||
mapping bug its own test had pinned, script-marker whitespace, fallible
|
||||
layout via `UncoverableGlyph`, real fraction gap-min constants —
|
||||
flagship scale 0.867, fallback depth 5).
|
||||
- Caret-driven suppression (the Q#MS5 gate over the effective caret and
|
||||
Q#MS11 selection endpoints, chunk substitution before tab expansion,
|
||||
the line-reuse predicate's third input, and the CursorByte /
|
||||
optimistic-edit / Decorations refresh triggers), the draw pass
|
||||
(per-glyph mini-buffers positioned by each shaped line's real
|
||||
baseline, fraction-rule quads over the washes, the F8b family pin),
|
||||
the Q#MS11 whole-rectangle wash widening, and the pixel acceptance
|
||||
battery (criteria 5–11, 14–16; 17 discharged by the differential
|
||||
`cargo tree -e features` check — byte-identical with and without the
|
||||
dependency line) are implemented on the branch tip.
|
||||
- Clippy is CLEAN on the whole workspace at `-D warnings` — the draw
|
||||
pass consumed every formerly-dead item.
|
||||
- Verification at the tip: 199 `pmacs-gpu` tests under
|
||||
`PMACS_REQUIRE_GPU=1`; 1,815 default + 1,992 CRDT library tests; M4
|
||||
121; full workspace sweep green (isolated `XDG_CONFIG_HOME`).
|
||||
- Remaining before PR: nothing known; awaiting the user's review pass.
|
||||
Named v0 approximations: the peer-caret half of acceptance 14 is
|
||||
pinned at the mapping level (unit tests), not pixels; a soft-wrapped
|
||||
spacer draws its box whole at the first run's origin; the fit budget
|
||||
reads the bundled code face even under a custom `set_font` family
|
||||
(the draw anchors to the real shaped baseline either way).
|
||||
|
||||
## Folding lane (Arc 6) — Stages 1 and 2 MERGED; Stage 3 (GPU) is next
|
||||
|
||||
Both shipped stages are on `main`; nothing in this arc is in flight. Stage 3
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -328,6 +328,9 @@ impl<'a> MathLayout<'a> {
|
|||
Ok(Self { face, constants })
|
||||
}
|
||||
|
||||
/// Test-only introspection: the production draw path consumes the
|
||||
/// constants through `layout`, never raw.
|
||||
#[cfg(test)]
|
||||
#[must_use]
|
||||
pub fn constants(&self) -> MathConstants {
|
||||
self.constants
|
||||
|
|
@ -516,6 +519,19 @@ mod tests {
|
|||
|
||||
use crate::math_parse::parse;
|
||||
|
||||
/// Framing acceptance 16 (provenance half): the GUST licence ships
|
||||
/// beside the font, names itself, and is not the OFL that covers
|
||||
/// `JetBrains` Mono.
|
||||
#[test]
|
||||
fn bundled_licences_are_distinct_and_name_their_terms() {
|
||||
let gust = include_str!("../fonts/GUST-FONT-LICENSE.txt");
|
||||
let ofl = include_str!("../fonts/OFL.txt");
|
||||
assert!(gust.contains("GUST Font License"));
|
||||
assert!(gust.contains("LaTeX Project Public License"));
|
||||
assert!(!ofl.contains("GUST"));
|
||||
assert_ne!(gust, ofl);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tex_symbol_greek_forms_are_italicised_too() {
|
||||
// F5's trap: correcting the seed map alone leaves these upright,
|
||||
|
|
@ -744,7 +760,12 @@ mod tests {
|
|||
if scale < MIN_FIT_SCALE {
|
||||
break Some((depth, src.clone()));
|
||||
}
|
||||
if depth >= 6 {
|
||||
// Headroom above the real boundary (5 with the round-3 MATH
|
||||
// gaps): if a metric shift pushed the boundary past this bound,
|
||||
// the expect below would fire with a message reading "the floor
|
||||
// is dead code" when the truth is "the boundary moved past the
|
||||
// search". Keep the bound comfortably above the boundary.
|
||||
if depth >= 8 {
|
||||
break None;
|
||||
}
|
||||
src = format!(r"\frac{{{src}}}{{c}}");
|
||||
|
|
|
|||
|
|
@ -454,6 +454,22 @@ mod tests {
|
|||
assert_eq!(mixed.len(), 1, "{mixed:?}");
|
||||
}
|
||||
|
||||
/// Round-3 F6 — a DOCUMENTED casualty of the `$$`-opaque rule, not a
|
||||
/// guard failure: in `$a$$b$` the first span's legitimate closer is
|
||||
/// immediately followed by the second span's opener, the lookahead
|
||||
/// reads that pair as display-math `$$`, and the pending opener is
|
||||
/// abandoned. Adjacent inline spans therefore need a separating
|
||||
/// character. Pandoc finds two spans here; this scanner deliberately
|
||||
/// finds none, because distinguishing `$a$$b$` from `$$x$$` requires
|
||||
/// closer-context the framing's opaque-`$$` rule gave away.
|
||||
#[test]
|
||||
fn adjacent_inline_spans_are_eaten_by_the_display_guard() {
|
||||
assert!(detect_math_spans("$a$$b$").is_empty());
|
||||
assert!(detect_math_spans("$x^2$$y^2$").is_empty());
|
||||
// One separating character restores both spans.
|
||||
assert_eq!(detect_math_spans("$a$ $b$").len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whitespace_before_a_script_marker_still_merges_the_scripts() {
|
||||
// F2: without skipping whitespace in `parse_scripts`, `x^2 _i` built
|
||||
|
|
|
|||
Loading…
Reference in New Issue