I am trying to roughly reproduce and maybe even improve upon the behavior of the following LaTeX macro in Typst:
\makeatletter
% knob: multiply font rule thickness by MUL/DIV (integers only)
\newcount\matrixsymbolthickmul \matrixsymbolthickmul=14 % numerator
\newcount\matrixsymbolthickdiv \matrixsymbolthickdiv=10 % denominator
\newcount\matrixsymboloffsetdiv \matrixsymboloffsetdiv=14 % vertical offset = ht(box)/this
% \matrixsymbol@ takes 5 args:
% #1 = math style token (\displaystyle, \textstyle, \scriptstyle, \scriptscriptstyle)
% #2 = a math font (e.g. \textfont3, \scriptfont3, \scriptscriptfont3) used to read \fontdimen8
% #3 = the actual symbol/formula to typeset (usually #1 from \matrixsymbol)
% #4 = trim (each side) to subtract from underline width (left & right)
% #5 = extend (total) to add to underline width (centered extension)
\protected\def\matrixsymbol@#1#2#3#4#5{%
% Typeset the symbol in the chosen math style and store it in box0
\setbox0=\hbox{$\m@th#1#3$}
%
% Rule thickness: take the font's default rule thickness (fontdimen8) for the chosen font #2, then scale it by thickmul/thickdiv
\dimen6=\fontdimen8#2\relax
\multiply\dimen6 by \matrixsymbolthickmul
\divide\dimen6 by \matrixsymbolthickdiv
%
% Vertical placement: fixed below the baseline as a fraction of the symbol box height
% (distance = ht(box0)/matrixsymboloffsetdiv)
\dimen8=\ht0 \divide\dimen8 by \matrixsymboloffsetdiv
%
% Underline width computation:
% start with symbol width
\dimen0=\wd0
% add total extension (#5)
\advance\dimen0 by #5\relax
% subtract 2 * trim (#4) because trim is per-side
\dimen2=#4\relax
\advance\dimen0 by -2\dimen2
%
% Horizontal start shift for the underline relative to the symbol:
% We want the extension (#5) split equally left/right (so shift left by #5/2), then apply trim (#4) (shift right by trim).
% net shift = trim - extend/2
\dimen4=#5\relax \divide\dimen4 by 2 \dimen4=-\dimen4 \advance\dimen4 by \dimen2
%
% Output: a box whose visible content is copy0, plus an overlaid underline:
% - \rlap makes the underline overlay without consuming horizontal space
% - \smash makes it add no height/depth (so it won't affect vertical spacing)
% - \raise moves it below the baseline by dimen8
\hbox{\rlap{\smash{\raise-\dimen8\hbox{\kern\dimen4\vrule width\dimen0 height0pt depth\dimen6}}}\copy0}%
}
\DeclareRobustCommand{\matrixsymbol}[1]{%
\begingroup
\let\@nomath\@gobble \mathversion{bold}% make #1 bold by switching to the bold math version
%
% \math@atom{#1}{<stuff>} preserves the *atom type* of #1 (ord/op/bin/rel/...)
% so spacing and script placement behave like the original symbol.
\math@atom{#1}{%
\mathchoice
{\matrixsymbol@{\displaystyle}{\textfont3}{#1}{0.05em}{0.00em}}%
{\matrixsymbol@{\textstyle}{\textfont3}{#1}{0.05em}{0.00em}}%
{\matrixsymbol@{\scriptstyle}{\scriptfont3}{#1}{0.05em}{0.00em}}%
{\matrixsymbol@{\scriptscriptstyle}{\scriptscriptfont3}{#1}{0.05em}{0.00em}}%
}%
\endgroup
}
\makeatother
What this does in LaTeX
- Typesets the symbol in bold math.
- Draws a custom underline at a fixed location below the baseline.
- preserves the original math atom type with
\math@atom, so- spacing between neighbors is unchanged,
- subscripts/superscripts attach exactly as if it were just
\mathbf{A}, - stacked scripts behave identically,
- the underline does not influence vertical spacing at all.
Goal in Typst
I want a makro matr(A) such that:
- the underline has the same depth below the baseline of the symbol
Afor all characters, regardless of of their descent, i.e. the underline sits at the same location for a Q as well as an A. - if equivalent to the LaTeX version, then the spacing should not be affected at all and the symbol would be typeset as if it were just a normal
bold(A) - optionally: the resulting symbol is typeset as if it were a letter with the ascent of
Aand the descent being the distance between the baseline and the underline - there is a knob to define the trimming of the underline on both ends
- another knob, a boolean, to determine if the trimming should adjust to the current math env (normal, script, sscript)
- thickness of the underline is determined by the thickness of the current math mode (normal, script, sscript)
What I have tried
I attempted something like:
#let matrixsymbol(
x,
trim: 0.01em,
thickness: 0.05em,
offset_div: 14,
base_rule: 0.04em,
class: "normal",
) = context {
let sym = math.bold(x)
let m = measure(sym)
let dy = m.height / offset_div
let ul_w = m.width - 2 * trim
let dx = m.width * 0.58
let underline = box(width: 0pt, height: 0pt, baseline: 0pt)[
#move(
dx: dx,
dy: dy,
rect(
width: ul_w,
height: thickness,
fill: black,
inset: 0pt,
outset: 0pt,
radius: 0pt,
),
)
]
math.class(class, underline + sym)
}
#let matr(x, ..args) = matrixsymbol(x, ..args)
$
matr(Q)_(matr(Q)_matr(Q))^matr(Q)^matr(Q)
bold(Q)_(bold(Q)_bold(Q))^bold(Q)^bold(Q)
$
$
matr(A)_(matr(A)_matr(A))^matr(A)^matr(A)
bold(A)_(bold(A)_bold(A))^bold(A)^bold(A)
$
$
matr(q)_(matr(q)_matr(q))^matr(q)^matr(q)
bold(q)_(bold(q)_bold(q))^bold(q)^bold(q)
$
Question
Is it possible in Typst to:
- Decorate a math symbol (e.g. with a custom underline)
- While keeping its original math atom type and script attachment behavior
- And without changing its layout metrics?
I would appreciate guidance on whether this is achievable pure typst, or fundamentally requires engine adjustments.
Thanks!