Skip to content

Commit e28cd72

Browse files
[204_30] Fix mismatched bracket sizes in multi-line formulas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6784da7 commit e28cd72

3 files changed

Lines changed: 162 additions & 4 deletions

File tree

devel/204_30.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# 204_30 Mismatched bracket sizes in multi-line formulas
2+
3+
## How to test
4+
- Open Mogan editor
5+
- Insert a display formula, use the align environment
6+
- On the first line, enter content with `\left[` (e.g. `f(x) \leq \left[ \int dx`)
7+
- On the second line, enter content with `\right]` (e.g. `+x \right]`)
8+
- Verify that the bracket sizes on both lines are consistent
9+
10+
## 2026/03/06 Fix mismatched bracket sizes in multi-line formulas
11+
12+
### What
13+
Fixed an issue where brackets (e.g. `[` and `]`) spanning multiple lines in multi-line math formulas (such as align environments) had inconsistent sizes.
14+
15+
### Why
16+
Multi-line math environments are internally implemented as tables, with each row typeset as an independent cell. Each cell's bracket size was calculated based only on that row's content, making it impossible to coordinate bracket sizes across rows. For example, the first row with an integral symbol made `[` large, but the shorter content on the second row kept `]` small.
17+
18+
### How
19+
Introduced a bracket-pending mechanism using environment variables, storing bracket height information per column (`math-bracket-pending-{col}`) and propagating it between table rows:
20+
21+
- When a row ends with an incomplete bracket pair (e.g. `<left-[>...<right-.>`), save the vertical extents of that row's content
22+
- When the next row detects an incomplete bracket pair at the start (e.g. `<left-.>...<right-]>`), read the saved extents and merge them
23+
- Supports brackets spanning three or more rows, with middle rows both reading and propagating pending state
24+
25+
Modified files:
26+
- `src/Typeset/Concat/concat_post.cpp`: added bracket_match_state, pending state management functions, modified handle_matching and handle_brackets
27+
- `src/Typeset/Concat/concater.hpp`: updated handle_matching function signature

