[LyX/master] add support for aligned-overset
Udi Fogiel
ufogiel at lyx.org
Tue Aug 4 16:43:16 UTC 2026
commit acb9110d617d0c3b61cab2ed45722732f1195a3c
Author: Udi Fogiel <ufogiel at lyx.org>
Date: Tue Aug 4 19:22:48 2026 +0300
add support for aligned-overset
see #13063
---
lib/chkconfig.ltx | 1 +
lib/doc/LaTeXConfig.lyx | 98 +++++++
lib/doc/Math.lyx | 113 ++++++++
lib/lyx2lyx/lyx_2_6.py | 293 ++++++++++++++++++++-
src/BufferParams.cpp | 3 +
src/LaTeXFeatures.cpp | 5 +
src/mathed/InsetMathGrid.cpp | 96 ++++++-
src/mathed/InsetMathGrid.h | 5 +
src/mathed/InsetMathHull.cpp | 15 ++
src/mathed/InsetMathHull.h | 2 +
src/mathed/InsetMathSplit.cpp | 10 +
src/mathed/InsetMathSplit.h | 2 +
src/mathed/MathParser.cpp | 12 +-
src/tex2lyx/Preamble.cpp | 3 +-
src/tex2lyx/test/CJK.lyx.lyx | 3 +-
src/tex2lyx/test/CJKutf8.lyx.lyx | 3 +-
src/tex2lyx/test/DummyDocument.lyx.lyx | 3 +-
src/tex2lyx/test/Dummy~Document.lyx.lyx | 3 +-
src/tex2lyx/test/XeTeX-polyglossia.lyx.lyx | 3 +-
src/tex2lyx/test/algo2e.lyx.lyx | 3 +-
src/tex2lyx/test/beamer.lyx.lyx | 3 +-
.../test/box-color-size-space-align.lyx.lyx | 3 +-
src/tex2lyx/test/listpreamble.lyx.lyx | 3 +-
src/tex2lyx/test/tabular-x-test.lyx.lyx | 3 +-
src/tex2lyx/test/test-insets-basic.lyx.lyx | 3 +-
src/tex2lyx/test/test-insets.lyx.lyx | 3 +-
src/tex2lyx/test/test-memoir.lyx.lyx | 3 +-
src/tex2lyx/test/test-minted.lyx.lyx | 3 +-
src/tex2lyx/test/test-modules.lyx.lyx | 3 +-
src/tex2lyx/test/test-refstyle-theorems.lyx.lyx | 3 +-
src/tex2lyx/test/test-scr.lyx.lyx | 3 +-
src/tex2lyx/test/test-structure.lyx.lyx | 3 +-
src/tex2lyx/test/test.lyx.lyx | 3 +-
src/tex2lyx/test/verbatim.lyx.lyx | 3 +-
src/version.h | 4 +-
35 files changed, 688 insertions(+), 34 deletions(-)
diff --git a/lib/chkconfig.ltx b/lib/chkconfig.ltx
index 855296f8ae..6f08bb76d8 100644
--- a/lib/chkconfig.ltx
+++ b/lib/chkconfig.ltx
@@ -325,6 +325,7 @@
\TestPackage{afterpage}
\TestPackage{algorithm}
\TestPackage{algorithm2e}
+\TestPackage{aligned-overset}
\TestPackage{amsmath}
\TestPackage{amstext}
\TestPackage{apacite}
diff --git a/lib/doc/LaTeXConfig.lyx b/lib/doc/LaTeXConfig.lyx
index 57bf45205b..4d195823f1 100644
--- a/lib/doc/LaTeXConfig.lyx
+++ b/lib/doc/LaTeXConfig.lyx
@@ -8776,6 +8776,104 @@ Modules
.
\end_layout
+\begin_layout Subsection
+aligned-overset
+\end_layout
+
+\begin_layout Description
+Found:
+
+\begin_inset Info
+type "package"
+arg "aligned-overset"
+\end_inset
+
+
+\end_layout
+
+\begin_layout Description
+CTAN:
+
+\family typewriter
+macros/latex/contrib/aligned-overset/
+\end_layout
+
+\begin_layout Description
+Notes:
+ The package
+\family sans
+aligned-overset
+\family default
+ lets the base of
+\family sans
+
+\backslash
+overset
+\family default
+ or
+\family sans
+
+\backslash
+underset
+\family default
+ serve as the alignment point in an
+\family sans
+amsmath
+\family default
+ aligned-type environment,
+ instead of the whole overset/underset atom.
+ LyX loads it automatically when
+\family sans
+
+\backslash
+overset
+\family default
+ or
+\family sans
+
+\backslash
+underset
+\family default
+ is entered as the first item of a non-first cell in an
+\family sans
+align
+\family default
+,
+
+\family sans
+alignat
+\family default
+,
+
+\family sans
+xalignat
+\family default
+,
+
+\family sans
+xxalignat
+\family default
+,
+ or
+\family sans
+flalign
+\family default
+ environment;
+ the alignment tab that would otherwise precede the inset is then written inside the
+\family sans
+
+\backslash
+overset
+\family default
+/
+\family sans
+
+\backslash
+underset
+\family default
+ call itself.
+\end_layout
+
\begin_layout Subsection
breakurl
\end_layout
diff --git a/lib/doc/Math.lyx b/lib/doc/Math.lyx
index db6ca39eeb..025ab9c003 100644
--- a/lib/doc/Math.lyx
+++ b/lib/doc/Math.lyx
@@ -149,6 +149,9 @@
}
\DeclareUnicodeCharacter{212B}{\AA}
\fi
+% Added by lyx2lyx
+\usepackage{aligned-overset}
+\let\lyxoverset\overset
\end_preamble
\options bibliography=totoc,index=totoc,BCOR7.5mm,titlepage,captions=tableheading
\use_default_options false
@@ -261,6 +264,7 @@ logicalmkup
\docbook_table_output 0
\docbook_mathml_prefix 1
\docbook_mathml_version 0
+\author 673085648 "ufogiel"
\author 2090250301 "fcan"
\end_header
@@ -32373,6 +32377,115 @@ Indented
\end_inset
pt space was inserted at the beginning of the first column.
+\change_inserted 673085648 1785840161
+
+\end_layout
+
+\begin_layout Subsection
+
+\change_inserted 673085648 1785840195
+Alignment Point Inside overset/underset
+\end_layout
+
+\begin_layout Standard
+
+\change_inserted 673085648 1785841460
+In an aligned environments,
+ if a cell starts with
+\series bold
+
+\backslash
+overset
+\series default
+/
+\series bold
+
+\backslash
+underset
+\series default
+,
+ the alignment point sits at the edge of the whole overset/underset atom —
+ which is as wide as whichever of the base or the accent is wider.
+ If the accent (e.g.
+\begin_inset space \space{}
+\end_inset
+
+
+\series bold
+
+\backslash
+text{
+\series default
+Def
+\series bold
+}
+\series default
+) is wider than the base (e.g.
+\begin_inset space \space{}
+\end_inset
+
+=),
+ the visual alignment point ends up offset from where the reader expects it (the base),
+ which looks wrong when several rows are meant to line up on that base character.
+\end_layout
+
+\begin_layout Standard
+
+\change_inserted 673085648 1785841464
+As of \SpecialChar LyX
+2.6 if
+\series bold
+
+\backslash
+overset
+\series default
+/
+\series bold
+
+\backslash
+underset
+\series default
+ is used as the first item in a cell not in the first column (i.e.
+ right after a &) and
+\family sans
+Document\SpecialChar menuseparator
+Settings\SpecialChar menuseparator
+Math Options\SpecialChar menuseparator
+aligned-overset
+\family default
+ is loaded then the format of the aligned-overset package will be used,
+ and e.g.
+\begin_inset space \space{}
+\end_inset
+
+
+\series bold
+&
+\backslash
+overset{
+\backslash
+text{Def}}{=}
+\series default
+ will be exported as
+\series bold
+
+\backslash
+overset{
+\backslash
+text{Def}}&{=}
+\series default
+.
+\begin_inset Formula
+\begin{align*}
+f(x)\lyxoverset{\text{Def}} & {=}x+x\\
+ & =2x
+\end{align*}
+
+\end_inset
+
+
+\change_unchanged
+
\end_layout
\begin_layout Subsection
diff --git a/lib/lyx2lyx/lyx_2_6.py b/lib/lyx2lyx/lyx_2_6.py
index fc9184c15f..db3af39caf 100644
--- a/lib/lyx2lyx/lyx_2_6.py
+++ b/lib/lyx2lyx/lyx_2_6.py
@@ -43,7 +43,6 @@ from lyx2lyx_tools import (
# Uncomment only what you need to import, please (parser_tools):
# check_token,
# count_pars_in_inset,
-# del_complete_lines,
# del_value,
# find_complete_lines,
# find_end_of,
@@ -52,6 +51,7 @@ from lyx2lyx_tools import (
# set_bool_value,
# is_in_inset
from parser_tools import (
+ del_complete_lines,
del_token,
find_end_of_inset,
find_end_of_layout,
@@ -1167,6 +1167,293 @@ def revert_lp_polyglossia(document):
document.header[i] = "\\language_package auto"
+def convert_use_alignedoverset(document):
+ """insert use_package aligned-overset"""
+ i = find_token(document.header, "\\use_package")
+ if i == -1:
+ document.warning("Malformed LyX document: Can't find \\use_package.")
+ return
+ pkg = "aligned-overset"
+ # Try candidates from most specific (with \let aliases) to least (bare
+ # \usepackage), so a round-tripped block is always removed in full and
+ # no \let alias line is ever left orphaned behind a partial match.
+ candidates = [
+ alignedoverset_preamble_lines(True, True),
+ alignedoverset_preamble_lines(True, False),
+ alignedoverset_preamble_lines(False, True),
+ alignedoverset_preamble_lines(False, False),
+ ]
+ found = False
+ for cand in candidates:
+ if del_complete_lines(
+ document.preamble, ["% Added by lyx2lyx"] + cand
+ ) or del_complete_lines(document.preamble, cand):
+ found = True
+ break
+ document.header.insert(i + 1, "\\use_package " + pkg + " " + ("2" if found else "0"))
+
+
+alignedoverset_align_envs = {
+ "align", "alignat", "xalignat", "xxalignat", "flalign",
+ "aligned", "alignedat", "split",
+}
+
+
+def alignedoverset_read_command(s, pos):
+ """pos points right after a backslash: return (name, endpos)"""
+ m = re.match(r"[a-zA-Z]+", s[pos:])
+ if m:
+ return m.group(0), pos + len(m.group(0))
+ if pos < len(s):
+ return s[pos], pos + 1
+ return "", pos
+
+
+def alignedoverset_matching_brace(s, pos):
+ """s[pos] == '{': return index of the matching '}', or -1"""
+ depth = 0
+ j = pos
+ n = len(s)
+ while j < n:
+ c = s[j]
+ if c == "\\":
+ _, j = alignedoverset_read_command(s, j + 1)
+ continue
+ if c == "{":
+ depth += 1
+ elif c == "}":
+ depth -= 1
+ if depth == 0:
+ return j
+ j += 1
+ return -1
+
+
+def rewrite_aligned_overset_text(text):
+ """Rewrite &\\overset{acc}{base} to \\lyxoverset{acc}&{base} (ditto
+ underset), for older LyX. Returns (newtext, used_overset,
+ used_underset, changed)."""
+ n = len(text)
+ out = []
+ i = 0
+ # One entry per currently-open \begin{...}, recording whether that
+ # specific environment is itself align-family. This lets a nested
+ # align-family environment (e.g. "aligned" inside "align") get its
+ # own & tabs rewritten independently of what it's nested in.
+ env_stack = []
+ brace_depth = 0
+ used_overset = False
+ used_underset = False
+ changed = False
+
+ while i < n:
+ c = text[i]
+ if c == "\\":
+ name, nexti = alignedoverset_read_command(text, i + 1)
+ if name in ("begin", "end"):
+ m = re.match(r"\{([a-zA-Z]+)(\*?)\}", text[nexti:])
+ if m:
+ full = text[i:nexti] + text[nexti : nexti + m.end()]
+ if name == "begin":
+ env_stack.append(m.group(1) in alignedoverset_align_envs)
+ elif env_stack:
+ env_stack.pop()
+ out.append(full)
+ i = nexti + m.end()
+ continue
+ out.append(text[i:nexti])
+ i = nexti
+ continue
+ if c == "{":
+ brace_depth += 1
+ out.append(c)
+ i += 1
+ continue
+ if c == "}":
+ brace_depth -= 1
+ out.append(c)
+ i += 1
+ continue
+ if c == "&" and brace_depth == 0 and env_stack and env_stack[-1]:
+ j = i + 1
+ while j < n and text[j] in " \t\n":
+ j += 1
+ m = re.match(r"\\(overset|underset)\{", text[j:])
+ if m:
+ cmd = m.group(1)
+ b1 = j + m.end() - 1
+ e1 = alignedoverset_matching_brace(text, b1)
+ if e1 != -1 and e1 + 1 < n and text[e1 + 1] == "{":
+ b2 = e1 + 1
+ e2 = alignedoverset_matching_brace(text, b2)
+ if e2 != -1:
+ acc = text[b1 + 1 : e1]
+ base = text[b2 + 1 : e2]
+ newcmd = "lyxoverset" if cmd == "overset" else "lyxunderset"
+ if cmd == "overset":
+ used_overset = True
+ else:
+ used_underset = True
+ out.append("\\" + newcmd + "{" + acc + "}&{" + base + "}")
+ i = e2 + 1
+ changed = True
+ continue
+ out.append(c)
+ i += 1
+ continue
+ out.append(c)
+ i += 1
+ return "".join(out), used_overset, used_underset, changed
+
+
+def convert_aligned_overset_text(text):
+ """Inverse of rewrite_aligned_overset_text. Returns (newtext,
+ used_overset, used_underset)."""
+ n = len(text)
+ out = []
+ i = 0
+ used_overset = False
+ used_underset = False
+
+ while i < n:
+ c = text[i]
+ if c == "\\":
+ name, nexti = alignedoverset_read_command(text, i + 1)
+ if name in ("lyxoverset", "lyxunderset"):
+ b1 = nexti
+ if b1 < n and text[b1] == "{":
+ e1 = alignedoverset_matching_brace(text, b1)
+ k = e1 + 1
+ while e1 != -1 and k < n and text[k] in " \t\n":
+ k += 1
+ if e1 != -1 and k < n and text[k] == "&":
+ k += 1
+ while k < n and text[k] in " \t\n":
+ k += 1
+ if k < n and text[k] == "{":
+ b2 = k
+ e2 = alignedoverset_matching_brace(text, b2)
+ if e2 != -1:
+ acc = text[b1 + 1 : e1]
+ base = text[b2 + 1 : e2]
+ newcmd = "overset" if name == "lyxoverset" else "underset"
+ if name == "lyxoverset":
+ used_overset = True
+ else:
+ used_underset = True
+ out.append("&\\" + newcmd + "{" + acc + "}{" + base + "}")
+ i = e2 + 1
+ continue
+ out.append(text[i:nexti])
+ i = nexti
+ continue
+ out.append(c)
+ i += 1
+ return "".join(out), used_overset, used_underset
+
+
+def alignedoverset_preamble_lines(used_overset, used_underset):
+ """The preamble lines revert_alignedoverset_body adds/
+ convert_alignedoverset_body removes, as a single group."""
+ lines = ["\\usepackage{aligned-overset}"]
+ if used_overset:
+ lines.append("\\let\\lyxoverset\\overset")
+ if used_underset:
+ lines.append("\\let\\lyxunderset\\underset")
+ return lines
+
+
+def convert_alignedoverset_body(document):
+ """Undo revert_alignedoverset_body: restore native \\overset/\\underset,
+ drop the \\let aliases. Only does so if the aligned-overset package is
+ set to auto or on; if it is off, \\lyxoverset/\\lyxunderset must stay as
+ is, since rewriting to native \\overset/\\underset without loading the
+ package would silently change the LaTeX output."""
+ pkg = "aligned-overset"
+ regexp = re.compile(r"(\\use_package\s+%s)" % pkg)
+ i = find_re(document.header, regexp, 0)
+ value = "1" # default is auto
+ if i != -1:
+ value = get_value(document.header, "\\use_package", i).split()[1]
+ if value == "0":
+ return
+ used_overset = False
+ used_underset = False
+ i = 0
+ while True:
+ i = find_token(document.body, "\\begin_inset Formula", i)
+ if i == -1:
+ break
+ j = find_end_of_inset(document.body, i)
+ if j == -1:
+ document.warning(
+ "Malformed LyX document: Can't find end of Formula inset at line "
+ + str(i)
+ )
+ i += 1
+ continue
+ text = "\n".join(document.body[i:j])
+ newtext, uo, uu = convert_aligned_overset_text(text)
+ if uo or uu:
+ document.body[i:j] = newtext.split("\n")
+ j = i + len(newtext.split("\n"))
+ used_overset = used_overset or uo
+ used_underset = used_underset or uu
+ i = j
+ if used_overset or used_underset:
+ lines = alignedoverset_preamble_lines(used_overset, used_underset)
+ if not del_complete_lines(document.preamble, ["% Added by lyx2lyx"] + lines):
+ del_complete_lines(document.preamble, lines)
+
+
+def revert_alignedoverset_body(document):
+ """Make aligned-overset usages readable by an older LyX, preserving
+ LaTeX output via \\let aliases. Returns (used_overset, used_underset)."""
+ used_overset = False
+ used_underset = False
+ i = 0
+ while True:
+ i = find_token(document.body, "\\begin_inset Formula", i)
+ if i == -1:
+ break
+ j = find_end_of_inset(document.body, i)
+ if j == -1:
+ document.warning(
+ "Malformed LyX document: Can't find end of Formula inset at line "
+ + str(i)
+ )
+ i += 1
+ continue
+ text = "\n".join(document.body[i:j])
+ newtext, uo, uu, changed = rewrite_aligned_overset_text(text)
+ if changed:
+ document.body[i:j] = newtext.split("\n")
+ j = i + len(newtext.split("\n"))
+ used_overset = used_overset or uo
+ used_underset = used_underset or uu
+ i = j
+ if used_overset or used_underset:
+ add_to_preamble(document, alignedoverset_preamble_lines(used_overset, used_underset))
+ return used_overset, used_underset
+
+
+def revert_use_alignedoverset(document):
+ "remove use_package aligned-overset"
+ pkg = "aligned-overset"
+ regexp = re.compile(r"(\\use_package\s+%s)" % pkg)
+ i = find_re(document.header, regexp, 0)
+ value = "1" # default is auto
+ if i != -1:
+ value = get_value(document.header, "\\use_package", i).split()[1]
+ del document.header[i]
+ # rewrite any actual uses so the effect survives in the older format
+ used_overset, used_underset = revert_alignedoverset_body(document)
+ # if the package was forced on but no actual use was found (and thus no
+ # preamble code added above), still load it explicitly
+ if value == "2" and not (used_overset or used_underset):
+ add_to_preamble(document, ["\\usepackage{" + pkg + "}"])
+
+
def revert_specialfonts(document):
"""Revert language-/script-specific fonts to preamble code"""
@@ -1710,11 +1997,13 @@ convert = [
[654, []],
[655, []],
[656, []],
- [657, [convert_svmatters]]
+ [657, [convert_svmatters]],
+ [658, [convert_use_alignedoverset, convert_alignedoverset_body]]
]
revert = [
+ [657, [revert_use_alignedoverset]],
[656, [revert_matters]],
[655, [revert_parfillskip]],
[654, [revert_hex32colors]],
diff --git a/src/BufferParams.cpp b/src/BufferParams.cpp
index 7367dd565b..3ff124adf6 100644
--- a/src/BufferParams.cpp
+++ b/src/BufferParams.cpp
@@ -575,6 +575,9 @@ map<string, string> const & BufferParams::auto_packages()
static Mutex mutex;
Mutex::Locker locker(&mutex);
// adding a package here implies a file format change!
+ packages["aligned-overset"] =
+ N_("The LaTeX package aligned-overset is only used if \\overset or \\underset "
+ "is used as the first item in a cell of an aligned math environment");
packages["amsmath"] =
N_("The LaTeX package amsmath is only used if AMS formula types or symbols from the AMS math toolbars are inserted into formulas");
packages["amssymb"] =
diff --git a/src/LaTeXFeatures.cpp b/src/LaTeXFeatures.cpp
index 71b72e7464..952fd61c5c 100644
--- a/src/LaTeXFeatures.cpp
+++ b/src/LaTeXFeatures.cpp
@@ -1523,6 +1523,11 @@ string const LaTeXFeatures::getPackages() const
params_.use_package("stackrel") != BufferParams::package_off)
packages << "\\usepackage{stackrel}\n";
+ // aligned-overset must be loaded after amsmath
+ if (mustProvide("aligned-overset") &&
+ params_.use_package("aligned-overset") != BufferParams::package_off)
+ packages << "\\usepackage{aligned-overset}\n";
+
if (mustProvide("undertilde") &&
params_.use_package("undertilde") != BufferParams::package_off)
packages << "\\usepackage{undertilde}\n";
diff --git a/src/mathed/InsetMathGrid.cpp b/src/mathed/InsetMathGrid.cpp
index 58c405eef6..dde5672cc8 100644
--- a/src/mathed/InsetMathGrid.cpp
+++ b/src/mathed/InsetMathGrid.cpp
@@ -48,6 +48,22 @@ using namespace lyx::support;
namespace lyx {
+namespace {
+
+// If \p md leads with \overset or \underset, return that inset, else nullptr.
+InsetMath const * alignedOversetLead(MathData const & md)
+{
+ if (md.empty())
+ return nullptr;
+ InsetMath const * in = md[0].nucleus();
+ if (in && (in->lyxCode() == MATH_OVERSET_CODE || in->lyxCode() == MATH_UNDERSET_CODE))
+ return in;
+ return nullptr;
+}
+
+} // namespace
+
+
static docstring verboseHLine(int n)
{
docstring res;
@@ -442,7 +458,10 @@ void InsetMathGrid::metrics(MetricsInfo & mi, Dimension & dim) const
for (row_type row = 0; row < nrows(); ++row) {
idx_type const i = index(row, col);
if (cellinfo_[i].multi != CELL_PART_OF_MULTICOLUMN) {
- int const w = cell(i).dimension(bv).wid;
+ int w = cell(i).dimension(bv).wid;
+ // aligned-overset use extra padding
+ if (col + 1 < ncols())
+ w += alignedOversetDelta(bv, index(row, col + 1));
col_type const cols = ncellcols(i);
if (cols > 1 && nrows() > 1) {
col_type last = col+cols-1;
@@ -905,6 +924,24 @@ void InsetMathGrid::swapCol(col_type col)
}
+int InsetMathGrid::alignedOversetDelta(BufferView const & bv, idx_type idx) const
+{
+ if (!useAlignedOverset() || col(idx) == 0)
+ return 0;
+ if (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN)
+ return 0;
+ if (buffer().params().use_package("aligned-overset") == BufferParams::package_off)
+ return 0;
+ InsetMath const * lead = alignedOversetLead(cell(idx));
+ if (!lead)
+ return 0;
+ int const dim0 = lead->cell(0).dimension(bv).wid;
+ int const dim1 = lead->cell(1).dimension(bv).width();
+ int const boxwid = max(dim1, dim0) + 4;
+ return boxwid / 2 - dim0 / 2 + 2;
+}
+
+
int InsetMathGrid::cellXOffset(BufferView const & bv, idx_type idx) const
{
if (cellinfo_[idx].multi == CELL_PART_OF_MULTICOLUMN)
@@ -913,10 +950,22 @@ int InsetMathGrid::cellXOffset(BufferView const & bv, idx_type idx) const
int x = colinfo_[c].offset;
char align = displayColAlign(idx);
Dimension const & celldim = cell(idx).dimension(bv);
+ // A leading aligned-overset item is shifted left by its own overhang
+ int const own_delta = alignedOversetDelta(bv, idx);
+ if (own_delta > 0)
+ return x - own_delta;
+ // A cell immediately before one that leads with an aligned-overset
+ // item has extra width
+ int next_delta = 0;
+ if (col(idx) + 1 < ncols()) {
+ idx_type const nexti = index(row(idx), col(idx) + 1);
+ next_delta = alignedOversetDelta(bv, nexti);
+ }
+ int const w = celldim.wid + next_delta;
if (align == 'r' || align == 'R')
- x += cellWidth(idx) - celldim.wid;
+ x += cellWidth(idx) - w;
if (align == 'c' || align == 'C')
- x += (cellWidth(idx) - celldim.wid) / 2;
+ x += (cellWidth(idx) - w) / 2;
return x;
}
@@ -1238,6 +1287,13 @@ void InsetMathGrid::validate(LaTeXFeatures & features) const
"border-left: thin solid black; vertical-align: middle; padding-left: 0.5ex;}\n"
"table.mathtable td {text-align: left; border: none;}");
}
+ if (useAlignedOverset() && features.runparams().isLaTeX()
+ && features.bufferParams().use_package("aligned-overset") != BufferParams::package_off) {
+ for (row_type row = 0; row < nrows(); ++row)
+ for (col_type col = 1; col < ncols(); ++col)
+ if (alignedOversetLead(cell(index(row, col))))
+ features.require("aligned-overset");
+ }
InsetMathNest::validate(features);
}
@@ -1258,6 +1314,8 @@ void InsetMathGrid::writeMath(TeXMathStream & os,
// is no longer necessary
bool const fragile = os.fragile()
&& !LaTeXFeatures::isAvailableAtLeastFrom("LaTeX", 2021, 06);
+ bool const use_aligned_overset = os.latex() && useAlignedOverset()
+ && buffer().params().use_package("aligned-overset") != BufferParams::package_off;
for (row_type row = beg_row; row < end_row; ++row) {
os << verboseHLine(rowinfo_[row].lines);
// don't write & and empty cells at end of line,
@@ -1286,7 +1344,9 @@ void InsetMathGrid::writeMath(TeXMathStream & os,
continue;
}
Changer dummy = os.changeRowEntry(entry);
- if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN) {
+ bool const multicol_begin =
+ cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN;
+ if (multicol_begin) {
size_t s = col + 1;
while (s < ncols() &&
cellinfo_[index(row, s)].multi == CELL_PART_OF_MULTICOLUMN)
@@ -1296,12 +1356,34 @@ void InsetMathGrid::writeMath(TeXMathStream & os,
<< "}{" << cellinfo_[idx].align
<< "}{";
}
- os << cell(idx);
+ // & \overset{acc}{base} -> \overset{acc}&{base} (aligned-overset)
+ InsetMath const * lead_overset =
+ (col > beg_col && !multicol_begin && use_aligned_overset)
+ ? alignedOversetLead(cell(idx)) : nullptr;
+ if (lead_overset) {
+ os << (lead_overset->lyxCode() == MATH_OVERSET_CODE ?
+ from_ascii("\\overset{") : from_ascii("\\underset{"))
+ << lead_overset->cell(1) << "}&{" << lead_overset->cell(0) << '}';
+ for (size_t i = 1; i < cell(idx).size(); ++i)
+ os << cell(idx)[i];
+ } else {
+ os << cell(idx);
+ }
if (os.pendingBrace())
ModeSpecifier specifier(os, TEXT_MODE);
- if (cellinfo_[idx].multi == CELL_BEGIN_OF_MULTICOLUMN)
+ if (multicol_begin)
os << '}';
- os << eocString(col + nccols - 1, lastcol);
+ docstring const eoc = eocString(col + nccols - 1, lastcol);
+ bool suppress_eoc = false;
+ if (!eoc.empty() && col + nccols < end_col) {
+ idx_type const nextidx = index(row, col + nccols);
+ if (cellinfo_[nextidx].multi != CELL_BEGIN_OF_MULTICOLUMN &&
+ use_aligned_overset &&
+ alignedOversetLead(cell(nextidx)))
+ suppress_eoc = true;
+ }
+ if (!suppress_eoc)
+ os << eoc;
col += nccols;
}
eol(os, row, fragile, os.latex(), last_eoln);
diff --git a/src/mathed/InsetMathGrid.h b/src/mathed/InsetMathGrid.h
index b23e95ea5c..81f1a66ae3 100644
--- a/src/mathed/InsetMathGrid.h
+++ b/src/mathed/InsetMathGrid.h
@@ -206,6 +206,9 @@ public:
virtual int border() const;
///
virtual bool handlesMulticolumn() const { return false; }
+ /// whether \overset/\underset leading a non-first cell of a row
+ /// should be rewritten as \overset{accent}&{base}
+ virtual bool useAlignedOverset() const { return false; }
///
void writeMath(TeXMathStream & os) const override;
@@ -240,6 +243,8 @@ protected:
int cellYOffset(BufferView const &, idx_type idx) const;
/// Width of cell, taking combined columns into account
int cellWidth(idx_type idx) const;
+ /// calculate shift needed for aligned-overset item
+ int alignedOversetDelta(BufferView const &, idx_type idx) const;
///
virtual int leftMargin() const { return 0; }
///
diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp
index 7b73455987..7e2bd32939 100644
--- a/src/mathed/InsetMathHull.cpp
+++ b/src/mathed/InsetMathHull.cpp
@@ -1163,6 +1163,21 @@ bool InsetMathHull::numberedType() const
}
+bool InsetMathHull::useAlignedOverset() const
+{
+ switch (type_) {
+ case hullAlign:
+ case hullAlignAt:
+ case hullXAlignAt:
+ case hullXXAlignAt:
+ case hullFlAlign:
+ return true;
+ default:
+ return false;
+ }
+}
+
+
void InsetMathHull::validate(LaTeXFeatures & features) const
{
if (features.runparams().isLaTeX()) {
diff --git a/src/mathed/InsetMathHull.h b/src/mathed/InsetMathHull.h
index 3924562abe..64c2253298 100644
--- a/src/mathed/InsetMathHull.h
+++ b/src/mathed/InsetMathHull.h
@@ -110,6 +110,8 @@ public:
/// get type
HullType getType() const override;
+ ///
+ bool useAlignedOverset() const override;
/// is mutation implemented for this type?
static bool isMutable(HullType type);
/// change type
diff --git a/src/mathed/InsetMathSplit.cpp b/src/mathed/InsetMathSplit.cpp
index b721e06398..eed675d377 100644
--- a/src/mathed/InsetMathSplit.cpp
+++ b/src/mathed/InsetMathSplit.cpp
@@ -94,6 +94,16 @@ int InsetMathSplit::displayColSpace(col_type col) const
}
+bool InsetMathSplit::useAlignedOverset() const
+{
+ docstring const base = support::suffixIs(name_, '*')
+ ? name_.substr(0, name_.size() - 1) : name_;
+ return base == "align" || base == "alignat" || base == "xalignat"
+ || base == "xxalignat" || base == "flalign"
+ || base == "aligned" || base == "alignedat" || base == "split";
+}
+
+
void InsetMathSplit::metrics(MetricsInfo & mi, Dimension & dim) const
{
diff --git a/src/mathed/InsetMathSplit.h b/src/mathed/InsetMathSplit.h
index cec49e2158..076186ee2c 100644
--- a/src/mathed/InsetMathSplit.h
+++ b/src/mathed/InsetMathSplit.h
@@ -41,6 +41,8 @@ public:
///
void validate(LaTeXFeatures & features) const override;
///
+ bool useAlignedOverset() const override;
+ ///
int defaultColSpace(col_type) override { return 0; }
///
int displayColSpace(col_type col) const override;
diff --git a/src/mathed/MathParser.cpp b/src/mathed/MathParser.cpp
index 99ee7d5d6a..865f0ad361 100644
--- a/src/mathed/MathParser.cpp
+++ b/src/mathed/MathParser.cpp
@@ -2074,8 +2074,16 @@ bool Parser::parse1(InsetMathGrid & grid, unsigned flags,
}
else if (l->inset == "underset" || l->inset == "overset") {
- cell->push_back(createInsetMath(t.cs(), buf));
- parse(cell->back().nucleus()->cell(1), FLAG_ITEM, mode);
+ MathAtom at = createInsetMath(t.cs(), buf);
+ parse(at.nucleus()->cell(1), FLAG_ITEM, mode);
+ // aligned-overset syntax: \overset{acc}&{base}
+ skipSpaces();
+ if (nextToken().cat() == catAlign) {
+ getToken();
+ if (addCol(grid, cellcol))
+ cell = &grid.cell(grid.index(cellrow, cellcol));
+ }
+ cell->push_back(at);
parse(cell->back().nucleus()->cell(0), FLAG_ITEM, mode);
}
diff --git a/src/tex2lyx/Preamble.cpp b/src/tex2lyx/Preamble.cpp
index 713d76f9fc..2099b37f3d 100644
--- a/src/tex2lyx/Preamble.cpp
+++ b/src/tex2lyx/Preamble.cpp
@@ -1068,6 +1068,7 @@ Preamble::Preamble() : one_language(true), default_language("english"),
h_crossref_package = "prettyref";
h_use_minted = false;
h_use_xcolor_svgnames = false;
+ h_use_packages["aligned-overset"] = "0";
h_use_packages["amsmath"] = "1";
h_use_packages["amssymb"] = "0";
h_use_packages["cancel"] = "0";
@@ -2073,7 +2074,7 @@ void Preamble::handle_package(Parser &p, string const & name,
|| is_known(name, known_typewriter_font_packages) || is_known(name, known_math_font_packages))
;
//"On". See the enum Package in BufferParams.h if you thought that "2" should have been "42"
- else if (name == "amsmath" || name == "amssymb" || name == "cancel" ||
+ else if (name == "aligned-overset" || name == "amsmath" || name == "amssymb" || name == "cancel" ||
name == "esint" || name == "mhchem" || name == "mathdots" ||
name == "mathtools" || name == "stackrel" ||
name == "stmaryrd" || name == "undertilde") {
diff --git a/src/tex2lyx/test/CJK.lyx.lyx b/src/tex2lyx/test/CJK.lyx.lyx
index 5b58b1ffff..d29161dc39 100644
--- a/src/tex2lyx/test/CJK.lyx.lyx
+++ b/src/tex2lyx/test/CJK.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -46,6 +46,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/CJKutf8.lyx.lyx b/src/tex2lyx/test/CJKutf8.lyx.lyx
index 0e6f9d21e1..5286b28bb2 100644
--- a/src/tex2lyx/test/CJKutf8.lyx.lyx
+++ b/src/tex2lyx/test/CJKutf8.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -46,6 +46,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/DummyDocument.lyx.lyx b/src/tex2lyx/test/DummyDocument.lyx.lyx
index fcf993dd7c..975e4c2356 100644
--- a/src/tex2lyx/test/DummyDocument.lyx.lyx
+++ b/src/tex2lyx/test/DummyDocument.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -44,6 +44,7 @@
\pdf_pdfusetitle 0
\papersize a4
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 2
\use_package amssymb 2
\use_package cancel 0
diff --git a/src/tex2lyx/test/Dummy~Document.lyx.lyx b/src/tex2lyx/test/Dummy~Document.lyx.lyx
index a349cf11c1..1cb5090530 100644
--- a/src/tex2lyx/test/Dummy~Document.lyx.lyx
+++ b/src/tex2lyx/test/Dummy~Document.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -44,6 +44,7 @@
\pdf_pdfusetitle 0
\papersize a4
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 2
\use_package amssymb 2
\use_package cancel 0
diff --git a/src/tex2lyx/test/XeTeX-polyglossia.lyx.lyx b/src/tex2lyx/test/XeTeX-polyglossia.lyx.lyx
index 4e39cbbf67..07b467a12a 100644
--- a/src/tex2lyx/test/XeTeX-polyglossia.lyx.lyx
+++ b/src/tex2lyx/test/XeTeX-polyglossia.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -45,6 +45,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/algo2e.lyx.lyx b/src/tex2lyx/test/algo2e.lyx.lyx
index e6e0db3688..a401ff4563 100644
--- a/src/tex2lyx/test/algo2e.lyx.lyx
+++ b/src/tex2lyx/test/algo2e.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -44,6 +44,7 @@ algorithm2e
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/beamer.lyx.lyx b/src/tex2lyx/test/beamer.lyx.lyx
index 9bcd72c903..bb48d1b88f 100644
--- a/src/tex2lyx/test/beamer.lyx.lyx
+++ b/src/tex2lyx/test/beamer.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -45,6 +45,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/box-color-size-space-align.lyx.lyx b/src/tex2lyx/test/box-color-size-space-align.lyx.lyx
index 5be194d5a6..710e2a180a 100644
--- a/src/tex2lyx/test/box-color-size-space-align.lyx.lyx
+++ b/src/tex2lyx/test/box-color-size-space-align.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -70,6 +70,7 @@
\use_hyperref false
\papersize b4
\use_geometry true
+\use_package aligned-overset 0
\use_package amsmath 2
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/listpreamble.lyx.lyx b/src/tex2lyx/test/listpreamble.lyx.lyx
index 4ddc3c39c0..132567faa0 100644
--- a/src/tex2lyx/test/listpreamble.lyx.lyx
+++ b/src/tex2lyx/test/listpreamble.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -49,6 +49,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/tabular-x-test.lyx.lyx b/src/tex2lyx/test/tabular-x-test.lyx.lyx
index d5cc5fde66..272c0d760a 100644
--- a/src/tex2lyx/test/tabular-x-test.lyx.lyx
+++ b/src/tex2lyx/test/tabular-x-test.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -47,6 +47,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-insets-basic.lyx.lyx b/src/tex2lyx/test/test-insets-basic.lyx.lyx
index 87445a7be8..a6303b29a2 100644
--- a/src/tex2lyx/test/test-insets-basic.lyx.lyx
+++ b/src/tex2lyx/test/test-insets-basic.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -80,6 +80,7 @@
\pdf_pdfusetitle 0
\papersize a4
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 2
\use_package amssymb 2
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-insets.lyx.lyx b/src/tex2lyx/test/test-insets.lyx.lyx
index 4d30e74644..c9f1d67faf 100644
--- a/src/tex2lyx/test/test-insets.lyx.lyx
+++ b/src/tex2lyx/test/test-insets.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -56,6 +56,7 @@
\pdf_pdfusetitle 0
\papersize a4
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 2
\use_package amssymb 2
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-memoir.lyx.lyx b/src/tex2lyx/test/test-memoir.lyx.lyx
index 8d1584ed68..982485d8f1 100644
--- a/src/tex2lyx/test/test-memoir.lyx.lyx
+++ b/src/tex2lyx/test/test-memoir.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -42,6 +42,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-minted.lyx.lyx b/src/tex2lyx/test/test-minted.lyx.lyx
index 82f0417725..0b25b30144 100644
--- a/src/tex2lyx/test/test-minted.lyx.lyx
+++ b/src/tex2lyx/test/test-minted.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -41,6 +41,7 @@
\use_hyperref false
\papersize a4
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-modules.lyx.lyx b/src/tex2lyx/test/test-modules.lyx.lyx
index 48b62b20fb..29a7336cb2 100644
--- a/src/tex2lyx/test/test-modules.lyx.lyx
+++ b/src/tex2lyx/test/test-modules.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -42,6 +42,7 @@ theorems-ams
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-refstyle-theorems.lyx.lyx b/src/tex2lyx/test/test-refstyle-theorems.lyx.lyx
index 453184a4e2..a06b588fbf 100644
--- a/src/tex2lyx/test/test-refstyle-theorems.lyx.lyx
+++ b/src/tex2lyx/test/test-refstyle-theorems.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -42,6 +42,7 @@ theorems-ams
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-scr.lyx.lyx b/src/tex2lyx/test/test-scr.lyx.lyx
index d3a746d6f3..b6a8e1df72 100644
--- a/src/tex2lyx/test/test-scr.lyx.lyx
+++ b/src/tex2lyx/test/test-scr.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -39,6 +39,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/test-structure.lyx.lyx b/src/tex2lyx/test/test-structure.lyx.lyx
index c184d6c913..2b61d07e71 100644
--- a/src/tex2lyx/test/test-structure.lyx.lyx
+++ b/src/tex2lyx/test/test-structure.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -76,6 +76,7 @@ logicalmkup
\use_hyperref false
\papersize legal
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/test.lyx.lyx b/src/tex2lyx/test/test.lyx.lyx
index bc0ef19c58..ed581a239c 100644
--- a/src/tex2lyx/test/test.lyx.lyx
+++ b/src/tex2lyx/test/test.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -46,6 +46,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/tex2lyx/test/verbatim.lyx.lyx b/src/tex2lyx/test/verbatim.lyx.lyx
index 9cd3c9385e..90b9ef4164 100644
--- a/src/tex2lyx/test/verbatim.lyx.lyx
+++ b/src/tex2lyx/test/verbatim.lyx.lyx
@@ -1,5 +1,5 @@
#LyX file created by tex2lyx 2.6
-\lyxformat 657
+\lyxformat 658
\begin_document
\begin_header
\save_transient_properties true
@@ -35,6 +35,7 @@
\use_hyperref false
\papersize default
\use_geometry false
+\use_package aligned-overset 0
\use_package amsmath 1
\use_package amssymb 0
\use_package cancel 0
diff --git a/src/version.h b/src/version.h
index 93eeff04a6..3a5466b998 100644
--- a/src/version.h
+++ b/src/version.h
@@ -32,8 +32,8 @@ extern char const * const lyx_version_info;
// Do not remove the comment below, so we get merge conflict in
// independent branches. Instead add your own.
-#define LYX_FORMAT_LYX 657 // spitz: matters
-#define LYX_FORMAT_TEX2LYX 657
+#define LYX_FORMAT_LYX 658 // ufogiel: aligned-overset
+#define LYX_FORMAT_TEX2LYX 658
#if LYX_FORMAT_TEX2LYX != LYX_FORMAT_LYX
#ifndef _MSC_VER
More information about the lyx-cvs
mailing list