src/Typeset/Concat/concat_post.cpp

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,84 @@ concater_rep::clean_and_correct () {
294294
* Resize brackets
295295
******************************************************************************/
296296

297+
struct bracket_match_state {
298+
bool left_seen;
299+
bool right_seen;
300+
bool start_empty;
301+
bool end_empty;
302+
bracket_match_state ()
303+
: left_seen (false), right_seen (false), start_empty (false),
304+
end_empty (false) {}
305+
};
306+
307+
static bool
308+
is_empty_left_delimiter (const string& s) {
309+
return starts (s, "<left-.");
310+
}
311+
312+
static bool
313+
is_empty_right_delimiter (const string& s) {
314+
return starts (s, "<right-.");
315+
}
316+
317+
static bracket_match_state
318+
classify_bracket_match (array<line_item>& a, int start, int end) {
319+
bracket_match_state st;
320+
for (int i= start; i <= end; i++) {
321+
int tp= a[i]->type;
322+
if (tp == LEFT_BRACKET_ITEM) {
323+
st.left_seen= true;
324+
if (i == start)
325+
st.start_empty= is_empty_left_delimiter (a[i]->b->get_leaf_string ());
326+
}
327+
else if (tp == RIGHT_BRACKET_ITEM) {
328+
st.right_seen= true;
329+
if (i == end)
330+
st.end_empty= is_empty_right_delimiter (a[i]->b->get_leaf_string ());
331+
}
332+
}
333+
return st;
334+
}
335+
336+
static string
337+
bracket_pending_key (edit_env env) {
338+
tree row= env->read (CELL_ROW_NR);
339+
tree col= env->read (CELL_COL_NR);
340+
if (!is_atomic (row) || !is_int (row)) return "";
341+
if (!is_atomic (col) || !is_int (col)) return "";
342+
343+
string key= "math-bracket-pending";
344+
key << "-" << col->label;
345+
return key;
346+
}
347+
348+
static bool
349+
get_bracket_pending (edit_env env, const string& key, SI& y1, SI& y2) {
350+
if (N (key) == 0) return false;
351+
tree pending= env->read (key);
352+
if (!is_tuple (pending) || N (pending) != 2) return false;
353+
if (!is_int (pending[0]) || !is_int (pending[1])) return false;
354+
y1= as_int (pending[0]);
355+
y2= as_int (pending[1]);
356+
return true;
357+
}
358+
359+
static void
360+
set_bracket_pending (edit_env env, const string& key, SI y1, SI y2) {
361+
if (N (key) == 0) return;
362+
env->write (key, tree (TUPLE, as_string ((int) y1), as_string ((int) y2)));
363+
}
364+
365+
static void
366+
clear_bracket_pending (edit_env env, const string& key) {
367+
if (N (key) == 0) return;
368+
env->write (key, tree (TUPLE));
369+
}
370+
297371
void
298-
concater_rep::handle_matching (int start, int end) {
372+
concater_rep::handle_matching (int start, int end, bool use_pending,
373+
SI pending_y1, SI pending_y2, SI& out_y1,
374+
SI& out_y2) {
299375
// cout << "matching " << start << " -- " << end << "\n";
300376
// cout << a << "\n\n";
301377
int i;
@@ -320,6 +396,12 @@ concater_rep::handle_matching (int start, int end) {
320396
y1= min (a[start]->b->y1, a[end]->b->y2);
321397
y2= max (a[start]->b->y1, a[end]->b->y2);
322398
}
399+
if (use_pending) {
400+
y1= min (y1, pending_y1);
401+
y2= max (y2, pending_y2);
402+
}
403+
out_y1= y1;
404+
out_y2= y2;
323405

324406
for (i= start; i <= end; i++) {
325407
int tp= a[i]->type;
@@ -407,25 +489,73 @@ concater_rep::handle_matching (int start, int end) {
407489

408490
void
409491
concater_rep::handle_brackets () {
492+
string pending_key= bracket_pending_key (env);
493+
SI pending_y1 = 0;
494+
SI pending_y2 = 0;
495+
bool has_pending=
496+
get_bracket_pending (env, pending_key, pending_y1, pending_y2);
497+
bool pending_was_present= has_pending;
498+
bool pending_touched = false;
499+
410500
int first= -1, start= 0, i= 0;
411501
while (i < N (a)) {
412502
if (a[i]->type == LEFT_BRACKET_ITEM) {
413503
if (first == -1) first= i;
414504
start= i;
415505
}
416506
if (a[i]->type == RIGHT_BRACKET_ITEM) {
507+
bracket_match_state st= classify_bracket_match (a, start, i);
508+
bool use_pending=
509+
has_pending && (st.start_empty || (!st.left_seen && st.right_seen));
510+
SI match_y1= 0, match_y2= 0;
417511
handle_scripts (succ (start), prec (i));
418-
handle_matching (start, i);
512+
handle_matching (start, i, use_pending, pending_y1, pending_y2, match_y1,
513+
match_y2);
514+
if (st.end_empty || (st.left_seen && !st.right_seen)) {
515+
pending_y1 = match_y1;
516+
pending_y2 = match_y2;
517+
has_pending = true;
518+
pending_touched= true;
519+
}
520+
else if ((st.start_empty && !st.end_empty) ||
521+
(!st.left_seen && st.right_seen)) {
522+
has_pending = false;
523+
pending_touched= true;
524+
}
419525
if (first != -1) i= first - 1;
420526
start= 0;
421527
first= -1;
422528
}
423529
i++;
424530
}
425531
if (N (a) > 0) {
532+
bracket_match_state st= classify_bracket_match (a, 0, N (a) - 1);
533+
bool use_pending=
534+
has_pending && (st.start_empty || (!st.left_seen && st.right_seen));
535+
SI match_y1= 0, match_y2= 0;
426536
handle_scripts (0, N (a) - 1);
427-
handle_matching (0, N (a) - 1);
537+
handle_matching (0, N (a) - 1, use_pending, pending_y1, pending_y2,
538+
match_y1, match_y2);
539+
if (st.end_empty || (st.left_seen && !st.right_seen)) {
540+
pending_y1 = match_y1;
541+
pending_y2 = match_y2;
542+
has_pending = true;
543+
pending_touched= true;
544+
}
545+
else if ((st.start_empty && !st.end_empty) ||
546+
(!st.left_seen && st.right_seen)) {
547+
has_pending = false;
548+
pending_touched= true;
549+
}
428550
}
551+
552+
if (has_pending && pending_was_present && !pending_touched)
553+
has_pending= false;
554+
555+
if (has_pending)
556+
set_bracket_pending (env, pending_key, pending_y1, pending_y2);
557+
else if (pending_was_present || pending_touched)
558+
clear_bracket_pending (env, pending_key);
429559
}
430560

431561
/******************************************************************************

src/Typeset/Concat/concater.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ class concater_rep {
189189
void glue (box b, int ref, int arg1, int arg2);
190190
void clean_and_correct ();
191191
void handle_scripts (int start, int end);
192-
void handle_matching (int start, int end);
192+
void handle_matching (int start, int end, bool use_pending, SI pending_y1,
193+
SI pending_y2, SI& out_y1, SI& out_y2);
193194
void handle_brackets ();
194195
void kill_spaces ();
195196

0 commit comments

Comments
 (0)