summaryrefslogtreecommitdiff
path: root/source/luametatex/source/tex/texmlist.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/luametatex/source/tex/texmlist.c')
-rw-r--r--source/luametatex/source/tex/texmlist.c7668
1 files changed, 7668 insertions, 0 deletions
diff --git a/source/luametatex/source/tex/texmlist.c b/source/luametatex/source/tex/texmlist.c
new file mode 100644
index 000000000..ac51d2c35
--- /dev/null
+++ b/source/luametatex/source/tex/texmlist.c
@@ -0,0 +1,7668 @@
+/*
+ See license.txt in the root of this project.
+*/
+
+/*tex
+
+ The code here has to deal with traditional \TEX\ fonts as well as the more modern \OPENTYPE\
+ fonts. In \TEX\ fonts the spacing between and construction of glyphs is determined by font
+ parameters, kerns, italic correction and linked lists of glyphs that make extensibles. In
+ \OPENTYPE\ fonts kerns are replaced by so called staircase kerns, italics are used differently
+ and extensibles are made from other glyphs, as in traditional \TEX\ fonts.
+
+ In traditional \TEX\ the italic correction is added to the width of the glyph. This is part of
+ the engine design and this is also reflected in the widtn metric of the font. In \OPENTYPE\ math
+ this is different. There the italic correction had more explicit usage. The 1.7 spec says:
+
+ \startitemize
+
+ \startitem
+ {\em italic correction:} When a run of slanted characters is followed by a straight
+ character (such as an operator or a delimiter), the italics correction of the last glyph is
+ added to its advance width.
+
+ When positioning limits on an N-ary operator (e.g., integral sign), the horizontal position
+ of the upper limit is moved to the right by half the italics correction, while the position
+ of the lower limit is moved to the left by the same distance. Comment HH: this is is only
+ true when we have a real italic integral where the top part stick out right and the bottom
+ part left. So, that's only 'one' n-ary operator.
+
+ When positioning superscripts and subscripts, their default horizontal positions are also
+ different by the amount of the italics correction of the preceding glyph.
+ \stopitem
+
+ \startitem
+ {\em math kerning:} Set the default horizontal position for the superscript as shifted
+ relative to the position of the subscript by the italics correction of the base glyph.
+ \stopitem
+
+ \stopitemize
+
+ Before this was specified we had to gamble a bit and assume that cambria was the font
+ benchmark and trust our eyes (and msword) for the logic. I must admit that I have been
+ fighting these italics in fonts (and the heuristics that \LUAMETATEX\ provided) right from the
+ start (for instance by using \LUA\ based postprocessing) but by now we know more and have more
+ fonts to test with. More fonts are handy because not all fonts are alike when it comes to
+ italics. Axis are another area of concern, as it looks like \OPENTYPE\ math fonts often already
+ apply that shift.
+
+ Now, one can think of cheating. Say that we add the italic correction to the widths and then
+ make the italic correction zero for all these shapes except those that have a slope, in which
+ case we negate tot correction. Unfortunately that doesn't work well because the traditional
+ code path {\em assumes} the too narrow shape: it doesn't compensate subscripts. Also, keep in
+ mind that in for instance Pagella (etc), at least in the pre 2022 versions, even upright
+ characters have italic corrections! It looks like they are used as kerns in a way similar to
+ staircase kerns. So, here, when we add the correction we incorrectly flag it as italic but we
+ have no way to distinguish them from regular kerns. When the gyre fonts never get corrected
+ we're stick with the two code paths forever.
+
+ Blocking italic correction via the glyph options is supported (not yet for other constructs
+ but that might happen). All this italic stuff makes the source a bit messy. Maybe the other
+ things will be controlled via a noad option.
+
+ The above description is no longer accurate but we keep it for historic reasons. We now
+ follow a reverse approach: we just assume \OPENTYPE\ but also expect the needed features to
+ be enabled explicitly. That means that for instance \quote {out of the box} the engine will
+ not apply italic correction.
+
+ In 2021-2022 Mikael Sundqvist and I (Hans Hagen) spent about a year investigating how we could
+ improve the rendering of math. Quite a bit of research went into that and we decided to get rid
+ of some old font code and concentrate on the \OPENTYPE\ fonts, although we found some flaws and
+ inconsistencies in them. The solution was to assume a Cambria alike font and adapt the other
+ fonts runtime using so called goodie files that are part of the \CONTEXT\ font loading code.
+ That way we could enforce some consistency and compentate for e.g. problematic dimensions like
+ widths and italic corrections as well as bad top accents and values of font parameters that
+ interfered with what we had in mind. We added plenty extra ones as well as extra kern options.
+ Combined with a more rich model for inter atom spacing we could improve the look and feel a lot.
+
+ When the engine got updated a couple of options came and went. An example of this is delimiter
+ options. For instance we tracked if a delimiter was actually changes and could then react to that
+ wrt italic corrections. In the new approach we no longer handle that because assume decent fonts
+ or at least tweaked ones (read: \CONTEXT\ font goodies being applied). So in the end those extra
+ delimiter options got removed or were just handled by the noad options. The code is still in the
+ repository. Also some options related to tracing injected kerns became defaults because we had
+ them always turned on.
+
+*/
+
+# include "luametatex.h"
+
+/*tex
+
+ We have some more function calls and local so we have replace |cur_style| by |style| where that
+ makes sense. The same is true for some local variables. This makes it a bit easier to
+ distinguish with the more global variables stored in state structures.
+
+ It's a stepwise process ... occasionally I visit this file and change the short variable names
+ to more verbose. There is also relatively new scaling code that needs checking.
+
+*/
+
+static void tex_aux_append_hkern_to_box_list (halfword q, scaled delta, halfword subtype, const char *trace);
+static void tex_aux_prepend_hkern_to_box_list(halfword q, scaled delta, halfword subtype, const char *trace);
+
+/*tex
+
+ \LUAMETATEX\ makes a bunch of extensions cf.\ the |MATH| table in \OPENTYPE, but some of the
+ |MathConstants| values have no matching usage in \LUAMETATEX\ right now.
+
+ \startitemize
+
+ \startitem
+ |ScriptPercentScaleDown| |ScriptScriptPercentScaleDown|: These should be handled by the
+ macro package, on the engine side there are three separate fonts.
+ \stopitem
+
+ \startitem
+ |DelimitedSubFormulaMinHeight|: This is perhaps related to word's natural math input?
+ We have no idea what to do about it.
+ \stopitem
+
+ \startitem
+ |MathLeading|: \LUAMETATEX\ does not currently handle multi line displays, and the
+ parameter does not seem to make much sense elsewhere.
+ \stopitem
+
+ \startitem
+ |FlattenedAccentBaseHeight|: This is based on the |flac| |GSUB| feature. It would not
+ be hard to support that, but proper math accent placements cf.\ |MATH| needs support
+ for |MathTopAccentAttachment| table to be implemented first.
+ \stopitem
+
+ \stopitemize
+
+ Old-style fonts do not define the |radical_rule|. This allows |make_radical| to select the
+ backward compatibility code, but it also means that we can't raise an error here.
+
+ Occasionally I visit this file and make some variables more verbose.
+
+ In the meantime some experimental and in the meantime obsolete code has been removed but it can
+ be found in the development repository if really needed. It makes no sense to keep code around
+ that has been replaced or improved otherwise. Some code we keep commented for a while before it
+ is flushed out.
+
+*/
+
+typedef struct scriptdata {
+ halfword node;
+ halfword fnt;
+ halfword chr;
+ halfword box;
+ scaled kern;
+ scaled slack;
+ int shifted;
+ int padding;
+} scriptdata;
+
+typedef struct delimiterextremes {
+ scaled tfont;
+ scaled tchar;
+ scaled bfont;
+ scaled bchar;
+ scaled height;
+ scaled depth;
+} delimiterextremes;
+
+typedef enum limits_modes {
+ limits_unknown_mode,
+ limits_vertical_mode, // limits
+ limits_horizontal_mode, // no limits
+} limits_modes;
+
+inline void tex_math_wipe_kerns(kernset *kerns) {
+ if (kerns) {
+ kerns->topright = 0;
+ kerns->topleft = 0;
+ kerns->bottomright = 0;
+ kerns->bottomleft = 0;
+ kerns->height = 0;
+ kerns->depth = 0;
+ kerns->toptotal = 0;
+ kerns->bottomtotal = 0;
+ }
+}
+
+inline void tex_math_copy_kerns(kernset *kerns, kernset *parent) {
+ if (kerns && parent) {
+ kerns->topright = parent->topright;
+ kerns->topleft = parent->topleft;
+ kerns->bottomright = parent->bottomright;
+ kerns->bottomleft = parent->bottomleft;
+ kerns->height = parent->height;
+ kerns->depth = parent->depth;
+ kerns->toptotal = parent->toptotal;
+ kerns->bottomtotal = parent->bottomtotal;
+ }
+}
+
+/*tex
+
+ When the style changes, the following piece of program computes associated information:
+
+*/
+
+inline static halfword tex_aux_set_style_to_size(halfword style)
+{
+ switch (style) {
+ case script_style:
+ case cramped_script_style:
+ return script_size;
+ case script_script_style:
+ case cramped_script_script_style:
+ return script_script_size;
+ default:
+ return text_size;
+ }
+}
+
+inline static void tex_aux_set_current_math_scale(halfword scale)
+{
+ glyph_scale_par = scale;
+ lmt_math_state.scale = glyph_scale_par;
+}
+
+inline static void tex_aux_set_current_math_size(halfword style)
+{
+ lmt_math_state.size = tex_aux_set_style_to_size(style);
+}
+
+inline static void tex_aux_make_style(halfword current, halfword *current_style, halfword *current_mu)
+{
+ halfword style = node_subtype(current);
+ switch (style) {
+ case scaled_math_style:
+ tex_aux_set_current_math_scale(style_scale(current));
+ break;
+ default:
+ if (is_valid_math_style(style)) {
+ if (current_style) {
+ *current_style = style;
+ }
+ tex_aux_set_current_math_size(style);
+ if (current_mu) {
+ *current_mu = scaledround(tex_get_math_quad_style(style) / 18.0);
+ }
+ }
+ break;
+ }
+}
+
+void tex_set_math_text_font(halfword style, int usetextfont)
+{
+ halfword size = tex_aux_set_style_to_size(style);
+ halfword font = tex_fam_fnt(cur_fam_par, size);
+ halfword scale = tex_get_math_font_scale(font, size);
+ switch (usetextfont) {
+ case math_atom_text_font_option:
+ scale = scaledround((double) scale * lmt_font_state.fonts[font]->size / lmt_font_state.fonts[cur_font_par]->size);
+ break;
+ case math_atom_math_font_option:
+ update_tex_font(0, font);
+ break;
+ }
+ update_tex_glyph_scale(scale);
+}
+
+static halfword tex_aux_math_penalty_what(int pre, halfword cls, halfword pre_code, halfword post_code)
+{
+ halfword value = count_parameter(pre ? (pre_code + cls) : (post_code + cls));
+ if (value == infinite_penalty) {
+ unsigned parent = (unsigned) count_parameter(first_math_parent_code + cls);
+ cls = pre ? ((parent >> 8) & 0xFF) : (parent & 0xFF);
+ if (! valid_math_class_code(cls)) {
+ return infinite_penalty;
+ }
+ value = count_parameter(pre ? (pre_code + cls) : (post_code + cls));
+ }
+ return value;
+}
+
+static halfword tex_aux_math_penalty(int main_style, int pre, halfword cls)
+{
+ switch (main_style) {
+ case display_style:
+ case cramped_display_style:
+ {
+ halfword value = tex_aux_math_penalty_what(pre, cls, first_math_display_pre_penalty_code, first_math_display_post_penalty_code);
+ if (value != infinite_penalty) {
+ return value;
+ } else {
+ break;
+ }
+ }
+ }
+ return tex_aux_math_penalty_what(pre, cls, first_math_pre_penalty_code, first_math_post_penalty_code);
+}
+
+inline static scaled limited_scaled(long l) {
+ if (l > max_dimen) {
+ return max_dimen;
+ } else if (l < -max_dimen) {
+ return -max_dimen;
+ } else {
+ return (scaled) l;
+ }
+}
+
+inline static scaled limited_rounded(double d) {
+ long l = scaledround(d);
+ if (l > max_dimen) {
+ return max_dimen;
+ } else if (l < -max_dimen) {
+ return -max_dimen;
+ } else {
+ return (scaled) l;
+ }
+}
+
+// inline static int tex_aux_has_opentype_metrics(halfword f)
+// {
+// return font_math_parameter_count(f) > 0 && ! font_oldmath(f);
+// }
+
+inline static int tex_aux_math_engine_control(halfword fnt, halfword chr)
+{
+ if (fnt && (math_font_control_par & math_control_use_font_control) == math_control_use_font_control) {
+ /*tex
+ This is only for old fonts and it might go away eventually. Not all control options relate to
+ a font.
+ */
+ return (font_mathcontrol(fnt) & chr) == chr;
+ }
+ return (math_font_control_par & chr) == chr;
+}
+
+/*
+
+ Todo: When we pass explicit dimensions (keyword driven) we use a different helper so that, if
+ needed we can add debug messages. These values {\em are} scaled according to the glyph scaling
+ so basically they are relative measures. Maybe we need an extra parameter to control this.
+
+*/
+
+inline static scaled tex_aux_math_glyph_scale(scaled v)
+{
+ return v ? scaledround(0.001 * glyph_scale_par * v) : 0;
+}
+
+inline static scaled tex_aux_math_x_scaled(scaled v, int style)
+{
+ scaled scale = tex_get_math_parameter(style, math_parameter_x_scale, NULL);
+ return v ? limited_rounded(0.000000001 * glyph_scale_par * glyph_x_scale_par * v * scale) : 0;
+}
+
+inline static scaled tex_aux_math_given_x_scaled(scaled v)
+{
+ return v;
+}
+
+/* used for math_operator_size */
+
+inline static scaled tex_aux_math_y_scaled(scaled v, int style)
+{
+ scaled scale = tex_get_math_parameter(style, math_parameter_y_scale, NULL);
+ return v ? limited_rounded(0.000000001 * glyph_scale_par * glyph_y_scale_par * v * scale) : 0;
+}
+
+inline static scaled tex_aux_math_given_y_scaled(scaled v)
+{
+ return v;
+}
+
+inline static scaled tex_aux_math_axis(halfword size)
+{
+ scaled a = tex_math_axis_size(size); /* already scaled to size and x_scale */
+ return a ? limited_rounded(0.000001 * glyph_scale_par * glyph_y_scale_par * a) : 0;
+}
+
+inline static scaled tex_aux_math_x_size_scaled(halfword f, scaled v, halfword size)
+{
+ return v ? limited_rounded(0.000000001 * tex_get_math_font_scale(f, size) * glyph_scale_par * glyph_x_scale_par * v) : 0;
+}
+
+inline static scaled tex_aux_math_y_size_scaled(halfword f, scaled v, halfword size)
+{
+ return v ? limited_rounded(0.000000001 * tex_get_math_font_scale(f, size) * glyph_scale_par * glyph_y_scale_par * v) : 0;
+}
+
+halfword tex_math_font_char_ht(halfword fnt, halfword chr, halfword style)
+{
+ return tex_aux_math_y_size_scaled(fnt, tex_char_height_from_font(fnt, chr), tex_aux_set_style_to_size(style));
+}
+
+halfword tex_math_font_char_dp(halfword fnt, halfword chr, halfword style)
+{
+ return tex_aux_math_y_size_scaled(fnt, tex_char_depth_from_font(fnt, chr), tex_aux_set_style_to_size(style));
+}
+
+inline static halfword tex_aux_new_math_glyph(halfword fnt, halfword chr, quarterword subtype) {
+ halfword scale = 1000;
+ halfword glyph = tex_new_glyph_node(subtype, fnt, tex_get_math_char(fnt, chr, lmt_math_state.size, &scale), null); /* todo: data */;
+ set_glyph_options(glyph, glyph_options_par);
+ glyph_scale(glyph) = tex_aux_math_glyph_scale(scale);
+ glyph_x_scale(glyph) = glyph_x_scale_par;
+ glyph_y_scale(glyph) = glyph_y_scale_par;
+ glyph_protected(glyph) = glyph_protected_math_code;
+ return glyph;
+}
+
+halfword tex_new_math_glyph(halfword fnt, halfword chr) {
+ return tex_aux_new_math_glyph(fnt, chr, 0);
+}
+
+static void tex_aux_trace_kerns(halfword kern, const char *what, const char *detail)
+{
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: %s, %s, amount %D]", what, detail, kern_amount(kern), pt_unit);
+ tex_end_diagnostic();
+ }
+}
+
+static halfword tex_aux_math_insert_font_kern(halfword current, scaled amount, halfword template, const char *trace)
+{
+ /*tex Maybe |math_font_kern|, also to prevent expansion. */
+ halfword kern = tex_new_kern_node(amount, font_kern_subtype);
+ tex_attach_attribute_list_copy(kern, template ? template : current);
+ if (node_next(current)) {
+ tex_couple_nodes(kern, node_next(current));
+ }
+ tex_couple_nodes(current, kern);
+ tex_aux_trace_kerns(kern, "adding font kern", trace);
+ return kern;
+}
+
+static halfword tex_aux_math_insert_italic_kern(halfword current, scaled amount, halfword template, const char *trace)
+{
+ /*tex Maybe |math_italic_kern|. */
+ halfword kern = tex_new_kern_node(amount, italic_kern_subtype);
+ tex_attach_attribute_list_copy(kern, template ? template : current);
+ if (node_next(current)) {
+ tex_couple_nodes(kern, node_next(current));
+ }
+ tex_couple_nodes(current, kern);
+ tex_aux_trace_kerns(kern, "adding italic kern", trace);
+ return kern;
+}
+
+static int tex_aux_math_followed_by_italic_kern(halfword current, const char *trace)
+{
+ if (current) {
+ halfword next = node_next(current);
+ if (next && node_type(next) == kern_node && node_subtype(next) == italic_kern_subtype) {
+ tex_aux_trace_kerns(next, "ignoring italic kern", trace);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static inline int tex_aux_checked_left_kern_fnt_chr(halfword fnt, halfword chr, halfword state, halfword subtype)
+{
+ halfword top = 0;
+ halfword bot = 0;
+ halfword hastop = (state & prime_script_state) || (state & post_super_script_state);
+ halfword hasbot = state & post_sub_script_state;
+ if (hastop && tex_math_has_class_option(subtype, left_top_kern_class_option)) {
+ top = tex_char_top_left_kern_from_font(fnt, chr);
+ }
+ if (hasbot && tex_math_has_class_option(subtype, left_bottom_kern_class_option)) {
+ bot = tex_char_bottom_left_kern_from_font(fnt, chr);
+ }
+ if (hastop && hasbot) {
+ return top > bot ? top : bot;
+ } else if (hastop) {
+ return top;
+ } else {
+ return bot;
+ }
+}
+
+static inline int tex_aux_checked_left_kern(halfword list, halfword state, halfword subtype)
+{
+ if (list && node_type(list) == glyph_node) {
+ return tex_aux_checked_left_kern_fnt_chr(glyph_font(list), glyph_character(list), state, subtype);
+ } else {
+ return 0;
+ }
+}
+
+static inline int tex_aux_checked_right_kern_fnt_chr(halfword fnt, halfword chr, halfword state, halfword subtype)
+{
+ halfword top = 0;
+ halfword bot = 0;
+ halfword hastop = state & pre_super_script_state;
+ halfword hasbot = state & pre_sub_script_state;
+ if (hastop && tex_math_has_class_option(subtype, right_top_kern_class_option)) {
+ top = tex_char_top_right_kern_from_font(fnt, chr);
+ }
+ if (hasbot && tex_math_has_class_option(subtype, right_bottom_kern_class_option)) {
+ bot = tex_char_bottom_right_kern_from_font(fnt, chr);
+ }
+ if (hastop && hasbot) {
+ return top < bot ? bot : top;
+ } else if (hastop) {
+ return top;
+ } else {
+ return bot;
+ }
+}
+
+static inline int tex_aux_checked_right_kern(halfword list, halfword state, halfword subtype)
+{
+ if (list && node_type(list) == glyph_node) {
+ return tex_aux_checked_right_kern_fnt_chr(glyph_font(list), glyph_character(list), state, subtype);
+ } else {
+ return 0;
+ }
+}
+
+/*tex We no longer need this one:
+
+ \starttyping
+ static halfword tex_aux_math_remove_italic_kern(halfword head, scaled *italic, const char *trace)
+ {
+ halfword tail = tex_tail_of_node_list(box_list(head));
+ if (tail && node_type(tail) == kern_node && node_subtype(tail) == italic_kern_subtype && kern_amount(tail) == *italic) {
+ tex_aux_trace_kerns(tail, "removing italic kern", trace);
+ if (head == tail) {
+ head = null;
+ } else {
+ head = node_prev(tail);
+ node_next(node_prev(tail)) = null;
+ }
+ tex_flush_node(tail);
+ *italic = 0;
+ }
+ return head;
+ }
+ \starttyping
+
+*/
+
+/*tex We no longer need this one:
+
+ \starttyping
+ static void tex_aux_normalize_delimiters(halfword l, halfword r)
+ {
+ if (box_width(l) == null_delimiter_space_par) {
+ box_height(l) = box_height(r);
+ box_depth(l) = box_depth(r);
+ box_shift_amount(l) = box_shift_amount(r);
+ } else if (box_width(r) == null_delimiter_space_par) {
+ box_height(r) = box_height(l);
+ box_depth(r) = box_depth(l);
+ box_shift_amount(r) = box_shift_amount(l);
+ }
+ }
+ \starttyping
+
+*/
+
+static scaled tex_aux_check_rule_thickness(halfword target, int size, halfword *fam, halfword control, halfword param)
+{
+ /* if (math_rule_thickness_mode_par > 0) { */
+ halfword family = noad_family(target);
+ if (family != unused_math_family) {
+ halfword font = tex_fam_fnt(family, size);
+ if (tex_aux_math_engine_control(font, control)) {
+ scaled thickness = tex_get_font_math_parameter(font, size, param);
+ if (thickness != undefined_math_parameter) {
+ *fam = family;
+ return thickness;
+ }
+ }
+ }
+ /* } */
+ return undefined_math_parameter;
+}
+
+/*tex Fake character */
+
+static halfword tex_aux_fake_nucleus(quarterword cls)
+{
+ halfword n = tex_new_node(simple_noad, cls);
+ halfword q = tex_new_node(math_char_node, 0);
+ set_noad_classes(n, cls);
+ noad_nucleus(n) = q;
+ return n;
+}
+
+/*tex For tracing purposes we add a kern instead of just adapting the width. */
+
+static void tex_aux_fake_delimiter(halfword result)
+{
+ halfword amount = tex_aux_math_given_x_scaled(null_delimiter_space_par);
+ if (amount) {
+ box_width(result) = amount;
+ box_list(result) = tex_new_kern_node(amount, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(box_list(result), result);
+ }
+}
+
+/*tex
+ A variant on a suggestion on the list based on analysis by Ulrik Vieth it in the mean
+ adapted. We keep these 500 and 2 because then we can use similar values.
+*/
+
+static scaled tex_aux_get_delimiter_height(scaled height, scaled depth, int axis, int size, int style)
+{
+ scaled delta1 = height + depth;
+ scaled delta2 = depth;
+ scaled delta3 = 0;
+ halfword percent = tex_get_math_parameter_default(style, math_parameter_delimiter_percent, 0);
+ scaled shortfall = tex_get_math_y_parameter_default(style, math_parameter_delimiter_shortfall, 0);
+ if (axis) {
+ delta2 += tex_aux_math_axis(size);
+ }
+ delta1 -= delta2;
+ if (delta2 > delta1) {
+ /*tex |delta1| is max distance from axis */
+ delta1 = delta2;
+ }
+ delta3 = scaledround((delta1 / 500.0) * delimiter_factor_par * (percent / 100.0));
+ delta2 = 2 * delta1 - delimiter_shortfall_par - shortfall;
+ return (delta3 < delta2) ? delta2 : delta3;
+}
+
+/*tex
+
+ In order to convert mlists to hlists, i.e., noads to nodes, we need several subroutines that
+ are conveniently dealt with now.
+
+ Let us first introduce the macros that make it easy to get at the parameters and other font
+ information. A size code, which is a multiple of 256, is added to a family number to get an
+ index into the table of internal font numbers for each combination of family and size. (Be
+ alert: size codes get larger as the type gets smaller.) In the meantime we use different
+ maxima and packing as in \LUATEX.
+
+*/
+
+static const char *tex_aux_math_size_string(int s)
+{
+ switch (s) {
+ case script_script_size: return "scriptscriptfont";
+ case script_size: return "scriptfont";
+ default: return "textfont";
+ }
+}
+
+/*tex Here is a simple routine that creates a flat copy of a nucleus. */
+
+static halfword tex_aux_math_clone(halfword n)
+{
+ if (n) {
+ halfword result = tex_new_node(node_type(n), 0);
+ tex_attach_attribute_list_copy(result, n);
+ tex_math_copy_char_data(result, n, 0);
+ return result;
+ } else {
+ return null;
+ }
+}
+
+/*tex
+ A helper used in void or phantom situations. We replace the content by a rule so that we still
+ have some content (handy for tracing).
+*/
+
+static halfword tex_aux_make_list_phantom(halfword source, int nowidth, halfword att)
+{
+ halfword target = null;
+ switch (node_type(source)) {
+ case hlist_node:
+ target = tex_new_node(hlist_node, node_subtype(source));
+ break;
+ case vlist_node:
+ target = tex_new_node(vlist_node, node_subtype(source));
+ break;
+ }
+ if (target) {
+ halfword rule = tex_new_rule_node(empty_rule_subtype);
+ tex_attach_attribute_list_attribute(target, att);
+ tex_attach_attribute_list_attribute(rule, att);
+ rule_width(rule) = nowidth ? 0 : box_width(source);
+ rule_height(rule) = box_height(source);
+ rule_depth(rule) = box_depth(source);
+ box_dir(target) = dir_lefttoright ;
+ box_height(target) = rule_height(rule);
+ box_depth(target) = rule_depth(rule);
+ box_width(target) = rule_width(rule);
+ box_shift_amount(target) = box_shift_amount(source);
+ box_list(target) = rule;
+ tex_flush_node_list(source);
+ return target;
+ } else {
+ return source;
+ }
+}
+
+/*tex
+
+ Here is a function that returns a pointer to a rule node having a given thickness |t|. The rule
+ will extend horizontally to the boundary of the vlist that eventually contains it.
+
+*/
+
+static halfword tex_aux_fraction_rule(scaled width, scaled height, halfword att, quarterword ruletype, halfword size, halfword fam)
+{
+ halfword rule = null;
+ int callback_id = lmt_callback_defined(math_rule_callback);
+ if (callback_id > 0) {
+ lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "ddddN->N", math_rules_mode_par ? ruletype : normal_rule_subtype, tex_fam_fnt(fam, size), width, height, att, &rule);
+ if (rule && node_type(rule) != hlist_node) {
+ rule = tex_hpack(rule, 0, packing_additional, direction_unknown, holding_none_option);
+ node_subtype(rule) = math_rule_list;
+ tex_attach_attribute_list_attribute(rule, att);
+ }
+ }
+ if (! rule) {
+ if (math_rules_mode_par) {
+ rule = tex_new_rule_node(ruletype);
+ rule_data(rule) = tex_fam_fnt(fam, size);
+ } else {
+ rule = tex_new_rule_node(normal_rule_subtype);
+ }
+ rule_height(rule) = height;
+ rule_depth(rule) = 0;
+ tex_attach_attribute_list_attribute(rule, att);
+ }
+ return rule;
+}
+
+/*tex
+
+ The |overbar| function returns a pointer to a vlist box that consists of a given box |b|, above
+ which has been placed a kern of height |k| under a fraction rule of thickness |t| under
+ additional space of height |ht|.
+
+*/
+
+static halfword tex_aux_overbar(halfword box, scaled gap, scaled height, scaled krn, halfword att, quarterword index, halfword size, halfword fam)
+{
+ halfword rule = tex_aux_fraction_rule(box_width(box), height, att, index, size, fam);
+ if (gap) {
+ halfword kern = tex_new_kern_node(gap, vertical_math_kern_subtype);
+ tex_attach_attribute_list_attribute(kern, att);
+ tex_couple_nodes(kern, box);
+ tex_couple_nodes(rule, kern);
+ } else {
+ tex_couple_nodes(rule, box);
+ }
+ if (krn) {
+ halfword kern = tex_new_kern_node(krn, vertical_math_kern_subtype);
+ tex_attach_attribute_list_attribute(kern, att);
+ tex_couple_nodes(kern, rule);
+ rule = kern;
+ }
+ rule = tex_vpack(rule, 0, packing_additional, max_dimen, (singleword) math_direction_par, holding_none_option);
+ tex_attach_attribute_list_attribute(rule, att);
+ return rule;
+}
+
+static halfword tex_aux_underbar(halfword box, scaled gap, scaled height, scaled krn, halfword att, quarterword index, halfword size, halfword fam)
+{
+ halfword rule = tex_aux_fraction_rule(box_width(box), height, att, index, size, fam);
+ if (gap) {
+ halfword kern = tex_new_kern_node(gap, vertical_math_kern_subtype);
+ tex_attach_attribute_list_attribute(kern, att);
+ tex_couple_nodes(box, kern);
+ tex_couple_nodes(kern, rule);
+ } else {
+ tex_couple_nodes(box, rule);
+ }
+ if (krn) {
+ halfword kern = tex_new_kern_node(krn, vertical_math_kern_subtype);
+ tex_attach_attribute_list_attribute(kern, att);
+ tex_couple_nodes(rule, kern);
+ }
+ rule = tex_vpack(box, 0, packing_additional, max_dimen, (singleword) math_direction_par, holding_none_option);
+ tex_attach_attribute_list_attribute(rule, att);
+ /* */
+ box_depth(rule) = box_total(rule) + krn - box_height(box);
+ box_height(rule) = box_height(box);
+ /* */
+ return rule;
+}
+
+/*tex
+
+ Here is a subroutine that creates a new box, whose list contains a single character, and whose
+ width includes the italic correction for that character. The height or depth of the box will be
+ negative, if the height or depth of the character is negative. Thus, this routine may deliver a
+ slightly different result than |hpack| would produce.
+
+ The oldmath font flag can be used for cases where we pass a new school math constants (aka
+ parameters) table but have a (virtual) font assembled that uses old school type one fonts. In
+ that case we have a diffeent code path for:
+
+ \startitemize
+ \startitem rule thickness \stopitem
+ \startitem accent skew \stopitem
+ \startitem italic correction (normal width assumes it to be added) \stopitem
+ \startitem kerning \stopitem
+ \startitem delimiter construction \stopitem
+ \startitem accent placement \stopitem
+ \stopitemize
+
+ In the traditional case an italic kern is always added and the |ic| variable is then passed
+ to the caller. For a while we had an option to add the correction to the width but now we
+ have the control options. So these are the options:
+
+ - traditional: insert a kern and pass that correction.
+ - opentype : traditional_math_char_italic_width: add to width
+ - : traditional_math_char_italic_pass : pass ic
+
+ Adding a kern in traditional mode is a mode driven option, not a font one.
+
+*/
+
+static halfword tex_aux_char_box(halfword fnt, int chr, halfword att, scaled *ic, quarterword subtype, scaled target, int style)
+{
+ /*tex The new box and its character node. */
+ halfword glyph = tex_aux_new_math_glyph(fnt, chr, subtype);
+ halfword box = tex_new_null_box_node(hlist_node, math_char_list);
+ scaledwhd whd = tex_char_whd_from_glyph(glyph);
+ tex_attach_attribute_list_attribute(glyph, att);
+ tex_attach_attribute_list_attribute(box, att);
+ box_width(box) = whd.wd;
+ box_height(box) = whd.ht;
+ box_depth(box) = whd.dp;
+ box_list(box) = glyph;
+ if (tex_has_glyph_option(glyph, glyph_option_no_italic_correction)) {
+ whd.ic = 0;
+ }
+ if (whd.ic) {
+ if (ic) {
+ *ic = whd.ic; /* also in open type? needs checking */
+ }
+ if (tex_aux_math_engine_control(fnt, math_control_apply_char_italic_kern)) {
+ tex_aux_math_insert_italic_kern(glyph, whd.ic, glyph, "box");
+ box_width(box) += whd.ic;
+ } else {
+ return box;
+ }
+ } else if (ic) {
+ *ic = 0;
+ }
+ if (target && whd.wd < target && tex_char_has_tag_from_font(fnt, chr, extend_last_tag)) {
+ scaled margin = tex_get_math_x_parameter_default(style, math_parameter_accent_extend_margin, 0);
+ scaled amount = target - 2 * margin;
+ glyph_x_scale(glyph) = lround((double) glyph_x_scale(glyph) * amount/whd.wd);
+ glyph_x_offset(glyph) = (whd.wd - amount)/2;
+ }
+ return box;
+}
+
+/*tex
+
+ When we build an extensible character, it's handy to have the following subroutine, which puts
+ a given character on top of the characters already in box |b|:
+
+*/
+
+// static scaled tex_aux_stack_into_box(halfword b, halfword f, int c, quarterword subtype, int horiziontal)
+// {
+// /*tex New node placed into |b|. Italic gets added to width in 8 bit fonts. */
+// halfword boxed = tex_aux_char_box(f, c, get_attribute_list(b), NULL, subtype);
+// halfword glyph = box_list(boxed);
+// if (horiziontal) {
+// halfword list = box_list(b);
+// if (list) {
+// tex_couple_nodes(tex_tail_of_node_list(list), boxed);
+// } else {
+// box_list(b) = boxed;
+// }
+// if (box_height(b) < box_height(boxed)) {
+// box_height(b) = box_height(boxed);
+// }
+// if (box_depth(b) < box_depth(boxed)) {
+// box_depth(b) = box_depth(boxed);
+// }
+// return tex_char_width_from_glyph(glyph);
+// } else {
+// tex_try_couple_nodes(boxed, box_list(b));
+// box_list(b) = boxed;
+// box_height(b) = box_height(boxed);
+// if (box_width(b) < box_width(boxed)) {
+// box_width(b) = box_width(boxed);
+// }
+// return tex_char_total_from_glyph(glyph);
+// }
+// }
+
+/*tex
+ There is no need to deal with an italic correction here. If there is one in an extensible we
+ have a real weird font! So in this version we don't end up with a redicoulous amount of hlists
+ in a horizontal extensible with is nicer when we trace. Actualy, the only extensibles that are
+ italic are integrals and these are not in traditional fonts.
+
+ We only got a warning with Lucida that has italic correction on the begin and end glyphs of
+ integrals and it looks real bad it we add that, so now we don't even warn any more and just
+ ignore it.
+*/
+
+static scaled tex_aux_stack_char_into_box(halfword box, halfword fnt, int chr, quarterword subtype, int horiziontal)
+{
+ halfword glyph = tex_aux_new_math_glyph(fnt, chr, subtype);
+ scaledwhd whd = tex_char_whd_from_glyph(glyph);
+ halfword list = box_list(box);
+ tex_attach_attribute_list_attribute(glyph, get_attribute_list(box));
+ if (horiziontal) {
+ if (list) {
+ tex_couple_nodes(tex_tail_of_node_list(list), glyph);
+ } else {
+ box_list(box) = glyph;
+ }
+ if (box_height(box) < whd.ht) {
+ box_height(box) = whd.ht;
+ }
+ if (box_depth(box) < whd.dp) {
+ box_depth(box) = whd.dp;
+ }
+ // if (whd.ic) {
+ // tex_print_message("italic correction found in horizontal delimiter parts, needs checking");
+ // }
+ return whd.wd;
+ } else {
+ halfword boxed = tex_new_null_box_node(hlist_node, math_char_list);
+ tex_attach_attribute_list_attribute(boxed, get_attribute_list(box));
+ box_width(boxed) = whd.wd;
+ box_height(boxed) = whd.ht;
+ box_depth(boxed) = whd.dp;
+ box_list(boxed) = glyph;
+ tex_try_couple_nodes(boxed, list);
+ box_list(box) = boxed;
+ // box_height(b) = box_height(boxed);
+ if (box_width(box) < whd.wd) {
+ box_width(box) = whd.wd;
+ }
+ // if (whd.ic) {
+ // tex_print_message("italic correction found in vertical delimiter parts, needs checking");
+ // }
+ return whd.ht + whd.dp;
+ }
+}
+
+static void tex_aux_stack_glue_into_box(halfword box, scaled min, scaled max) {
+ halfword glue = tex_new_glue_node(zero_glue, user_skip_glue); /* todo: subtype, correction_skip_glue? */
+ glue_amount(glue) = min;
+ glue_stretch(glue) = max - min;
+ tex_add_glue_option(glue, glue_option_no_auto_break);
+ tex_attach_attribute_list_copy(glue, box);
+ if (node_type(box) == vlist_node) {
+ tex_try_couple_nodes(glue, box_list(box));
+ box_list(box) = glue;
+ } else {
+ halfword list = box_list(box);
+ if (list) {
+ tex_couple_nodes(tex_tail_of_node_list(list), glue);
+ } else {
+ box_list(box) = glue;
+ }
+ }
+}
+
+/*tex
+
+ \TEX's most important routine for dealing with formulas is called |mlist_to_hlist|. After a
+ formula has been scanned and represented as an mlist, this routine converts it to an hlist that
+ can be placed into a box or incorporated into the text of a paragraph. The explicit parameter
+ |cur_mlist| points to the first node or noad in the given mlist (and it might be |null|). The
+ parameter |penalties| is |true| if penalty nodes for potential line breaks are to be inserted
+ into the resulting hlist, the parameter |cur_style| is a style code. After |mlist_to_hlist| has
+ acted, |vlink (temp_head)| points to the translated hlist.
+
+ Since mlists can be inside mlists, the procedure is recursive. And since this is not part of
+ \TEX's inner loop, the program has been written in a manner that stresses compactness over
+ efficiency. (This is no longer always true in \LUAMETATEX.)
+
+*/
+
+static halfword tex_aux_top_extensible_from_box(halfword e)
+{
+ if (node_type(e) == vlist_node && node_subtype(e) == math_v_extensible_list) {
+ e = box_list(e);
+ while (e) {
+ if (node_type(e) == hlist_node && box_list(e) && node_type(box_list(e)) == glyph_node) {
+ return box_list(e); /* hit is first */
+ } else {
+ e = node_next(e);
+ }
+ }
+ }
+ return null;
+}
+
+static halfword tex_aux_bottom_extensible_from_box(halfword e)
+{
+ halfword g = null;
+ if (node_type(e) == vlist_node && node_subtype(e) == math_v_extensible_list) {
+ e = box_list(e);
+ while (e) {
+ if (node_type(e) == hlist_node && box_list(e) && node_type(box_list(e)) == glyph_node) {
+ g = box_list(e); /* last so far */
+ }
+ e = node_next(e);
+ }
+ }
+ return g; /* hit is last */
+}
+
+static halfword tex_aux_get_delimiter_box(halfword fnt, halfword chr, scaled target, scaled minoverlap, int horizontal, halfword att)
+{
+ halfword size = lmt_math_state.size;
+ int callback_id = lmt_callback_defined(make_extensible_callback);
+ if (callback_id > 0) {
+ /*tex
+ This call is not optimized as it hardly makes sense to use it ... special
+ and a bit of feature creep too.
+ */
+ halfword boxed = null;
+ lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "ddddbNd->N", fnt, chr, target, minoverlap, horizontal, att, size, &boxed);
+ if (boxed) {
+ switch (node_type(boxed)) {
+ case hlist_node:
+ case vlist_node:
+ return boxed;
+ default:
+ tex_formatted_error("fonts", "invalid extensible character %i created for font %i, [h|v]list expected", chr, fnt);
+ break;
+ }
+ }
+ }
+ return tex_make_extensible(fnt, chr, target, minoverlap, horizontal, att, size);
+}
+
+halfword tex_make_extensible(halfword fnt, halfword chr, scaled target, scaled minoverlap, int horizontal, halfword att, halfword size)
+{
+ /*tex natural (maximum) size of the stack */
+ scaled max_natural = 0;
+ /*tex amount of possible shrink in the stack */
+ scaled max_shrink = 0;
+ extinfo *extensible = NULL;
+ scaled overlap;
+ /*tex a temporary counter number of extensible pieces */
+ int pieces = 0;
+ /*tex new box */
+ halfword box = tex_new_null_box_node(horizontal ? hlist_node : vlist_node, horizontal ? math_h_extensible_list : math_v_extensible_list);
+ /*tex number of times to repeat each repeatable item in |ext| */
+ int with_extenders = -1;
+ int n_of_extenders = 0;
+ int n_of_normal = 0;
+ if (minoverlap < 0) {
+ minoverlap = 0;
+ }
+ /* chr = math_char_exists(fnt, chr, math_state.size); */
+ if (horizontal) {
+ extensible = tex_char_horizontal_parts_from_font(fnt, chr);
+ } else {
+ extensible = tex_char_vertical_parts_from_font(fnt, chr);
+ }
+ tex_attach_attribute_list_attribute(box, att);
+ for (extinfo *e = extensible; e; e = e->next) {
+ if (! tex_char_exists(fnt, e->glyph)) {
+ tex_handle_error(
+ normal_error_type,
+ "Extension part doesn't exist.",
+ "Each glyph part in an extensible item should exist in the font. I will give up\n"
+ "trying to find a suitable size for now. Fix your font!"
+ );
+ tex_aux_fake_delimiter(box);
+ return box;
+ } else {
+ if (e->extender == math_extension_repeat) {
+ n_of_extenders++;
+ } else {
+ n_of_normal++;
+ }
+ /*tex
+ No negative overlaps or advances are allowed. Watch out, we patch the glyph data at
+ the \TEX\ end here.
+ */
+ if (e->start_overlap < 0 || e->end_overlap < 0 || e->advance < 0) {
+ tex_handle_error(
+ normal_error_type,
+ "Extensible recipe has negative fields.",
+ "All measurements in extensible items should be positive. To get around this\n"
+ "problem, I have changed the font metrics. Fix your font!"
+ );
+ if (e->start_overlap < 0) {
+ e->start_overlap = 0;
+ }
+ if (e->end_overlap < 0) {
+ e->end_overlap = 0;
+ }
+ if (e->advance < 0) {
+ e->advance = 0;
+ }
+ }
+ }
+ }
+ if (n_of_normal == 0) {
+ tex_handle_error(
+ normal_error_type,
+ "Extensible recipe has no fixed parts.",
+ "Each extensible recipe should have at least one non-repeatable part. To get\n"
+ "around this problem, I have changed the first part to be non-repeatable. Fix your\n"
+ "font!"
+ );
+ if (extensible) { /* get rid of warning */
+ extensible->extender = 0;
+ }
+ n_of_normal = 1;
+ n_of_extenders--;
+ }
+ /*tex
+
+ In the meantime the Microsoft Typography website has a good description of the process:
+
+ \startitemize
+ \startitem
+ Assemble all parts with all extenders removed and with connections overlapping by
+ the maximum amount. This gives the smallest possible result.
+ \stopitem
+ \startitem
+ Determine how much extra width/height can be obtained from all existing connections
+ between neighboring parts by using minimal overlaps. If that is enough to achieve
+ the size goal, extend each connection equally by changing overlaps of connectors to
+ finish the job.
+ \stopitem
+ \startitem
+ If all connections have been extended to the minimum overlap and further growth is
+ needed, add one of each extender, and repeat the process from the first step.
+ \stopitem
+ \stopitemize
+
+ Original comment: |ext| holds a linked list of numerous items that may or may not be
+ repeatable. For the total height, we have to figure out how many items are needed to create
+ a stack of at least |v|. The next |while| loop does that. It has two goals: it finds out
+ the natural height |b_max| of the all the parts needed to reach at least |v|, and it sets
+ |with_extenders| to the number of times each of the repeatable items in |ext| has to be
+ repeated to reach that height.
+
+ It's an example figure it out once, write the solution, test it well and then never look
+ back code.
+ */
+ while (max_natural < target && n_of_extenders > 0) {
+ overlap = 0;
+ max_natural = 0;
+ with_extenders++;
+ if (horizontal) {
+ for (extinfo *e = extensible; e; e = e->next) {
+ if (e->extender == 0) {
+ scaled initial = tex_aux_math_x_size_scaled(fnt, e->start_overlap, size);
+ scaled advance = tex_aux_math_x_size_scaled(fnt, e->advance, size);
+ if (minoverlap < initial) {
+ initial = minoverlap;
+ }
+ if (overlap < initial) {
+ initial = overlap;
+ }
+ if (advance == 0) {
+ /*tex for tfm fonts (so no need for scaling) */
+ advance = tex_aux_math_x_size_scaled(fnt, tex_char_width_from_font(fnt, e->glyph), size); /* todo: combine */
+ if (advance <= 0) {
+ tex_formatted_error("fonts", "bad horizontal extensible character %i in font %i", chr, fnt);
+ }
+ }
+ max_natural += advance - initial;
+ overlap = tex_aux_math_x_size_scaled(fnt, e->end_overlap, size);
+ } else {
+ pieces = with_extenders;
+ while (pieces > 0) {
+ scaled initial = tex_aux_math_x_size_scaled(fnt, e->start_overlap, size);
+ scaled advance = tex_aux_math_x_size_scaled(fnt, e->advance, size);
+ if (minoverlap < initial) {
+ initial = minoverlap;
+ }
+ if (overlap < initial) {
+ initial = overlap;
+ }
+ if (advance == 0) {
+ /*tex for tfm fonts (so no need for scaling) */
+ advance = tex_aux_math_x_size_scaled(fnt, tex_char_width_from_font(fnt, e->glyph), size); /* todo: combine */
+ if (advance <= 0) {
+ tex_formatted_error("fonts", "bad horizontal extensible character %i in font %i", chr, fnt);
+ }
+ }
+ max_natural += advance - initial;
+ overlap = tex_aux_math_x_size_scaled(fnt, e->end_overlap, size);
+ pieces--;
+ }
+ }
+ }
+ } else {
+ for (extinfo *e = extensible; e; e = e->next) {
+ if (e->extender == 0) {
+ scaled initial = tex_aux_math_y_size_scaled(fnt, e->start_overlap, size);
+ scaled advance = tex_aux_math_y_size_scaled(fnt, e->advance, size);
+ if (minoverlap < initial) {
+ initial = minoverlap;
+ }
+ if (overlap < initial) {
+ initial = overlap;
+ }
+ if (advance == 0) {
+ /*tex for tfm fonts (so no need for scaling) */
+ advance = tex_aux_math_y_size_scaled(fnt, tex_char_total_from_font(fnt, e->glyph), size); /* todo: combine */
+ if (advance <= 0) {
+ tex_formatted_error("fonts", "bad vertical extensible character %i in font %i", chr, fnt);
+ }
+ }
+ max_natural += advance - initial;
+ overlap = tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
+ } else {
+ pieces = with_extenders;
+ while (pieces > 0) {
+ scaled initial = tex_aux_math_y_size_scaled(fnt, e->start_overlap, size);
+ scaled advance = tex_aux_math_y_size_scaled(fnt, e->advance, size);
+ if (minoverlap < initial) {
+ initial = minoverlap;
+ }
+ if (overlap < initial) {
+ initial = overlap;
+ }
+ if (advance == 0) {
+ /*tex for tfm fonts (so no need for scaling) */
+ advance = tex_aux_math_y_size_scaled(fnt, tex_char_total_from_font(fnt, e->glyph), size); /* todo: combine */
+ if (advance <= 0) {
+ tex_formatted_error("fonts", "bad vertical extensible character %i in font %i", chr, fnt);
+ }
+ }
+ max_natural += advance - initial;
+ overlap = tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
+ pieces--;
+ }
+ }
+ }
+ }
+ }
+ /*tex
+ Assemble box using |with_extenders| copies of each extender, with appropriate glue wherever
+ an overlap occurs.
+ */
+ overlap = 0;
+ max_natural = 0;
+ max_shrink = 0;
+ for (extinfo *e = extensible; e; e = e->next) {
+ if (e->extender == 0) {
+ scaled progress;
+ scaled initial = horizontal ? tex_aux_math_x_size_scaled(fnt, e->start_overlap, size) : tex_aux_math_y_size_scaled(fnt,e->start_overlap, size);
+ if (overlap < initial) {
+ initial = overlap;
+ }
+ progress = initial;
+ if (minoverlap < initial) {
+ initial = minoverlap;
+ }
+ if (progress > 0) {
+ tex_aux_stack_glue_into_box(box, -progress, -initial);
+ max_shrink += (-initial) - (-progress);
+ max_natural -= progress;
+ }
+ max_natural += tex_aux_stack_char_into_box(box, fnt, e->glyph, glyph_math_extensible_subtype, horizontal);
+ overlap = horizontal ? tex_aux_math_x_size_scaled(fnt, e->end_overlap, size) : tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
+ pieces--;
+ } else {
+ pieces = with_extenders;
+ while (pieces > 0) {
+ scaled progress;
+ scaled initial = horizontal ? tex_aux_math_x_size_scaled(fnt, e->start_overlap, size) : tex_aux_math_y_size_scaled(fnt, e->start_overlap, size);
+ if (overlap < initial) {
+ initial = overlap;
+ }
+ progress = initial;
+ if (minoverlap < initial) {
+ initial = minoverlap;
+ }
+ if (progress > 0) {
+ tex_aux_stack_glue_into_box(box, -progress, -initial);
+ max_shrink += (-initial) - (-progress);
+ max_natural -= progress;
+ }
+ max_natural += tex_aux_stack_char_into_box(box, fnt, e->glyph, glyph_math_extensible_subtype, horizontal);
+ overlap = horizontal ? tex_aux_math_x_size_scaled(fnt, e->end_overlap, size) : tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
+ pieces--;
+ }
+ }
+ }
+ /*tex Set glue so as to stretch the connections if needed. */
+ if (target > max_natural && max_shrink > 0) {
+ scaled delta = target - max_natural;
+ /*tex Don't stretch more than |s_max|. */
+ if (delta > max_shrink) {
+ delta = max_shrink;
+ }
+ box_glue_order(box) = normal_glue_order;
+ box_glue_sign(box) = stretching_glue_sign;
+ box_glue_set(box) = (glueratio) (delta / (glueratio) max_shrink);
+ max_natural += delta;
+ }
+ if (horizontal) {
+ box_width(box) = max_natural;
+ node_subtype(box) = math_h_extensible_list;
+ } else {
+ box_height(box) = max_natural;
+ node_subtype(box) = math_v_extensible_list;
+ }
+ return box;
+}
+
+/*tex
+
+ The |var_delimiter| function, which finds or constructs a sufficiently large delimiter, is the
+ most interesting of the auxiliary functions that currently concern us. Given a pointer |d| to a
+ delimiter field in some noad, together with a size code |s| and a vertical distance |v|, this
+ function returns a pointer to a box that contains the smallest variant of |d| whose height plus
+ depth is |v| or more. (And if no variant is large enough, it returns the largest available
+ variant.) In particular, this routine will construct arbitrarily large delimiters from
+ extensible components, if |d| leads to such characters.
+
+ The value returned is a box whose |shift_amount| has been set so that the box is vertically
+ centered with respect to the axis in the given size. If a built-up symbol is returned, the
+ height of the box before shifting will be the height of its topmost component.
+
+*/
+
+static halfword register_extensible(halfword fnt, halfword chr, int size, halfword result, halfword att)
+{
+ int callback_id = lmt_callback_defined(register_extensible_callback);
+ if (callback_id > 0) {
+ halfword b = null;
+ lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dddN->N", fnt, chr, size, result, &b);
+ if (b) {
+ switch (node_type(b)) {
+ case hlist_node:
+ case vlist_node:
+ tex_attach_attribute_list_attribute(b, att);
+ return b;
+ default:
+ tex_formatted_error("fonts", "invalid extensible character %U registered for font %F, [h|v]list expected", chr, fnt);
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/*tex
+ A first version passed the first and last glyph around but then we need to maintain a copy because
+ we can register a composed delimiter which can result in a flush of these nodes.
+*/
+
+static halfword tex_aux_make_delimiter(halfword target, halfword delimiter, int size, scaled targetsize, int flat, int style, int shift, int *stack, scaled *delta, scaled tolerance, int nooverflow, delimiterextremes *extremes, scaled move)
+{
+ /*tex the box that will be constructed */
+ halfword result = null;
+ /*tex best-so-far and tentative font codes */
+ halfword fnt = null_font;
+ /*tex best-so-far and tentative character codes */
+ int chr = 0;
+ int nxtchr = 0;
+ /*tex are we trying the large variant? */
+ int large_attempt = 0;
+ int do_parts = 0;
+ /*tex to save the current attribute list */
+ halfword att = null;
+ if (extremes) {
+ extremes->tfont = null_font;
+ extremes->bfont = null_font;
+ extremes->tchar = 0;
+ extremes->bchar = 0;
+ extremes->height = 0;
+ extremes->depth = 0;
+ }
+ if (delimiter && ! delimiter_small_family(delimiter) && ! delimiter_small_character(delimiter)
+ && ! delimiter_large_family(delimiter) && ! delimiter_large_character(delimiter)) {
+ halfword result = tex_new_null_box_node(hlist_node, math_v_delimiter_list);
+ tex_attach_attribute_list_copy(result, delimiter);
+ if (! flat) {
+ tex_aux_fake_delimiter(result);
+ }
+ tex_flush_node(delimiter); /* no, we can assign later on ... better a fatal error here */
+ return result;
+ }
+ if (delimiter) {
+ /*tex largest height-plus-depth so far */
+ scaled besttarget = 0;
+ /*tex |z| runs through font family members */
+ int curfam = delimiter_small_family(delimiter);
+ int curchr = 0;
+ int count = 0;
+ int prvfnt = null_font;
+ int prvchr = 0;
+ nxtchr = delimiter_small_character(delimiter);
+ while (1) {
+ /*tex
+ The search process is complicated slightly by the facts that some of the characters
+ might not be present in some of the fonts, and they might not be probed in increasing
+ order of height. When we run out of sizes (variants) and end up at an extensible
+ pointer (parts) we quit the loop.
+ */
+ if (curfam || nxtchr) {
+ halfword curfnt = tex_fam_fnt(curfam, size);
+ if (curfnt != null_font) {
+ curchr = nxtchr;
+ CONTINUE:
+ count++;
+ if (tex_char_exists(curfnt, curchr)) {
+ scaled total = flat ? tex_aux_math_x_size_scaled(curfnt, tex_char_width_from_font(curfnt, curchr), size): tex_aux_math_y_size_scaled(curfnt, tex_char_total_from_font(curfnt, curchr), size);
+ if (nooverflow && total >= targetsize) {
+ if (total > targetsize && prvfnt != null_font) {
+ fnt = prvfnt;
+ chr = prvchr;
+ } else {
+ fnt = curfnt;
+ chr = curchr;
+ }
+ besttarget = total;
+ goto FOUND;
+ } else if (total >= besttarget) {
+ prvfnt = curfnt;
+ prvchr = curchr;
+ fnt = curfnt;
+ chr = curchr;
+ besttarget = total;
+ if (total >= (targetsize - tolerance)) {
+ goto FOUND;
+ }
+ }
+ if (tex_char_has_tag_from_font(curfnt, curchr, extension_tag)) {
+ fnt = curfnt;
+ chr = curchr;
+ do_parts = 1;
+ goto FOUND;
+ } else if (count > 1000) {
+ tex_formatted_warning("fonts", "endless loop in extensible character %U of font %F", curchr, curfnt);
+ goto FOUND;
+ } else if (tex_char_has_tag_from_font(curfnt, curchr, list_tag)) {
+ prvfnt = curfnt;
+ prvchr = curchr;
+ curchr = tex_char_remainder_from_font(curfnt, curchr);
+ goto CONTINUE;
+ }
+ }
+ }
+ }
+ if (large_attempt) {
+ /*tex There were none large enough. */
+ goto FOUND;
+ } else {
+ large_attempt = 1;
+ curfam = delimiter_large_family(delimiter);
+ nxtchr = delimiter_large_character(delimiter);
+ }
+ }
+ }
+ FOUND:
+ if (delimiter) {
+ /*tex
+ The builder below sets the list if needed and we dereference later because otherwise
+ the list gets flushed before it can be reused.
+ */
+ att = get_attribute_list(delimiter);
+ wipe_attribute_list_only(delimiter);
+ tex_flush_node(delimiter);
+ }
+ if (fnt != null_font) {
+ /*tex
+ When the following code is executed, |do_parts| will be true if a built-up symbol is
+ supposed to be returned.
+ */
+ extinfo *ext = NULL;
+ if (do_parts) {
+ /* tex_char_process(fnt, chr); */ /* in case we realloc */
+ ext = flat ? tex_char_horizontal_parts_from_font(fnt, chr) : tex_char_vertical_parts_from_font(fnt, chr);
+ }
+ if (ext) {
+ scaled minoverlap = flat ? tex_get_math_x_parameter_default(style, math_parameter_connector_overlap_min, 0) : tex_get_math_y_parameter_default(style, math_parameter_connector_overlap_min, 0);;
+ result = tex_aux_get_delimiter_box(fnt, chr, targetsize, minoverlap, flat, att);
+ if (delta) {
+ if (tex_aux_math_engine_control(fnt, math_control_apply_vertical_italic_kern)) {
+ *delta = tex_aux_math_x_size_scaled(fnt, tex_char_vertical_italic_from_font(fnt, nxtchr), size);
+ } else {
+ *delta = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, nxtchr), size);
+ }
+ }
+ if (stack) {
+ *stack = 1 ;
+ }
+ if (! flat && extremes) {
+ halfword first = tex_aux_top_extensible_from_box(result);
+ halfword last = tex_aux_bottom_extensible_from_box(result);
+ extremes->tfont = glyph_font(first);
+ extremes->tchar = glyph_character(first);
+ extremes->bfont = glyph_font(last);
+ extremes->bchar = glyph_character(last);
+ extremes->height = box_height(result);
+ extremes->depth = box_depth(result);
+ }
+ } else {
+ /*tex
+ Here italic is added to width in traditional fonts which makes the delimiter get
+ the real width. An \OPENTYPE\ font already has the right width. There is one case
+ where |delta| (ic) gets subtracted but only for a traditional font. In that case
+ the traditional width (which is fake width + italic) becomes less and the delta is
+ added. See (**).
+ */
+ result = tex_aux_char_box(fnt, chr, att, delta, glyph_math_delimiter_subtype, flat ? targetsize : 0, style);
+ if (stack) {
+ *stack = 0 ;
+ }
+ if (! flat && extremes) {
+ extremes->tfont = fnt;
+ extremes->tchar = chr;
+ extremes->bfont = fnt;
+ extremes->bchar = chr;
+ extremes->height = box_height(result);
+ extremes->depth = box_depth(result);
+ }
+ }
+ } else {
+ /*tex This can be an empty one as is often the case with fractions! */
+ result = tex_new_null_box_node(hlist_node, flat ? math_h_delimiter_list : math_v_delimiter_list);
+ tex_attach_attribute_list_attribute(result, att);
+ /*tex Use this width if no delimiter was found. */
+ if (! flat) {
+ tex_aux_fake_delimiter(result);
+ }
+ if (delta) {
+ *delta = 0;
+ }
+ if (stack) {
+ *stack = 0 ;
+ }
+ }
+ if (do_parts) {
+ if (has_noad_option_phantom(target) || has_noad_option_void(target)) {
+ result = tex_aux_make_list_phantom(result, has_noad_option_void(target), att);
+ } else {
+ result = register_extensible(fnt, chr, size, result, att);
+ }
+ }
+ if (! flat) {
+ /*tex A vertical variant. Todo: add a kern instead. */
+ switch (shift) {
+ case 0:
+ box_shift_amount(result) = tex_half_scaled(box_height(result) - box_depth(result));
+ break;
+ case 1:
+ box_shift_amount(result) = tex_half_scaled(box_height(result) - box_depth(result));
+ box_shift_amount(result) -= tex_aux_math_axis(size);
+ break;
+ case 2:
+ box_shift_amount(result) = move;
+ break;
+ }
+ if (do_parts && extremes && extremes->height) {
+ extremes->height -= box_shift_amount(result);
+ extremes->depth += box_shift_amount(result);
+ }
+ }
+ /* This needs checking in case the ref was changed. */
+ delete_attribute_reference(att);
+ if ((node_type(result) == hlist_node || node_type(result) == vlist_node) && node_subtype(result) == unknown_list) {
+ node_subtype(result) = flat ? math_h_delimiter_list : math_v_delimiter_list;
+ }
+ return result;
+}
+
+/*tex
+
+ The next subroutine is much simpler; it is used for numerators and denominators of fractions as
+ well as for displayed operators and their limits above and below. It takes a given box~|b| and
+ changes it so that the new box is centered in a box of width~|w|. The centering is done by
+ putting |\hss| glue at the left and right of the list inside |b|, then packaging the new box;
+ thus, the actual box might not really be centered, if it already contains infinite glue.
+
+ The given box might contain a single character whose italic correction has been added to the
+ width of the box; in this case a compensating kern is inserted. Actually, we now check for
+ the last glyph.
+
+*/
+
+static halfword tex_aux_rebox(halfword box, scaled width, halfword size)
+{
+ (void) size;
+ if (box_width(box) != width && box_list(box)) {
+ /*tex temporary registers for list manipulation */
+ halfword head = box_list(box);
+ quarterword subtype = node_subtype(box);
+ halfword att = get_attribute_list(box);
+ /*tex When the next two are not seen we can wipe att so we reserve by bump! */
+ add_attribute_reference(att);
+ if (node_type(box) == vlist_node) {
+ box = tex_hpack(box, 0, packing_additional, direction_unknown, holding_none_option);
+ node_subtype(box) = subtype;
+ tex_attach_attribute_list_attribute(box, att);
+ head = box_list(box);
+ } else if (head && node_type(head) == glyph_node && ! node_next(head)) {
+ /*tex
+ This hack is for traditional fonts so with a proper opentype font we don't end up
+ here (because then the width is unchanged). However controls can cheat so there is
+ no explicit check for an opentype situation here.
+ */
+ if (tex_aux_math_engine_control(glyph_font(head), math_control_rebox_char_italic_kern)) {
+ scaled boxwidth = box_width(box);
+ scaled chrwidth = tex_char_width_from_glyph(head);
+ if (boxwidth != chrwidth) {
+ /*tex
+ This is typical old font stuff. Maybe first check if we can just
+ remove a trailing kern. Also, why not just adapt the box width.
+ */
+ halfword kern = tex_new_kern_node(boxwidth - chrwidth, italic_kern_subtype); /* horizontal_math_kern */
+ tex_attach_attribute_list_attribute(kern, att);
+ tex_couple_nodes(head, kern);
+ }
+ }
+ }
+ box_list(box) = null;
+ tex_flush_node(box);
+ {
+ halfword left = tex_new_glue_node(filll_glue, user_skip_glue); /* todo: subtype, correction_skip_glue? */
+ halfword right = tex_new_glue_node(filll_glue, user_skip_glue); /* todo: subtype, correction_skip_glue? */
+ tex_add_glue_option(left, glue_option_no_auto_break);
+ tex_add_glue_option(right, glue_option_no_auto_break);
+ tex_attach_attribute_list_attribute(left, att);
+ tex_attach_attribute_list_attribute(right, att);
+ tex_couple_nodes(left, head);
+ tex_couple_nodes(tex_tail_of_node_list(head), right);
+ box = tex_hpack(left, width, packing_exactly, direction_unknown, holding_none_option);
+ tex_attach_attribute_list_attribute(box, att);
+ node_subtype(box) = subtype;
+ }
+ /*tex As we bumped we now need to unbump the ref counter! */
+ delete_attribute_reference(att);
+ } else {
+ box_width(box) = width;
+ }
+ return box;
+}
+
+/*tex
+
+ Here is a subroutine that creates a new glue specification from another one that is expressed
+ in |mu|, given the value of the math unit.
+
+*/
+
+inline static scaled tex_aux_mu_mult(scaled a, scaled n, scaled f)
+{
+ return tex_multiply_and_add(n, a, tex_xn_over_d(a, f, unity), max_dimen);
+}
+
+inline static void tex_aux_calculate_glue(scaled m, scaled *f, scaled *n)
+{
+ /*tex fraction part of |m| */
+ *f = 0;
+ /*tex integer part of |m| */
+ *n = tex_x_over_n_r(m, unity, f);
+ /*tex the new glue specification */
+ if (f < 0) {
+ --n;
+ f += unity;
+ }
+}
+
+static halfword tex_aux_math_muglue(halfword g, quarterword subtype, scaled m, halfword detail, int style)
+{
+ scaled f, n;
+ halfword glue = tex_new_node(glue_node, subtype);
+ tex_aux_calculate_glue(m, &f, &n);
+ /* convert |mu| to |pt| */
+ glue_amount(glue) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_amount(g), style), n, f);
+ if (math_glue_stretch_enabled) {
+ scaled stretch = tex_aux_math_x_scaled(glue_stretch(g), style);
+ glue_stretch_order(glue) = glue_stretch_order(g);
+ glue_stretch(glue) = (glue_stretch_order(glue) == normal_glue_order) ? tex_aux_mu_mult(stretch, n, f) : stretch;
+ }
+ if (math_glue_shrink_enabled) {
+ scaled shrink = tex_aux_math_x_scaled(glue_shrink(g), style);
+ glue_shrink_order(glue) = glue_shrink_order(g);
+ glue_shrink(glue) = (glue_shrink_order(glue) == normal_glue_order) ? tex_aux_mu_mult(shrink, n, f) : shrink;
+ }
+ glue_font(glue) = detail;
+ tex_add_glue_option(glue, glue_option_no_auto_break);
+ return glue;
+}
+
+static halfword tex_aux_math_glue(halfword g, quarterword subtype, halfword detail)
+{
+ halfword glue = tex_new_glue_node(g, subtype);
+ if (! math_glue_stretch_enabled) {
+ glue_stretch_order(glue) = 0;
+ glue_stretch(glue) = 0;
+ }
+ if (! math_glue_shrink_enabled) {
+ glue_shrink_order(glue) = 0;
+ glue_shrink(glue) = 0;
+ }
+ glue_font(glue) = detail;
+ tex_add_glue_option(glue, glue_option_no_auto_break);
+ return glue;
+}
+
+static halfword tex_aux_math_dimen(halfword g, quarterword subtype, halfword detail)
+{
+ halfword glue = tex_new_glue_node(null, subtype);
+ glue_amount(glue) = g;
+ glue_font(glue) = detail;
+ tex_add_glue_option(glue, glue_option_no_auto_break);
+ return glue;
+}
+
+static void tex_aux_math_glue_to_glue(halfword p, scaled m, int style)
+{
+ scaled f, n;
+ tex_aux_calculate_glue(m, &f, &n);
+ /*tex convert |mu| to |pt| */
+ glue_amount(p) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_amount(p), style), n, f);
+ if (! math_glue_stretch_enabled) {
+ glue_stretch_order(p) = 0;
+ glue_stretch(p) = 0;
+ } else if (glue_stretch_order(p) == normal_glue_order) {
+ glue_stretch(p) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_stretch(p), style), n, f);
+ }
+ if (! math_glue_shrink_enabled) {
+ glue_shrink_order(p) = 0;
+ glue_shrink(p) = 0;
+ } else if (glue_shrink_order(p) == normal_glue_order) {
+ glue_shrink(p) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_shrink(p), style), n, f);
+ }
+ /*tex Okay, we could have had a special subtype but we're stuck with this now. */
+ node_subtype(p) = inter_math_skip_glue;
+ tex_add_glue_option(p, glue_option_no_auto_break);
+}
+
+/*tex
+
+ The |math_kern| subroutine removes |mu_glue| from a kern node, given the value of the math
+ unit.
+
+*/
+
+static void tex_aux_make_kern(halfword current, scaled mu, int style)
+{
+ if (node_subtype(current) == explicit_math_kern_subtype) {
+ scaled f, n;
+ tex_aux_calculate_glue(mu, &f, &n);
+ kern_amount(current) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_amount(current), style), n, f);
+ node_subtype(current) = explicit_kern_subtype;
+ }
+}
+
+/*tex
+
+ Conditional math glue (|\nonscript|) results in a |glue_node| pointing to |zero_glue|, with
+ |subtype(q)=cond_math_glue|; in such a case the node following will be eliminated if it is a
+ glue or kern node and if the current size is different from |text_size|.
+
+ Unconditional math glue (|\muskip|) is converted to normal glue by multiplying the dimensions
+ by |current_mu|.
+
+*/
+
+static void tex_aux_make_glue(halfword current, scaled mu, int style)
+{
+ switch (node_subtype(current)) {
+ case mu_glue:
+ tex_aux_math_glue_to_glue(current, mu, style);
+ break;
+ case conditional_math_glue:
+ if (lmt_math_state.size != text_size) {
+ halfword p = node_next(current);
+ if (p) {
+ switch (node_type(p)) {
+ case glue_node:
+ case kern_node:
+ if (node_next(p)) {
+ tex_couple_nodes(current, node_next(p));
+ node_next(p) = null;
+ } else {
+ node_next(current) = null;
+ }
+ tex_flush_node_list(p);
+ break;
+ }
+ }
+ }
+ break;
+ case rulebased_math_glue:
+ break;
+ }
+}
+
+/*tex
+
+ The |mlist_to_hlist| operation is actually called a lot when we have a math intense document,
+ because it is also called nested. Here we have the main runner, called in the main loop;
+ watch the callback.
+
+*/
+
+inline static int tex_aux_is_math_penalty(halfword n)
+{
+ return node_type(n) == penalty_node && (node_subtype(n) == math_pre_penalty_subtype || node_subtype(n) == math_post_penalty_subtype);
+}
+
+void tex_run_mlist_to_hlist(halfword mlist, halfword penalties, halfword style, int beginclass, int endclass)
+{
+ if (mlist) {
+ int saved_level = lmt_math_state.level;
+ int callback_id = lmt_callback_defined(mlist_to_hlist_callback);
+ lmt_math_state.level = 0;
+ if (! valid_math_class_code(beginclass)) {
+ beginclass = unset_noad_class;
+ }
+ if (! valid_math_class_code(endclass)) {
+ endclass = unset_noad_class;
+ }
+ math_begin_class_par = unset_noad_class;
+ math_end_class_par = unset_noad_class;
+ /* not on the stack ... yet */
+ if (tracing_math_par >= 1) {
+ tex_begin_diagnostic();
+ switch (style) {
+ case display_style:
+ tex_print_str("> \\displaymath=");
+ break;
+ case text_style:
+ tex_print_str("> \\inlinemath=");
+ break;
+ default:
+ tex_print_str("> \\math=");
+ break;
+ }
+ tex_show_box(mlist);
+ tex_end_diagnostic();
+ }
+ tex_finalize_math_parameters();
+ if (callback_id > 0) {
+ lua_State *L = lmt_lua_state.lua_instance;
+ int top = 0;
+ if (lmt_callback_okay(L, callback_id, &top)) {
+ int i;
+ node_prev(mlist) = null ;
+ lmt_node_list_to_lua(L, mlist);
+ lmt_push_math_style_name(L, style);
+ lua_pushboolean(L, penalties);
+ lua_pushinteger(L, beginclass);
+ lua_pushinteger(L, endclass);
+ lua_pushinteger(L, lmt_math_state.level);
+ i = lmt_callback_call(L, 6, 1, top);
+ if (i) {
+ lmt_callback_error(L, top, i);
+ node_next(temp_head) = null;
+ } else {
+ halfword a = lmt_node_list_from_lua(L, -1);
+ /* node_prev(node_next(a)) = null; */
+ node_next(temp_head) = a;
+ lmt_callback_wrapup(L, top);
+ }
+ } else {
+ node_next(temp_head) = null;
+ }
+ } else if (callback_id == 0) {
+ node_next(temp_head) = tex_mlist_to_hlist(mlist, penalties, style, beginclass, endclass, NULL);
+ } else {
+ node_next(temp_head) = null;
+ }
+ if (penalties) { // && tex_in_main_math_style(style)
+ /*tex This makes no sense in display math not in script styles. */
+ switch (style) {
+ case text_style:
+ case cramped_text_style:
+ if (math_forward_penalties_par) {
+ halfword n = tex_get_specification_count(math_forward_penalties_par);
+ if (n > 0) {
+ halfword h = node_next(temp_head);
+ halfword i = 1;
+ while (h && i <= n) {
+ if (tex_aux_is_math_penalty(h)) {
+ penalty_amount(h) += tex_get_specification_penalty(math_forward_penalties_par, i);
+ ++i;
+ }
+ h = node_next(h);
+ }
+ }
+ }
+ if (math_backward_penalties_par) {
+ halfword n = tex_get_specification_count(math_backward_penalties_par);
+ if (n > 0) {
+ halfword t = tex_tail_of_node_list(node_next(temp_head));
+ halfword i = 1;
+ while (t && i <= n) {
+ if (tex_aux_is_math_penalty(t)) {
+ penalty_amount(t) += tex_get_specification_penalty(math_backward_penalties_par, i);
+ ++i;
+ }
+ t = node_prev(t);
+ }
+ }
+ }
+ break;
+ }
+ if (node_next(temp_head) && math_threshold_par) {
+ scaledwhd siz = tex_natural_hsizes(node_next(temp_head), null, 0.0, 0, 0);
+ if (siz.wd < glue_amount(math_threshold_par)) {
+ halfword box = tex_new_node(hlist_node, unknown_list);
+ tex_attach_attribute_list_copy(box, node_next(temp_head));
+ box_width(box) = siz.wd;
+ box_height(box) = siz.ht;
+ box_depth(box) = siz.dp;
+ box_list(box) = node_next(temp_head);
+ node_next(temp_head) = box;
+ if (glue_stretch(math_threshold_par) || glue_shrink(math_threshold_par)) {
+ halfword glue = tex_new_glue_node(math_threshold_par, u_leaders);
+ tex_add_glue_option(glue, glue_option_no_auto_break);
+ tex_attach_attribute_list_copy(glue, box);
+ glue_amount(glue) = siz.wd;
+ glue_leader_ptr(glue) = box;
+ node_next(temp_head) = glue;
+ } else {
+ node_next(temp_head) = box;
+ }
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: boxing inline, threshold %D, width %D, height %D, depth %D]",
+ glue_amount(math_threshold_par), pt_unit, // todo: stretch and shrink
+ siz.wd, pt_unit, siz.ht, pt_unit, siz.dp, pt_unit
+ );
+ tex_end_diagnostic();
+ }
+ }
+ }
+ /*
+ At the outer level we check for discretionaries. Maybe only when we are in text or display?
+ */
+ {
+ halfword current = temp_head;
+ while (current) {
+ /*tex Maybe |math_discretionary_code| but I need to check the impact on \CONTEXT\ first. */
+ if (node_type(current) == glyph_node && tex_has_glyph_option(current, glyph_option_math_discretionary)) {
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: promoting glyph with character %U to discretionary]", glyph_character(current));
+ tex_end_diagnostic();
+ }
+ current = tex_glyph_to_discretionary(current, mathematics_discretionary_code, tex_has_glyph_option(current, glyph_option_math_italics_too));
+ }
+ current = node_next(current);
+ }
+ }
+ }
+ lmt_math_state.level = saved_level;
+ } else {
+ node_next(temp_head) = null;
+ }
+}
+
+/*tex
+
+ The recursion in |mlist_to_hlist| is due primarily to a subroutine called |clean_box| that puts
+ a given noad field into a box using a given math style; |mlist_to_hlist| can call |clean_box|,
+ which can call |mlist_to_hlist|.
+
+ The box returned by |clean_box| is clean in the sense that its |shift_amount| is zero.
+
+*/
+
+inline static void tex_aux_remove_italic_after_first_glyph(halfword box)
+{
+ halfword list = box_list(box);
+ if (list && node_type(list) == glyph_node) {
+ halfword next = node_next(list);
+ /*todo: check for italic property */
+ if (next && ! node_next(next) && node_type(next) == kern_node && node_subtype(next) == italic_kern_subtype) {
+ /*tex Unneeded italic correction. */
+ box_width(box) -= kern_amount(next);
+ tex_flush_node(next);
+ node_next(list) = null;
+ }
+ }
+}
+
+static halfword tex_aux_clean_box(halfword n, int main_style, int style, quarterword subtype, int keepitalic, kernset *kerns)
+{
+ /*tex beginning of a list to be boxed */
+ halfword list;
+ /*tex box to be returned */
+ halfword result;
+ /*tex beginning of mlist to be translated */
+ halfword mlist = null;
+ switch (node_type(n)) {
+ case math_char_node:
+ mlist = tex_new_node(simple_noad, ordinary_noad_subtype);
+ noad_nucleus(mlist) = tex_aux_math_clone(n);
+ tex_attach_attribute_list_copy(mlist, n);
+ break;
+ case sub_box_node:
+ list = kernel_math_list(n);
+ goto FOUND;
+ case sub_mlist_node:
+ mlist = kernel_math_list(n);
+ break;
+ default:
+ list = tex_new_null_box_node(hlist_node, math_list_list);
+ tex_attach_attribute_list_copy(list, n);
+ goto FOUND;
+ }
+ /*tex This might add some italic correction. */
+ list = tex_mlist_to_hlist(mlist, 0, main_style, unset_noad_class, unset_noad_class, kerns);
+ /*tex recursive call */
+ tex_aux_set_current_math_size(style); /* persists after call */
+ FOUND:
+ if (! list || node_type(list) == glyph_node) {
+ result = tex_hpack(list, 0, packing_additional, direction_unknown, holding_none_option);
+ tex_attach_attribute_list_copy(result, list);
+ } else if (! node_next(list) && (node_type(list) == hlist_node || node_type(list) == vlist_node) && (box_shift_amount(list) == 0)) {
+ /*tex It's already clean. */
+ result = list;
+ } else {
+ result = tex_hpack(list, 0, packing_additional, direction_unknown, holding_none_option);
+ tex_attach_attribute_list_copy(result, list);
+ }
+ node_subtype(result) = subtype;
+ if (! keepitalic) {
+ tex_aux_remove_italic_after_first_glyph(result);
+ }
+ return result;
+}
+
+/*tex
+
+ It is convenient to have a procedure that converts a |math_char| field to an unpacked form. The
+ |fetch| routine sets |cur_f| and |cur_c| to the font code and character code of a given noad
+ field. It also takes care of issuing error messages for nonexistent characters; in such cases,
+ |char_exists (cur_f, cur_c)| will be |false| after |fetch| has acted, and the field will also
+ have been reset to |null|. The outputs of |fetch| are placed in global variables so that we can
+ access them any time we want. We add a bit more detail about the location of the issue than
+ standard \TEX\ does.
+
+ The |cur_f| and |cur_c| variables are now locals and we keep the (opentype) state otherwise.
+
+*/
+
+static int tex_aux_fetch(halfword n, const char *where, halfword *f, halfword *c) /* todo: also pass size */
+{
+ if (node_type(n) == glyph_node) {
+ *f = glyph_font(n);
+ *c = glyph_character(n);
+ /* lmt_math_state.opentype = tex_aux_has_opentype_metrics(*f); */
+ if (tex_char_exists(*f, *c)) {
+ return 1;
+ } else {
+ tex_char_warning(*f, *c);
+ return 0;
+ }
+ } else {
+ *f = tex_fam_fnt(kernel_math_family(n), lmt_math_state.size);
+ *c = kernel_math_character(n);
+ if (*f == null_font) {
+ char msg[256];
+ snprintf(msg, 255, "\\%s%d is undefined in %s, font id %d, character %d)",
+ tex_aux_math_size_string(lmt_math_state.size), kernel_math_family(n), where, *f, *c
+ );
+ tex_handle_error(
+ normal_error_type,
+ msg,
+ "Somewhere in the math formula just ended, you used the stated character from an\n"
+ "undefined font family. For example, plain TeX doesn't allow \\it or \\sl in\n"
+ "subscripts. Proceed, and I'll try to forget that I needed that character."
+ );
+ return 0;
+ } else {
+ /* lmt_math_state.opentype = tex_aux_has_opentype_metrics(*f); */
+ if (tex_math_char_exists(*f, *c, lmt_math_state.size)) {
+ return 1;
+ } else {
+ tex_char_warning(*f, *c);
+ return 0;
+ }
+ }
+ }
+}
+
+/*tex
+
+ We need to do a lot of different things, so |mlist_to_hlist| makes two passes over the given
+ mlist.
+
+ The first pass does most of the processing: It removes |mu| spacing from glue, it recursively
+ evaluates all subsidiary mlists so that only the top-level mlist remains to be handled, it puts
+ fractions and square roots and such things into boxes, it attaches subscripts and superscripts,
+ and it computes the overall height and depth of the top-level mlist so that the size of
+ delimiters for a |fence_noad| will be known. The hlist resulting from each noad is recorded in
+ that noad's |new_hlist| field, an integer field that replaces the |nucleus| or |thickness|.
+
+ The second pass eliminates all noads and inserts the correct glue and penalties between nodes.
+
+*/
+
+static void tex_aux_assign_new_hlist(halfword target, halfword hlist)
+{
+ switch (node_type(target)) {
+ case fraction_noad:
+ kernel_math_list(fraction_numerator(target)) = null;
+ kernel_math_list(fraction_denominator(target)) = null;
+ tex_flush_node(fraction_numerator(target));
+ tex_flush_node(fraction_denominator(target));
+ fraction_numerator(target) = null;
+ fraction_denominator(target) = null;
+ break;
+ case radical_noad:
+ case simple_noad:
+ case accent_noad:
+ if (noad_nucleus(target)) {
+ kernel_math_list(noad_nucleus(target)) = null;
+ tex_flush_node(noad_nucleus(target));
+ noad_nucleus(target) = null;
+ }
+ break;
+ }
+ noad_new_hlist(target) = hlist;
+}
+
+/*tex
+
+ Most of the actual construction work of |mlist_to_hlist| is done by procedures with names like
+ |make_fraction|, |make_radical|, etc. To illustrate the general setup of such procedures, let's
+ begin with a couple of simple ones.
+
+*/
+
+static void tex_aux_make_over(halfword target, halfword style, halfword size, halfword fam)
+{
+ /*tex
+
+ No rule adaption yet, maybe it will never be implemented because overbars should be proper
+ extensibles. The order is: kern, rule, gap, content.
+
+ */
+ halfword result;
+ scaled thickness = tex_get_math_y_parameter_checked(style, math_parameter_overbar_rule);
+ scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_overbar_vgap);
+ scaled kern = tex_get_math_y_parameter_checked(style, math_parameter_overbar_kern);
+ {
+ halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_over_rule, OverbarRuleThickness);
+ if (t != undefined_math_parameter) {
+ thickness = t;
+ }
+ }
+ result = tex_aux_overbar(
+ tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_over_line_variant), style, math_nucleus_list, 0, NULL),
+ vgap, thickness, kern,
+ get_attribute_list(noad_nucleus(target)), math_over_rule_subtype, size, fam
+ );
+ node_subtype(result) = math_over_list;
+ kernel_math_list(noad_nucleus(target)) = result;
+ node_type(noad_nucleus(target)) = sub_box_node;
+}
+
+static void tex_aux_make_under(halfword target, halfword style, halfword size, halfword fam)
+{
+ /*tex
+
+ No rule adaption yet, maybe never as underbars should be proper extensibles. Here |x| is
+ the head, and |p| the tail but we keep the original names. The order is: content, gap,
+ rule, kern.
+
+ */
+ halfword result;
+ scaled thickness = tex_get_math_y_parameter_checked(style, math_parameter_underbar_rule);
+ scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_underbar_vgap);
+ scaled kern = tex_get_math_y_parameter_checked(style, math_parameter_underbar_kern);
+ {
+ halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_under_rule, UnderbarRuleThickness);
+ if (t != undefined_math_parameter) {
+ thickness = t;
+ }
+ }
+ result = tex_aux_underbar(
+ tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_under_line_variant), style, math_nucleus_list, 0, NULL),
+ vgap, thickness, kern,
+ get_attribute_list(noad_nucleus(target)), math_under_rule_subtype, size, fam
+ );
+ node_subtype(result) = math_over_list;
+ kernel_math_list(noad_nucleus(target)) = result;
+ node_type(noad_nucleus(target)) = sub_box_node;
+}
+
+/*tex
+
+ In \LUAMETATEX\ we also permit |\vcenter| in text mode but there we use another function than
+ the one below.
+
+ */
+
+static void tex_aux_make_vcenter(halfword target, halfword style, halfword size)
+{
+ halfword box = kernel_math_list(noad_nucleus(target));
+ if (node_type(box) != vlist_node) {
+ box = tex_aux_clean_box(noad_nucleus(target), style, style, math_list_list, 0, NULL); // todo: math_vcenter_list
+ kernel_math_list(noad_nucleus(target)) = box;
+ node_type(noad_nucleus(target)) = sub_box_node;
+ }
+ {
+ scaled total = box_total(box);
+ scaled axis = has_box_axis(box, no_math_axis) ? 0 : tex_aux_math_axis(size);
+ box_height(box) = axis + tex_half_scaled(total);
+ box_depth(box) = total - box_height(box);
+ }
+}
+
+/*tex
+
+ According to the rules in the |DVI| file specifications, we ensure alignment between a square
+ root sign and the rule above its nucleus by assuming that the baseline of the square-root
+ symbol is the same as the bottom of the rule. The height of the square-root symbol will be the
+ thickness of the rule, and the depth of the square-root symbol should exceed or equal the
+ height-plus-depth of the nucleus plus a certain minimum clearance~|psi|. The symbol will be
+ placed so that the actual clearance is |psi| plus half the excess.
+
+*/
+
+static void tex_aux_make_hextension(halfword target, int style, int size)
+{
+ int stack = 0;
+ scaled radicalwidth = tex_aux_math_given_x_scaled(noad_width(target));
+ halfword extensible = radical_left_delimiter(target);
+ halfword delimiter = tex_aux_make_delimiter(target, extensible, size, radicalwidth, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(target), NULL, 0);
+ halfword delimiterwidth = box_width(delimiter);
+ if (! stack && radicalwidth && (radicalwidth != delimiterwidth)) {
+ if (has_noad_option_middle(target)) {
+ scaled delta = tex_half_scaled(radicalwidth - delimiterwidth);
+ if (delta) {
+ halfword kern = tex_new_kern_node(delta, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(kern, delimiter);
+ delimiter = kern;
+ }
+ delimiterwidth = radicalwidth;
+ } else if (has_noad_option_exact(target)) {
+ delimiterwidth = radicalwidth;
+ }
+ }
+ delimiter = tex_hpack(delimiter, 0, packing_additional, direction_unknown, holding_none_option);
+ box_width(delimiter) = delimiterwidth;
+ tex_attach_attribute_list_copy(delimiter, target);
+ kernel_math_list(noad_nucleus(target)) = delimiter;
+ radical_left_delimiter(target) = null;
+ radical_right_delimiter(target) = null;
+}
+
+static void tex_aux_preroll_root_radical(halfword target, int style, int size)
+{
+ (void) size;
+ noad_new_hlist(target) = tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_radical_variant), style, math_nucleus_list, 0, NULL);
+}
+
+static halfword tex_aux_link_radical(halfword nucleus, halfword delimiter, halfword companion, halfword rightdelimiter)
+{
+ if (companion) {
+ tex_couple_nodes(delimiter, nucleus);
+ tex_couple_nodes(nucleus, companion);
+ return delimiter;
+ } else if (rightdelimiter) {
+ tex_couple_nodes(nucleus, delimiter);
+ return nucleus;
+ } else {
+ tex_couple_nodes(delimiter, nucleus);
+ return delimiter;
+ }
+}
+
+static void tex_aux_assign_radical(halfword target, halfword radical)
+{
+ halfword result = tex_hpack(radical, 0, packing_additional, direction_unknown, holding_none_option);
+ node_subtype(result) = math_radical_list;
+ tex_attach_attribute_list_copy(result, target);
+ kernel_math_list(noad_nucleus(target)) = result;
+ node_type(noad_nucleus(target)) = sub_box_node;
+ radical_left_delimiter(target) = null;
+ radical_right_delimiter(target) = null;
+}
+
+static void tex_aux_set_radical_kerns(delimiterextremes *extremes, kernset *kerns)
+{
+ if (kerns && extremes->tfont) {
+ if (tex_math_has_class_option(radical_noad_subtype, carry_over_left_top_kern_class_option)) {
+ kerns->topleft = tex_char_top_left_kern_from_font(extremes->tfont, extremes->tchar);
+ }
+ if (tex_math_has_class_option(radical_noad_subtype, carry_over_left_bottom_kern_class_option)) {
+ kerns->bottomleft = tex_char_bottom_left_kern_from_font(extremes->bfont, extremes->bchar);
+ }
+ if (tex_math_has_class_option(radical_noad_subtype, carry_over_right_top_kern_class_option)) {
+ kerns->topright = tex_char_top_right_kern_from_font(extremes->tfont, extremes->tchar);
+ }
+ if (tex_math_has_class_option(radical_noad_subtype, carry_over_right_bottom_kern_class_option)) {
+ kerns->bottomright = tex_char_bottom_right_kern_from_font(extremes->bfont, extremes->bchar);
+ }
+ if (tex_math_has_class_option(radical_noad_subtype, prefer_delimiter_dimensions_class_option)) {
+ kerns->height = extremes->height;
+ kerns->depth = extremes->depth;
+ }
+ }
+}
+
+static void tex_aux_make_root_radical(halfword target, int style, int size, kernset *kerns)
+{
+ halfword nucleus = noad_new_hlist(target);
+ scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_radical_vgap);
+ scaled theta = tex_get_math_y_parameter(style, math_parameter_radical_rule);
+ scaled kern = tex_get_math_y_parameter_checked(style, math_parameter_radical_kern);
+ scaled fam = delimiter_small_family(radical_left_delimiter(target));
+ halfword leftdelimiter = radical_left_delimiter(target);
+ halfword rightdelimiter = radical_right_delimiter(target);
+ halfword delimiter = leftdelimiter ? leftdelimiter : rightdelimiter;
+ halfword companion = leftdelimiter ? rightdelimiter : null;
+ halfword radical = null;
+ delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
+ noad_new_hlist(target) = null;
+ /*tex
+ We can take the rule width from the fam/style of the delimiter or use the most recent math
+ parameters value.
+ */
+ {
+ halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_radical_rule, RadicalRuleThickness);
+ if (t != undefined_math_parameter) {
+ theta = t;
+ }
+ }
+ {
+ halfword weird = theta == undefined_math_parameter;
+ if (weird) {
+ /*tex What do we have here. Why not issue an error */
+ theta = tex_get_math_y_parameter_checked(style, math_parameter_fraction_rule); /* a bit weird this one */
+ }
+ delimiter = tex_aux_make_delimiter(target, delimiter, size, box_total(nucleus) + clearance + theta, 0, style, 1, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, 0);
+ if (companion) {
+ /*tex For now we assume symmetry and same height and depth! */
+ companion = tex_aux_make_delimiter(target, companion, size, box_total(nucleus) + clearance + theta, 0, style, 1, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, 0);
+ }
+ if (weird) {
+ /*tex
+ If |y| is a composite then set |theta| to the height of its top character, else set it
+ to the height of |y|. Really?
+ */
+ halfword list = box_list(delimiter);
+ if (list && (node_type(list) == hlist_node)) {
+ /*tex possible composite */
+ halfword glyph = box_list(list);
+ if (glyph && node_type(glyph) == glyph_node) {
+ /*tex top character */
+ theta = tex_char_height_from_glyph(glyph);
+ } else {
+ theta = box_height(delimiter);
+ }
+ } else {
+ theta = box_height(delimiter);
+ }
+ }
+ }
+ /* */
+ tex_aux_set_radical_kerns(&extremes, kerns);
+ /*
+ Radicals in traditional fonts have their shape below the baseline which makes them unuseable
+ as stand alone characters but here we compensate for that fact. Opentype fonts derived from
+ traditional \TEX\ fonts can also be like that and it goed unnoticed until one accesses the
+ shape as character directly. Normally that gets corrected in the font when this has become
+ clear.
+ */
+ {
+ halfword delta = (box_total(delimiter) - theta) - (box_total(nucleus) + clearance);
+ if (delta > 0) {
+ /*tex increase the actual clearance */
+ clearance += tex_half_scaled(delta);
+ }
+ box_shift_amount(delimiter) = (box_height(delimiter) - theta) - (box_height(nucleus) + clearance);
+ if (companion) {
+ box_shift_amount(companion) = (box_height(companion) - theta) - (box_height(nucleus) + clearance);
+ }
+ }
+ if (node_type(delimiter) == vlist_node && node_subtype(delimiter) == math_v_delimiter_list) {
+ halfword before = tex_get_math_x_parameter_default(style, math_parameter_radical_extensible_before, 0);
+ tex_aux_prepend_hkern_to_box_list(nucleus, before, horizontal_math_kern_subtype, "bad delimiter");
+ }
+ if (node_type(companion) == vlist_node && node_subtype(companion) == math_v_delimiter_list) {
+ halfword after = tex_get_math_x_parameter_default(style, math_parameter_radical_extensible_after, 0);
+ tex_aux_append_hkern_to_box_list(nucleus, after, horizontal_math_kern_subtype, "bad delimiter");
+ }
+ {
+ halfword total = box_total(delimiter);
+ halfword list = tex_aux_overbar(nucleus, clearance, theta, kern, get_attribute_list(delimiter), math_radical_rule_subtype, size, fam);
+ radical = tex_aux_link_radical(list, delimiter, companion, rightdelimiter);
+ if (radical_degree(target)) {
+ halfword degree = tex_aux_clean_box(radical_degree(target), script_script_style, style, math_degree_list, 0, NULL);
+ scaled width = box_width(degree);
+ tex_attach_attribute_list_copy(degree, radical_degree(target));
+ if (width) {
+ scaled before = tex_get_math_x_parameter_checked(style, math_parameter_radical_degree_before);
+ scaled after = tex_get_math_x_parameter_checked(style, math_parameter_radical_degree_after);
+ /* scaled raise = tex_get_math_y_parameter_checked(style, math_parameter_radical_degree_raise); */ /* no! */
+ scaled raise = tex_get_math_parameter_checked(style, math_parameter_radical_degree_raise);
+ /* old:
+ if (-after > (width + before)) {
+ after = -(width + before);
+ }
+ new: */
+ if (-after > width) {
+ before += -after - width;
+ }
+ if (after) {
+ halfword kern = tex_new_kern_node(after, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, radical_degree(target));
+ tex_couple_nodes(kern, radical);
+ nucleus = kern;
+ } else {
+ nucleus = radical;
+ }
+ box_shift_amount(degree) = - (tex_xn_over_d(total, raise, 100) - box_depth(radical) - box_shift_amount(radical));
+ tex_couple_nodes(degree, nucleus);
+ if (before) {
+ halfword kern = tex_new_kern_node(before, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, radical_degree(target));
+ tex_couple_nodes(kern, degree);
+ radical = kern;
+ } else {
+ radical = degree;
+ }
+ } else {
+ tex_flush_node(degree);
+ }
+ /*tex for |\Uroot.. {<list>} {}|: */
+ kernel_math_list(radical_degree(target)) = null;
+ tex_flush_node(radical_degree(target));
+ radical_degree(target) = null;
+ }
+ }
+ tex_aux_assign_radical(target, radical);
+}
+
+/*tex
+ This is pretty much the same as the above when the |norule| option is set. But by splitting this
+ variant off we can enhance it more cleanly.
+*/
+
+static void tex_aux_make_delimited_radical(halfword target, int style, int size, kernset *kerns)
+{
+ halfword nucleus = noad_new_hlist(target);
+ /* scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_radical_vgap); */
+ halfword leftdelimiter = radical_left_delimiter(target);
+ halfword rightdelimiter = radical_right_delimiter(target);
+ halfword delimiter = leftdelimiter ? leftdelimiter : rightdelimiter;
+ halfword companion = leftdelimiter ? rightdelimiter : null;
+ halfword radical = null;
+ halfword depth = has_noad_option_exact(target) ? radical_depth(target) : (box_depth(nucleus) + radical_depth(target));
+ halfword height = has_noad_option_exact(target) ? radical_height(target) : (box_height(nucleus) + radical_height(target));
+ halfword total = height + depth;
+ delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
+ noad_new_hlist(target) = null;
+ delimiter = tex_aux_make_delimiter(target, delimiter, size, total, 0, style, 2, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, depth);
+ if (companion) {
+ /*tex For now we assume symmetry and same height and depth! */
+ companion = tex_aux_make_delimiter(target, companion, size, total, 0, style, 2, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, depth);
+ }
+ tex_aux_set_radical_kerns(&extremes, kerns);
+ radical = tex_aux_link_radical(nucleus, delimiter, companion, rightdelimiter);
+ tex_aux_assign_radical(target, radical);
+}
+
+/*tex Construct a vlist box: */
+
+static halfword tex_aux_wrapup_over_under_delimiter(halfword target, halfword x, halfword y, scaled shift_up, scaled shift_down, quarterword st)
+{
+ halfword box = tex_new_null_box_node(vlist_node, st);
+ scaled delta = (shift_up - box_depth(x)) - (box_height(y) - shift_down);
+ box_height(box) = shift_up + box_height(x);
+ box_depth(box) = box_depth(y) + shift_down;
+ tex_attach_attribute_list_copy(box, target);
+ if (delta) {
+ halfword kern = tex_new_kern_node(delta, vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(x, kern);
+ tex_couple_nodes(kern, y);
+ } else {
+ tex_couple_nodes(x, y);
+ }
+ box_list(box) = x;
+ return box;
+}
+
+/*tex When |exact| use radicalwidth (|y| is delimiter). */
+
+inline static halfword tex_aux_check_radical(halfword target, int stack, halfword r, halfword t)
+{
+ if (! stack && (box_width(r) >= box_width(t))) {
+ scaled width = tex_aux_math_given_x_scaled(noad_width(target));
+ if (width) {
+ scaled delta = width - box_width(r);
+ if (delta) {
+ if (has_noad_option_left(target)) {
+ halfword kern = tex_new_kern_node(delta, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(kern, r);
+ } else if (has_noad_option_middle(target)) {
+ halfword kern = tex_new_kern_node(tex_half_scaled(delta), horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(kern, r);
+ } else if (has_noad_option_right(target)) {
+ /*tex also kind of exact compared to vertical */
+ } else {
+ return r;
+ }
+ r = tex_hpack(r, 0, packing_additional, direction_unknown, holding_none_option);
+ box_width(r) = noad_width(target);
+ tex_attach_attribute_list_copy(r, target);
+ }
+ }
+ }
+ return r;
+}
+
+inline static void tex_aux_fixup_radical_width(halfword target, halfword x, halfword y)
+{
+ if (box_width(y) >= box_width(x)) {
+ if (noad_width(target)) {
+ box_shift_amount(x) += tex_half_scaled(box_width(y) - box_width(x)) ;
+ }
+ box_width(x) = box_width(y);
+ } else {
+ if (noad_width(target)) {
+ box_shift_amount(y) += tex_half_scaled(box_width(x) - box_width(y)) ;
+ }
+ box_width(y) = box_width(x);
+ }
+}
+
+inline static halfword tex_aux_get_radical_width(halfword target, halfword p)
+{
+ return noad_width(target) ? noad_width(target) : box_width(p);
+}
+
+/*tex
+
+ This has the |nucleus| box |x| as a limit above an extensible delimiter |y|.
+
+*/
+
+static void tex_aux_make_over_delimiter(halfword target, int style, int size)
+{
+ halfword result;
+ scaled delta;
+ int stack;
+ scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_bgap);
+ scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_vgap);
+ halfword content = tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_over_delimiter_variant), style, math_nucleus_list, 0, NULL);
+ scaled width = tex_aux_get_radical_width(target, content);
+ halfword over_delimiter = fraction_left_delimiter(target);
+ halfword delimiter = tex_aux_make_delimiter(target, over_delimiter, size, width, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(target), NULL, 0);
+ fraction_left_delimiter(target) = null;
+ delimiter = tex_aux_check_radical(target, stack, delimiter, content);
+ tex_aux_fixup_radical_width(target, content, delimiter);
+ delta = clearance - (shift - box_depth(content) - box_height(delimiter));
+ if (delta > 0) {
+ shift += delta;
+ }
+ result = tex_aux_wrapup_over_under_delimiter(target, content, delimiter, shift, 0, math_over_delimiter_list);
+ box_width(result) = box_width(content);
+ kernel_math_list(noad_nucleus(target)) = result;
+ node_type(noad_nucleus(target)) = sub_box_node;
+}
+
+/*tex
+
+ This has the extensible delimiter |x| as a limit below |nucleus| box |y|.
+
+*/
+
+static void tex_aux_make_under_delimiter(halfword target, int style, int size)
+{
+ halfword result;
+ scaled delta;
+ int stack;
+ scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_bgap);
+ scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_vgap);
+ halfword content = tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_under_delimiter_variant), style, math_nucleus_list, 0, NULL);
+ scaled width = tex_aux_get_radical_width(target, content);
+ halfword under_delimiter = fraction_left_delimiter(target);
+ halfword delimiter = tex_aux_make_delimiter(target, under_delimiter, size, width, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(target), NULL, 0);
+ fraction_left_delimiter(target) = null;
+ delimiter = tex_aux_check_radical(target, stack, delimiter, content);
+ tex_aux_fixup_radical_width(target, delimiter, content);
+ delta = clearance - (- box_depth(delimiter) - (box_height(content) - shift));
+ if (delta > 0) {
+ shift += delta;
+ }
+ result = tex_aux_wrapup_over_under_delimiter(target, delimiter, content, 0, shift, math_under_delimiter_list);
+ box_width(result) = box_width(content);
+ kernel_math_list(noad_nucleus(target)) = result;
+ node_type(noad_nucleus(target)) = sub_box_node;
+}
+
+/*tex
+
+ This has the extensible delimiter |x| as a limit above |nucleus| box |y|.
+
+*/
+
+static void tex_aux_make_delimiter_over(halfword target, int style, int size)
+{
+ halfword result;
+ scaled actual;
+ int stack;
+ scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_bgap);
+ scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_vgap);
+ halfword content = tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_delimiter_over_variant), style, math_nucleus_list, 0, NULL);
+ scaled width = tex_aux_get_radical_width(target, content);
+ halfword over_delimiter = fraction_left_delimiter(target);
+ halfword delimiter = tex_aux_make_delimiter(target, over_delimiter, size + (size == script_script_size ? 0 : 1), width, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(over_delimiter), NULL, 0);
+ fraction_left_delimiter(target) = null;
+ delimiter = tex_aux_check_radical(target, stack, delimiter, content);
+ tex_aux_fixup_radical_width(target, delimiter, content);
+ shift -= box_total(delimiter);
+ actual = shift - box_height(content);
+ if (actual < clearance) {
+ shift += (clearance - actual);
+ }
+ result = tex_aux_wrapup_over_under_delimiter(target, delimiter, content, shift, 0, math_over_delimiter_list);
+ box_width(result) = box_width(delimiter);
+ kernel_math_list(noad_nucleus(target)) = result;
+ node_type(noad_nucleus(target)) = sub_box_node;
+}
+
+/*tex
+
+ This has the extensible delimiter |y| as a limit below a |nucleus| box |x|.
+
+*/
+
+static void tex_aux_make_delimiter_under(halfword target, int style, int size)
+{
+ halfword result;
+ scaled actual;
+ int stack;
+ scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_bgap);
+ scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_vgap);
+ halfword content = tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_delimiter_under_variant), style, math_nucleus_list, 0, NULL);
+ scaled width = tex_aux_get_radical_width(target, content);
+ halfword under_delimiter = fraction_left_delimiter(target);
+ halfword delimiter = tex_aux_make_delimiter(target, under_delimiter, size + (size == script_script_size ? 0 : 1), width, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(under_delimiter), NULL, 0);
+ fraction_left_delimiter(target) = null;
+ delimiter = tex_aux_check_radical(target, stack, delimiter, content);
+ tex_aux_fixup_radical_width(target, content, delimiter);
+ shift -= box_total(delimiter);
+ actual = shift - box_depth(content);
+ if (actual < clearance) {
+ shift += (clearance - actual);
+ }
+ result = tex_aux_wrapup_over_under_delimiter(target, content, delimiter, 0, shift, math_under_delimiter_list);
+ /*tex This also equals |width(y)|: */
+ box_width(result) = box_width(delimiter);
+ kernel_math_list(noad_nucleus(target)) = result;
+ node_type(noad_nucleus(target)) = sub_box_node;
+}
+
+static void tex_aux_make_radical(halfword target, int style, int size, kernset *kerns)
+{
+ switch (node_subtype(target)) {
+ case under_delimiter_radical_subtype:
+ tex_aux_make_under_delimiter(target, style, size);
+ break;
+ case over_delimiter_radical_subtype:
+ tex_aux_make_over_delimiter(target, style, size);
+ break;
+ case delimiter_under_radical_subtype:
+ tex_aux_make_delimiter_under(target, style, size);
+ break;
+ case delimiter_over_radical_subtype:
+ tex_aux_make_delimiter_over(target, style, size);
+ break;
+ case delimited_radical_subtype:
+ tex_aux_make_delimited_radical(target, style, size, kerns);
+ break;
+ case h_extensible_radical_subtype:
+ tex_aux_make_hextension(target, style, size);
+ break;
+ default:
+ tex_aux_make_root_radical(target, style, size, kerns);
+ break;
+ }
+ if (noad_source(target)) {
+ halfword result = kernel_math_list(noad_nucleus(target));
+ if (result) {
+ box_source_anchor(result) = noad_source(target);
+ tex_set_box_geometry(result, anchor_geometry);
+ }
+ }
+}
+
+static void tex_aux_preroll_radical(halfword target, int style, int size)
+{
+ switch (node_subtype(target)) {
+ case under_delimiter_radical_subtype:
+ case over_delimiter_radical_subtype:
+ case delimiter_under_radical_subtype:
+ case delimiter_over_radical_subtype:
+ case h_extensible_radical_subtype:
+ break;
+ default:
+ tex_aux_preroll_root_radical(target, style, size);
+ break;
+ }
+}
+
+/*tex
+
+ Slants are not considered when placing accents in math mode. The accenter is centered over the
+ accentee, and the accent width is treated as zero with respect to the size of the final box.
+
+*/
+
+typedef enum math_accent_location_codes {
+ top_accent_code = 1,
+ bot_accent_code = 2,
+ overlay_accent_code = 4,
+ stretch_accent_code = 8,
+} math_accent_location_codes;
+
+static int tex_aux_compute_accent_skew(halfword target, int flags, scaled *s, halfword size)
+{
+ /*tex will be true if a top-accent is placed in |s| */
+ int absolute = 0;
+ switch (node_type(noad_nucleus(target))) {
+ case math_char_node:
+ {
+ halfword chr = null;
+ halfword fnt = null;
+ tex_aux_fetch(noad_nucleus(target), "accent", &fnt, &chr);
+ if (tex_aux_math_engine_control(fnt, math_control_accent_skew_apply)) {
+ /*tex
+ There is no bot_accent so let's assume that the shift also applies
+ to bottom and overlay accents.
+ */
+ *s = tex_char_top_accent_from_font(fnt, chr);
+ if (*s != INT_MIN) {
+ *s = tex_aux_math_x_size_scaled(fnt, *s, size);
+ absolute = 1;
+ } else {
+ *s = 0;
+ }
+ } else if (flags & top_accent_code) {
+ *s = tex_aux_math_x_size_scaled(fnt, tex_get_kern(fnt, chr, font_skew_char(fnt)), size);
+ } else {
+ *s = 0;
+ }
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: accent skew, font %i, chr %x, skew %D, absolute %i]", fnt, chr, *s, pt_unit, absolute);
+ tex_end_diagnostic();
+ }
+ break;
+ }
+ case sub_mlist_node:
+ {
+ /*tex
+ If |nucleus(q)| is a |sub_mlist_node| composed of an |accent_noad| we:
+
+ \startitemize
+ \startitem
+ use the positioning of the nucleus of that noad, recursing until
+ \stopitem
+ \startitem
+ the inner most |accent_noad|. This way multiple stacked accents are
+ \stopitem
+ \startitem
+ aligned to the inner most one.
+ \stopitem
+ \stoptitemize
+
+ The vlink test was added in version 1.06, so that we only consider a lone noad:
+
+ $
+ \Umathaccent bottom 0 0 "023DF { \Umathaccent fixed 0 0 "00302 { m } r } \quad
+ \Umathaccent bottom 0 0 "023DF { l \Umathaccent fixed 0 0 "00302 { m } r } \quad
+ \Umathaccent bottom 0 0 "023DF { l \Umathaccent fixed 0 0 "00302 { m } } \quad
+ \Umathaccent bottom 0 0 "023DF { \Umathaccent fixed 0 0 "00302 { m } } \quad
+ \Umathaccent bottom 0 0 "023DF { l r }
+ $
+
+ */
+ halfword p = kernel_math_list(noad_nucleus(target));
+ if (p && ! node_next(p)) {
+ switch (node_type(p)) {
+ case accent_noad:
+ absolute = tex_aux_compute_accent_skew(p, flags, s, size);
+ break;
+ case simple_noad:
+ if (! noad_has_following_scripts(p)) {
+ absolute = tex_aux_compute_accent_skew(p, flags, s, size);
+ }
+ break;
+ }
+ }
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: accent skew, absolute %i]", absolute);
+ tex_end_diagnostic();
+ }
+ break;
+ }
+ }
+ return absolute;
+}
+static void tex_aux_do_make_math_accent(halfword target, halfword accentfnt, halfword accentchr, int flags, int style, int size, scaled *accenttotal)
+{
+ /*tex The width and height (without scripts) of base character: */
+ scaled baseheight = 0;
+ // scaled basedepth = 0;
+ scaled basewidth = 0;
+ /*tex The space to remove between accent and base: */
+ scaled delta = 0;
+ scaled overshoot = 0;
+ extinfo *extended = NULL;
+ halfword attrlist = node_attr(target);
+ scaled fraction = accent_fraction(target) > 0 ? accent_fraction(target) : 1000;
+ scaled skew = 0;
+ halfword accent = null;
+ halfword base = null;
+ halfword result = null;
+ halfword nucleus = noad_nucleus(target);
+ halfword stretch = (flags & stretch_accent_code) == stretch_accent_code;
+ /*tex
+ Compute the amount of skew, or set |skew| to an alignment point. This will be true if a
+ top-accent has been determined.
+ */
+ int absolute = tex_aux_compute_accent_skew(target, flags, &skew, size);
+ {
+ halfword usedstyle;
+ if (flags & top_accent_code) {
+ usedstyle = tex_math_style_variant(style, math_parameter_top_accent_variant);
+ } else if (flags & bot_accent_code) {
+ usedstyle = tex_math_style_variant(style, math_parameter_bottom_accent_variant);
+ } else {
+ usedstyle = tex_math_style_variant(style, math_parameter_overlay_accent_variant);
+ }
+ /*tex Beware: this adds italic correction because it feeds into mlist_to_hlist */
+ base = tex_aux_clean_box(noad_nucleus(target), usedstyle, style, math_nucleus_list, 1, NULL); /* keep italic */
+ basewidth = box_width(base);
+ baseheight = box_height(base);
+ // basedepth = box_depth(base);
+ }
+ if (! absolute && tex_aux_math_engine_control(accentfnt, math_control_accent_skew_half)) {
+ skew = tex_half_scaled(basewidth);
+ absolute = 1;
+ }
+ /*tex
+ Todo: |w = w - loffset - roffset| but then we also need to add a few
+ kerns so no hurry with that one.
+ */
+ if (stretch && (tex_char_width_from_font(accentfnt, accentchr) < basewidth)) {
+ /*tex Switch to a larger accent if available and appropriate */
+ scaled target = 0;
+ if (flags & overlay_accent_code) {
+ target = baseheight;
+ } else {
+ target += basewidth;
+ if (base) {
+ /*tex Use larger margins, */
+ halfword list = box_list(base);
+ if (list && node_type(list) == glyph_node) {
+ halfword basefnt = glyph_font(list);
+ halfword basechr = glyph_character(list);
+ if (basefnt && basechr) {
+ target += tex_aux_math_x_size_scaled(basefnt, tex_char_right_margin_from_font(basefnt, basechr), size);
+ target += tex_aux_math_x_size_scaled(basefnt, tex_char_left_margin_from_font(basefnt, basechr), size);
+ }
+ }
+ }
+ }
+ if (fraction > 0) {
+ target = tex_xn_over_d(target, fraction, 1000);
+ }
+ while (1) {
+ if (tex_char_has_tag_from_font(accentfnt, accentchr, extension_tag)) {
+ extended = tex_char_horizontal_parts_from_font(accentfnt, accentchr);
+ }
+ if (extended) {
+ /*tex
+ This is a bit weird for an overlay but anyway, here we don't need a factor as
+ we don't step.
+ */
+ halfword overlap = tex_get_math_x_parameter_checked(style, math_parameter_connector_overlap_min);
+ accent = tex_aux_get_delimiter_box(accentfnt, accentchr, basewidth, overlap, 1, attrlist);
+ accent = register_extensible(accentfnt, accentchr, size, accent, attrlist);
+ break;
+ } else if (! tex_char_has_tag_from_font(accentfnt, accentchr, list_tag)) {
+ break;
+ } else {
+ halfword remainder = tex_char_remainder_from_font(accentfnt, accentchr);
+ if (! tex_char_exists(accentfnt, remainder)) {
+ break;
+ } else if (flags & overlay_accent_code) {
+ if (tex_aux_math_y_size_scaled(accentfnt, tex_char_height_from_font(accentfnt, remainder), size) > target) {
+ break;
+ }
+ } else {
+ if (tex_aux_math_x_size_scaled(accentfnt, tex_char_width_from_font(accentfnt, remainder), size) > target) {
+ break;
+ }
+ }
+ accentchr = remainder;
+ }
+ }
+ /*tex
+ So here we then need to package the offsets.
+ */
+ }
+ if (! accent) {
+ /*tex Italic gets added to width for traditional fonts (no italic anyway): */
+ accent = tex_aux_char_box(accentfnt, accentchr, attrlist, NULL, glyph_math_accent_subtype, basewidth, style);
+ }
+ if (accenttotal) {
+ *accenttotal = box_total(accent);
+ }
+ if (flags & top_accent_code) {
+ scaled b = tex_get_math_y_parameter(style, math_parameter_accent_base_height);
+ scaled f = tex_get_math_y_parameter(style, math_parameter_flattened_accent_base_height);
+ scaled u = tex_get_math_y_parameter(style, stretch ? math_parameter_flattened_accent_top_shift_up : math_parameter_accent_top_shift_up);
+ if (f != undefined_math_parameter && baseheight > f) {
+ halfword flatchr = tex_char_flat_accent_from_font(accentfnt, accentchr);
+ if (flatchr != INT_MIN && flatchr != accentchr) {
+ tex_flush_node(accent);
+ accent = tex_aux_char_box(accentfnt, flatchr, attrlist, NULL, glyph_math_accent_subtype, basewidth, style);
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: flattening accent, old %x, new %x]", accentchr, flatchr);
+ tex_end_diagnostic();
+ }
+ accentchr = flatchr;
+ }
+ }
+ if (b != undefined_math_parameter) {
+ /* not okay */
+ delta = baseheight < b ? baseheight : b;
+ }
+ if (u != undefined_math_parameter) {
+ delta -= u;
+ }
+ } else if (flags & bot_accent_code) {
+ // scaled b = tex_get_math_y_parameter(style, math_parameter_accent_base_depth, 0);
+ // scaled f = tex_get_math_y_parameter(style, math_parameter_flattened_accent_base_depth, 0);
+ scaled l = tex_get_math_y_parameter(style, stretch ? math_parameter_flattened_accent_bottom_shift_down : math_parameter_accent_bottom_shift_down);
+ // if (b != undefined_math_parameter) {
+ // /* not okay */
+ // delta = basedepth < b ? basedepth : b;
+ // }
+ if (l != undefined_math_parameter) {
+ delta += l;
+ }
+ } else { /* if (flags & overlay_accent_code) { */
+ /*tex Center the accent vertically around base: */
+ delta = tex_half_scaled(box_total(accent) + box_total(base));
+ }
+ if (node_type(nucleus) != math_char_node) {
+ /*tex We have a molecule, not a simple atom. */
+ } else if (noad_has_following_scripts(target)) {
+ /*tex Swap the scripts: */
+ tex_flush_node_list(base);
+ base = tex_new_node(simple_noad, ordinary_noad_subtype);
+ tex_attach_attribute_list_copy(base, nucleus);
+ noad_nucleus(base) = tex_aux_math_clone(nucleus);
+ /* we no longer move the outer scripts to the inner noad */
+ node_type(nucleus) = sub_mlist_node;
+ kernel_math_list(nucleus) = base;
+ base = tex_aux_clean_box(nucleus, style, style, math_nucleus_list, 1, NULL); /* keep italic */
+ delta = delta + box_height(base) - baseheight;
+ baseheight = box_height(base);
+ } else {
+ /*tex We have only pure math char nodes here:*/
+ // halfword basefnt = tex_fam_fnt(math_family(nucleus), size);
+ // if (tex_aux_has_opentype_metrics(basefnt)) {
+ // halfword basechr = math_character(nucleus);
+ // if (math_kernel_node_has_option(nucleus, math_kernel_no_italic_correction)) {
+ // italic = 0;
+ // } else if (tex_aux_math_engine_control(basefnt, math_control_accent_italic_kern)) {
+ // italic = tex_aux_math_x_style_scaled(basefnt, tex_char_italic_from_font(basefnt, basechr), size);
+ // }
+ // }
+ }
+ /*tex The top accents of both characters are aligned. */
+ {
+ halfword accentwidth = box_width(accent);
+ if (absolute) {
+ scaled anchor = 0;
+ if (extended) {
+ /*tex If the accent is extensible just take the center. */
+ anchor = tex_half_scaled(accentwidth);
+ } else {
+ anchor = tex_char_top_accent_from_font(accentfnt, accentchr); /* no bot accent key */
+ if (anchor != INT_MIN) {
+ anchor = tex_aux_math_y_size_scaled(accentfnt, anchor, size); /* why y and not x */
+ } else {
+ /*tex just take the center */
+ anchor = tex_half_scaled(accentwidth);
+ }
+ }
+ if (math_direction_par == dir_righttoleft) {
+ skew += anchor - accentwidth;
+ } else {
+ skew -= anchor;
+ }
+ } else if (accentwidth == 0) {
+ skew += basewidth;
+ } else if (math_direction_par == dir_righttoleft) {
+ skew += accentwidth; /* ok? */
+ } else {
+ skew += tex_half_scaled(basewidth - accentwidth);
+ }
+ box_shift_amount(accent) = skew;
+ box_width(accent) = 0; /* in gyre zero anyway */
+ if (accentwidth) {
+ overshoot = accentwidth + skew - basewidth;
+ }
+ if (overshoot < 0) {
+ overshoot = 0;
+ }
+ }
+ if (flags & (top_accent_code)) {
+ accent_top_overshoot(target) = overshoot;
+ }
+ if (flags & (bot_accent_code)) {
+ accent_bot_overshoot(target) = overshoot;
+ }
+ if (flags & (top_accent_code | overlay_accent_code)) {
+ if (delta) {
+ halfword kern = tex_new_kern_node(-delta, vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(accent, kern);
+ tex_couple_nodes(kern, base);
+ } else {
+ tex_couple_nodes(accent, base);
+ }
+ result = accent;
+ } else {
+ tex_couple_nodes(base, accent);
+ result = base;
+ }
+ result = tex_vpack(result, 0, packing_additional, max_dimen, (singleword) math_direction_par, holding_none_option);
+ tex_attach_attribute_list_copy(result, target);
+ node_subtype(result) = math_accent_list;
+ box_width(result) = box_width(base); // basewidth
+ delta = baseheight - box_height(result);
+ if (flags & (top_accent_code | overlay_accent_code)) {
+ if (delta > 0) {
+ /*tex make the height of box |y| equal to |h| */
+ halfword kern = tex_new_kern_node(delta, vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_try_couple_nodes(kern, box_list(result));
+ box_list(result) = kern;
+ box_height(result) = baseheight;
+ }
+ } else {
+ box_shift_amount(result) = - delta;
+ }
+ box_width(result) += overshoot;
+ // if (italic) {
+ // /*tex
+ // The old font codepath has ic built in, but new font code doesn't so we add
+ // the correction here.
+ // */
+ // tex_aux_math_insert_italic_kern(result, italic, nucleus, "accent");
+ // box_width(result) += italic ;
+ // }
+ kernel_math_list(nucleus) = result;
+ node_type(nucleus) = sub_box_node;
+}
+
+static void tex_aux_make_accent(halfword target, int style, int size, kernset *kerns)
+{
+ int topstretch = 0; /* ! (node_subtype(q) % 2); */
+ int botstretch = 0; /* ! (node_subtype(q) / 2); */
+ halfword fnt = null;
+ halfword chr = null;
+ /*tex
+ We don't do some div and mod magic on the subtype here: we just check it:
+ */
+ switch (node_subtype(target)) {
+ case bothflexible_accent_subtype: topstretch = 1; botstretch = 1; break;
+ case fixedtop_accent_subtype : botstretch = 1; break;
+ case fixedbottom_accent_subtype : topstretch = 1; break;
+ case fixedboth_accent_subtype : break;
+ }
+ /*tex
+ There is some inefficiency here as we calculate the width of the nuclues upto three times.
+ Maybe I need to have a look at that some day.
+ */
+ if (accent_top_character(target)) {
+ if (tex_aux_fetch(accent_top_character(target), "top accent", &fnt, &chr)) {
+ tex_aux_do_make_math_accent(target, fnt, chr, top_accent_code | (topstretch ? stretch_accent_code : 0), style, size, &(kerns->toptotal));
+ }
+ tex_flush_node(accent_top_character(target));
+ accent_top_character(target) = null;
+ }
+ if (accent_bottom_character(target)) {
+ if (tex_aux_fetch(accent_bottom_character(target), "bottom accent", &fnt, &chr)) {
+ tex_aux_do_make_math_accent(target, fnt, chr, bot_accent_code | (botstretch ? stretch_accent_code : 0), style, size, &(kerns->bottomtotal));
+ }
+ tex_flush_node(accent_bottom_character(target));
+ accent_bottom_character(target) = null;
+ }
+ if (accent_middle_character(target)) {
+ if (tex_aux_fetch(accent_middle_character(target), "overlay accent", &fnt, &chr)) {
+ tex_aux_do_make_math_accent(target, fnt, chr, overlay_accent_code | stretch_accent_code, style, size, NULL);
+ }
+ tex_flush_node(accent_middle_character(target));
+ accent_middle_character(target) = null;
+ }
+ if (noad_source(target)) {
+ halfword result = kernel_math_list(noad_nucleus(target));
+ if (result) {
+ box_source_anchor(result) = noad_source(target);
+ tex_set_box_geometry(result, anchor_geometry);
+ }
+ }
+}
+
+/*tex
+
+ The |make_fraction| procedure is a bit different because it sets |new_hlist (q)| directly rather
+ than making a sub-box.
+
+ Kerns are probably never zero so no need to be lean here. Actually they are likely to
+ be the same. By the time we make the rule we already dealt with all these clearance
+ issues, so we're sort of ahead of what happens in a callback wrt thickness.
+
+ This rather large function has been split up in pieces which is a bit more readable but also gives
+ a much bigger binary (probably due to inlining the helpers).
+
+*/
+
+/*tex
+ Create equal-width boxes |x| and |z| for the numerator and denominator. After this one is
+ called we compute the default amounts |shift_up| and |shift_down| by which they are displaced
+ from the baseline.
+*/
+
+static void tex_aux_wrap_fraction_parts(halfword target, int style, int size, halfword *numerator, halfword *denominator, int check)
+{
+ if (noad_style(target) == unused_math_style) {
+ *numerator = tex_aux_clean_box(fraction_numerator(target), tex_math_style_variant(style, math_parameter_numerator_variant), style, math_numerator_list, 0, NULL);
+ *denominator = tex_aux_clean_box(fraction_denominator(target), tex_math_style_variant(style, math_parameter_denominator_variant), style, math_denominator_list, 0, NULL);
+ } else {
+ *numerator = tex_aux_clean_box(fraction_numerator(target), noad_style(target), style, math_numerator_list, 0, NULL);
+ *denominator = tex_aux_clean_box(fraction_denominator(target), noad_style(target), style, math_denominator_list, 0, NULL);
+ }
+ if (check) {
+ if (box_width(*numerator) < box_width(*denominator)) {
+ *numerator = tex_aux_rebox(*numerator, box_width(*denominator), size);
+ } else {
+ *denominator = tex_aux_rebox(*denominator, box_width(*numerator), size);
+ }
+ }
+}
+
+/*tex
+ Put the fraction into a box with its delimiters, and make |new_hlist(q)| point to it.
+*/
+
+static void tex_aux_wrap_fraction_result(halfword target, int style, int size, halfword fraction, kernset *kerns)
+{
+ halfword result = null;
+ halfword left_delimiter = fraction_left_delimiter(target);
+ halfword right_delimiter = fraction_right_delimiter(target);
+ if (left_delimiter || right_delimiter) {
+ halfword left = null;
+ halfword right = null;
+ halfword delta = tex_get_math_y_parameter(style, math_parameter_fraction_del_size);
+ if (delta == undefined_math_parameter) {
+ delta = tex_aux_get_delimiter_height(box_height(fraction), box_depth(fraction), 1, size, style);
+ }
+ /*tex Watch out: there can be empty delimiter boxes but with width. */
+ delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
+ left = tex_aux_make_delimiter(target, left_delimiter, size, delta, 0, style, 1, NULL, NULL, 0, has_noad_option_nooverflow(target), NULL, 0);
+ right = tex_aux_make_delimiter(target, right_delimiter, size, delta, 0, style, 1, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, 0);
+ if (kerns && extremes.tfont) {
+ if (tex_math_has_class_option(fraction_noad_subtype, carry_over_left_top_kern_class_option)) {
+ kerns->topleft = tex_char_top_left_kern_from_font(extremes.tfont, extremes.tchar);
+ }
+ if (tex_math_has_class_option(fraction_noad_subtype, carry_over_left_bottom_kern_class_option)) {
+ kerns->bottomleft = tex_char_bottom_left_kern_from_font(extremes.bfont, extremes.bchar);
+ }
+ if (tex_math_has_class_option(fraction_noad_subtype, carry_over_right_top_kern_class_option)) {
+ kerns->topright = tex_char_top_right_kern_from_font(extremes.tfont, extremes.tchar);
+ }
+ if (tex_math_has_class_option(fraction_noad_subtype, carry_over_right_bottom_kern_class_option)) {
+ kerns->bottomright = tex_char_bottom_right_kern_from_font(extremes.bfont, extremes.bchar);
+ }
+ if (tex_math_has_class_option(fraction_noad_subtype, prefer_delimiter_dimensions_class_option)) {
+ kerns->height = extremes.height;
+ kerns->depth = extremes.depth;
+ }
+ }
+ /* tex_aux_normalize_delimiters(left, right); */
+ tex_couple_nodes(left, fraction);
+ tex_couple_nodes(fraction, right);
+ fraction = left;
+ }
+ result = tex_hpack(fraction, 0, packing_additional, direction_unknown, holding_none_option);
+ /*tex There can also be a nested one: */
+ node_subtype(result) = math_fraction_list;
+ tex_aux_assign_new_hlist(target, result);
+ if (noad_source(target)) {
+ box_source_anchor(result) = noad_source(target);
+ // box_anchor(result) = left_origin_anchor;
+ tex_set_box_geometry(result, anchor_geometry);
+ }
+}
+
+/*tex
+ The numerator and denominator must be separated by a certain minimum clearance, called |clr| in
+ the following program. The difference between |clr| and the actual clearance is |2 * delta|.
+*/
+
+static void tex_aux_calculate_fraction_shifts_stack(halfword target, int style, int size, halfword numerator, halfword denominator, scaled *shift_up, scaled *shift_down, scaled *delta)
+{
+ scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_stack_vgap);
+ (void) size;
+ *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_stack_num_up);
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_stack_denom_down);
+ *shift_up = tex_round_xn_over_d(*shift_up, fraction_v_factor(target), 1000);
+ *shift_down = tex_round_xn_over_d(*shift_down, fraction_v_factor(target), 1000);
+ *delta = tex_half_scaled(clearance - ((*shift_up - box_depth(numerator)) - (box_height(denominator) - *shift_down)));
+ if (*delta > 0) {
+ *shift_up += *delta;
+ *shift_down += *delta;
+ }
+}
+
+/*tex
+ In the case of a fraction line, the minimum clearance depends on the actual thickness of the
+ line.
+*/
+
+static void tex_aux_calculate_fraction_shifts_normal(halfword target, int style, int size, halfword numerator, halfword denominator, scaled *shift_up, scaled *shift_down, scaled *delta)
+{
+ scaled axis = tex_aux_math_axis(size);
+ scaled numerator_clearance = tex_get_math_y_parameter_checked(style, math_parameter_fraction_num_vgap);
+ scaled denominator_clearance = tex_get_math_y_parameter_checked(style, math_parameter_fraction_denom_vgap);
+ scaled delta_up = 0;
+ scaled delta_down = 0;
+ *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_fraction_num_up);
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_fraction_denom_down);
+ *shift_up = tex_round_xn_over_d(*shift_up, fraction_v_factor(target), 1000);
+ *shift_down = tex_round_xn_over_d(*shift_down, fraction_v_factor(target), 1000);
+ /* hm, delta is only set when we have a middle delimiter ... needs checking .. i should write this from scratch */
+ *delta = tex_half_scaled(tex_aux_math_given_y_scaled(fraction_rule_thickness(target)));
+ if (has_noad_option_exact(target)) {
+ delta_up = numerator_clearance - ((*shift_up - box_depth(numerator) ) - (axis + *delta));
+ delta_down = denominator_clearance - ((*shift_down - box_height(denominator)) + (axis - *delta));
+ } else {
+ // maybe this is just the old tex code path
+ scaled rule_thickness = tex_aux_math_given_y_scaled(fraction_rule_thickness(target));
+ scaled rule_parameter = tex_get_math_y_parameter_checked(style, math_parameter_fraction_rule);
+ numerator_clearance = tex_ext_xn_over_d(numerator_clearance, rule_thickness, rule_parameter);
+ denominator_clearance = tex_ext_xn_over_d(denominator_clearance, rule_thickness, rule_parameter);
+ delta_up = numerator_clearance - ((*shift_up - box_depth(numerator) ) - (axis + *delta));
+ delta_down = denominator_clearance - ((*shift_down - box_height(denominator)) + (axis - *delta));
+ }
+ *shift_up += delta_up;
+ *shift_down += delta_down;
+}
+
+static scaled tex_aux_check_fraction_rule(halfword target, int style, int size, int fractiontype, halfword *usedfam)
+{
+ scaled preferfont = has_noad_option_preferfontthickness(target);
+ halfword fam = math_rules_fam_par;
+ (void) style;
+ /*tex
+ We can take the rule width from an explicitly set fam, even if a fraction itself has no
+ character, otherwise we just use the math parameter.
+ */
+ if (preferfont) {
+ /*tex Forced by option or command. */
+ } else if (fractiontype == above_fraction_subtype) {
+ /*tex Bypassed by command. */
+ preferfont = 0;
+ } else if (fraction_rule_thickness(target)) {
+ /*tex Controlled by optional parameter. */
+ preferfont = 1;
+ }
+ if (preferfont) {
+ halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_fraction_rule, FractionRuleThickness);
+ if (t != undefined_math_parameter) {
+ fraction_rule_thickness(target) = t;
+ }
+ }
+ if (fraction_rule_thickness(target) == preset_rule_thickness) {
+ fraction_rule_thickness(target) = tex_get_math_y_parameter_checked(style, math_parameter_fraction_rule);
+ }
+ if (usedfam) {
+ *usedfam = fam;
+ }
+ return tex_aux_math_given_y_scaled(fraction_rule_thickness(target));
+}
+
+static void tex_aux_compensate_fraction_rule(halfword target, halfword fraction, halfword separator, scaled thickness)
+{
+ (void) target;
+ if (box_total(separator) != thickness) {
+ scaled half = tex_half_scaled(box_total(separator) - thickness);
+ box_height(fraction) += half;
+ box_depth(fraction) += half;
+ }
+}
+
+static void tex_aux_apply_fraction_shifts(halfword fraction, halfword numerator, halfword denominator, scaled shift_up, scaled shift_down)
+{
+ box_height(fraction) = shift_up + box_height(numerator);
+ box_depth(fraction) = box_depth(denominator) + shift_down;
+ box_width(fraction) = box_width(numerator);
+}
+
+/*tex
+ We construct a vlist box for the fraction, according to |shift_up| and |shift_down|. Maybe in
+ the meantime it is nicer to just calculate the fraction instead of messing with the height and
+ depth explicitly (the old approach).
+*/
+
+static halfword tex_aux_assemble_fraction(halfword target, int style, int size, halfword numerator, halfword denominator, halfword separator, scaled delta, scaled shift_up, scaled shift_down)
+{
+ (void) target;
+ (void) style;
+ if (separator) {
+ scaled axis = tex_aux_math_axis(size);
+ halfword after = tex_new_kern_node((axis - delta) - (box_height(denominator) - shift_down), vertical_math_kern_subtype);
+ halfword before = tex_new_kern_node((shift_up - box_depth(numerator)) - (axis + delta), vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(after, target);
+ tex_attach_attribute_list_copy(before, target);
+ tex_couple_nodes(separator, after);
+ tex_couple_nodes(after, denominator);
+ tex_couple_nodes(before, separator);
+ tex_couple_nodes(numerator, before);
+ } else {
+ halfword between = tex_new_kern_node((shift_up - box_depth(numerator)) - (box_height(denominator) - shift_down), vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(between, target);
+ tex_couple_nodes(between, denominator);
+ tex_couple_nodes(numerator, between);
+ }
+ return numerator;
+}
+
+static halfword tex_aux_make_skewed_fraction(halfword target, int style, int size, kernset *kerns)
+{
+ halfword middle = null;
+ halfword fraction = null;
+ halfword numerator = null;
+ halfword denominator = null;
+ scaled delta = 0;
+ halfword middle_delimiter = fraction_middle_delimiter(target);
+ scaled maxheight = 0;
+ scaled maxdepth = 0;
+ scaled ngap = 0;
+ scaled dgap = 0;
+ scaled hgap = 0;
+ delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
+ scaled tolerance = tex_get_math_y_parameter_default(style, math_parameter_skewed_delimiter_tolerance, 0);
+ scaled shift_up = tex_get_math_y_parameter_checked(style, math_parameter_skewed_fraction_vgap);
+ scaled shift_down = tex_round_xn_over_d(shift_up, fraction_v_factor(target), 1000);
+ (void) kerns;
+ shift_up = shift_down; /*tex The |shift_up| value might change later. */
+ tex_aux_wrap_fraction_parts(target, style, size, &numerator, &denominator, 0);
+ /*tex
+ Here we don't share code bnecause we're going horizontal.
+ */
+ if (! has_noad_option_noaxis(target)) {
+ shift_up += tex_half_scaled(tex_aux_math_axis(size));
+ }
+ /*tex
+ Construct a hlist box for the fraction, according to |hgap| and |vgap|.
+ */
+ hgap = tex_get_math_x_parameter_checked(style, math_parameter_skewed_fraction_hgap);
+ hgap = tex_round_xn_over_d(hgap, fraction_h_factor(target), 1000);
+ {
+ scaled ht = box_height(numerator) + shift_up;
+ scaled dp = box_depth(numerator) - shift_up;
+ if (dp < 0) {
+ dp = 0;
+ }
+ if (ht < 0) {
+ ht = 0;
+ }
+ if (ht > maxheight) {
+ maxheight = ht;
+ }
+ if (dp > maxdepth) {
+ maxdepth = dp;
+ }
+ }
+ {
+ scaled ht = box_height(denominator) - shift_down;
+ scaled dp = box_depth(denominator) + shift_down;
+ if (dp < 0) {
+ dp = 0;
+ }
+ if (ht < 0) {
+ ht = 0;
+ }
+ if (ht > maxheight) {
+ maxheight = ht;
+ }
+ if (dp > maxdepth) {
+ maxdepth = dp;
+ }
+ }
+ box_shift_amount(numerator) = -shift_up;
+ box_shift_amount(denominator) = shift_down;
+ delta = maxheight + maxdepth;
+ middle = tex_aux_make_delimiter(target, middle_delimiter, size, delta, 0, style, 1, NULL, NULL, tolerance, has_noad_option_nooverflow(target), &extremes, 0);
+ fraction = tex_new_null_box_node(hlist_node, math_fraction_list);
+ tex_attach_attribute_list_copy(fraction, target);
+ box_width(fraction) = box_width(numerator) + box_width(denominator) + box_width(middle) - hgap;
+ hgap = -tex_half_scaled(hgap);
+ box_height(fraction) = box_height(middle) > maxheight ? box_height(middle) : maxheight;
+ box_depth(fraction) = box_depth(middle) > maxdepth ? box_depth(middle) : maxdepth;
+ ngap = hgap;
+ dgap = hgap;
+ if (tex_math_has_class_option(fraction_noad_subtype, carry_over_left_top_kern_class_option)) {
+ ngap += tex_char_top_left_kern_from_font(extremes.tfont, extremes.tchar);
+ }
+ if (tex_math_has_class_option(fraction_noad_subtype, carry_over_right_bottom_kern_class_option)) {
+ dgap += tex_char_bottom_right_kern_from_font(extremes.tfont, extremes.tchar);
+ }
+ if (ngap || dgap) {
+ // todo: only add when non zero
+ halfword nkern = tex_new_kern_node(ngap, horizontal_math_kern_subtype);
+ halfword dkern = tex_new_kern_node(dgap, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(nkern, target);
+ tex_attach_attribute_list_copy(dkern, target);
+ tex_couple_nodes(numerator, nkern);
+ tex_couple_nodes(nkern, middle);
+ tex_couple_nodes(middle, dkern);
+ tex_couple_nodes(dkern, denominator);
+ } else {
+ tex_couple_nodes(numerator, middle);
+ tex_couple_nodes(middle, denominator);
+ }
+ box_list(fraction) = numerator;
+ return fraction;
+}
+
+static halfword tex_aux_make_stretched_fraction(halfword target, int style, int size, kernset *kerns)
+{
+ halfword middle = null;
+ halfword numerator = null;
+ halfword denominator = null;
+ scaled shift_up = 0;
+ scaled shift_down = 0;
+ scaled delta = 0;
+ halfword middle_delimiter = fraction_middle_delimiter(target);
+ halfword thickness = tex_aux_check_fraction_rule(target, style, size, stretched_fraction_subtype, NULL);
+ halfword fraction = tex_new_null_box_node(vlist_node, math_fraction_list);
+ (void) kerns;
+ tex_attach_attribute_list_copy(fraction, target);
+ tex_aux_wrap_fraction_parts(target, style, size, &numerator, &denominator, 1);
+ tex_aux_calculate_fraction_shifts_normal(target, style, size, numerator, denominator, &shift_up, &shift_down, &delta);
+ tex_aux_apply_fraction_shifts(fraction, numerator, denominator, shift_up, shift_down);
+ middle = tex_aux_make_delimiter(target, middle_delimiter, size, box_width(fraction), 1, style, 0, NULL, NULL, 0, 0, NULL, 0);
+ if (box_width(middle) < box_width(fraction)) {
+ /*tex It's always in the details: */
+ scaled delta = (box_width(fraction) - box_width(middle)) / 2;
+ tex_aux_prepend_hkern_to_box_list(middle, delta, horizontal_math_kern_subtype, "bad delimiter");
+ tex_aux_append_hkern_to_box_list(middle, delta, horizontal_math_kern_subtype, "bad delimiter");
+ box_width(middle) = box_width(fraction);
+ }
+ tex_aux_compensate_fraction_rule(target, fraction, middle, thickness);
+ box_list(fraction) = tex_aux_assemble_fraction(target, style, size, numerator, denominator, middle, delta, shift_up, shift_down);
+ return fraction;
+}
+
+static halfword tex_aux_make_ruled_fraction(halfword target, int style, int size, kernset *kerns, int fractiontype)
+{
+ halfword numerator = null;
+ halfword denominator = null;
+ scaled shift_up = 0;
+ scaled shift_down = 0;
+ scaled delta = 0;
+ halfword fam = 0;
+ halfword thickness = tex_aux_check_fraction_rule(target, style, size, fractiontype, &fam);
+ halfword fraction = tex_new_null_box_node(vlist_node, math_fraction_list);
+ halfword rule = null;
+ (void) kerns;
+ tex_attach_attribute_list_copy(fraction, target);
+ tex_aux_wrap_fraction_parts(target, style, size, &numerator, &denominator, 1);
+ if (fraction_rule_thickness(target) == 0) {
+ tex_aux_calculate_fraction_shifts_stack(target, style, size, numerator, denominator, &shift_up, &shift_down, &delta);
+ } else {
+ tex_aux_calculate_fraction_shifts_normal(target, style, size, numerator, denominator, &shift_up, &shift_down, &delta);
+ }
+ tex_aux_apply_fraction_shifts(fraction, numerator, denominator, shift_up, shift_down);
+ if (fractiontype != atop_fraction_subtype) {
+ rule = tex_aux_fraction_rule(box_width(fraction), thickness, get_attribute_list(target), math_fraction_rule_subtype, size, fam);
+ tex_aux_compensate_fraction_rule(target, fraction, rule, thickness);
+ }
+ box_list(fraction) = tex_aux_assemble_fraction(target, style, size, numerator, denominator, rule, delta, shift_up, shift_down);
+ return fraction;
+}
+
+/*tex
+ We intercept bad nodes created at the \LUA\ end but only partially. The fraction handler is
+ quite complex and uses a lot of parameters. You shouldn't mess with \TEX.
+*/
+
+static void tex_aux_make_fraction(halfword target, int style, int size, kernset *kerns)
+{
+ quarterword fractiontype = node_subtype(target);
+ halfword fraction = null;
+ TRYAGAIN:
+ switch (fractiontype) {
+ case over_fraction_subtype:
+ case atop_fraction_subtype:
+ case above_fraction_subtype:
+ tex_flush_node_list(fraction_middle_delimiter(target));
+ fraction_middle_delimiter(target) = null;
+ fraction = tex_aux_make_ruled_fraction(target, style, size, kerns, fractiontype);
+ break;
+ case skewed_fraction_subtype:
+ fraction_rule_thickness(target) = 0;
+ fraction = tex_aux_make_skewed_fraction(target, style, size, kerns);
+ break;
+ case stretched_fraction_subtype:
+ fraction = tex_aux_make_stretched_fraction(target, style, size, kerns);
+ break;
+ default:
+ fractiontype = atop_fraction_subtype;
+ goto TRYAGAIN;
+ }
+ tex_aux_wrap_fraction_result(target, style, size, fraction, kerns);
+ fraction_left_delimiter(target) = null;
+ fraction_middle_delimiter(target) = null;
+ fraction_right_delimiter(target) = null;
+}
+
+/*tex
+
+ If the nucleus of an |op_noad| is a single character, it is to be centered vertically with
+ respect to the axis, after first being enlarged (via a character list in the font) if we are in
+ display style. The normal convention for placing displayed limits is to put them above and
+ below the operator in display style.
+
+ The italic correction is removed from the character if there is a subscript and the limits are
+ not being displayed. The |make_op| routine returns the value that should be used as an offset
+ between subscript and superscript.
+
+ After |make_op| has acted, |subtype(q)| will be |limits| if and only if the limits have been
+ set above and below the operator. In that case, |new_hlist(q)| will already contain the desired
+ final box.
+
+ In display mode we also handle the nolimits scripts here because we have an option to tweak the
+ placement with |\mathnolimitsmode| in displaymode. So, when we have neither |\limits| or
+ |\nolimits| in text mode we fall through and scripts are dealt with later.
+
+*/
+
+static void tex_aux_make_scripts (
+ halfword target,
+ halfword kernel,
+ scaled italic,
+ int style,
+ scaled supshift,
+ scaled subshift,
+ scaled supdrop,
+ kernset *kerns
+);
+
+static halfword tex_aux_check_nucleus_complexity (
+ halfword target,
+ scaled *delta,
+ halfword style,
+ halfword size,
+ kernset *kerns
+);
+
+/*
+ For easy configuration ... fonts are somewhat inconsistent and the
+ values for italic correction run from 30 to 60\% of the width.
+
+*/
+
+static void tex_aux_get_shifts(int mode, int style, scaled delta, scaled *top, scaled *bot)
+{
+ switch (mode) {
+ case 0:
+ /*tex full bottom correction */
+ *top = 0;
+ *bot = -delta;
+ break;
+ case 1:
+ /*tex |MathConstants| driven */
+ *top = tex_round_xn_over_d(delta, tex_get_math_parameter_default(style, math_parameter_nolimit_sup_factor, 0), 1000);
+ *bot = -tex_round_xn_over_d(delta, tex_get_math_parameter_default(style, math_parameter_nolimit_sub_factor, 0), 1000);
+ break ;
+ case 2:
+ /*tex no correction */
+ *top = 0;
+ *bot = 0;
+ break ;
+ case 3:
+ /*tex half bottom correction */
+ *top = 0;
+ *bot = -tex_half_scaled(delta);
+ break;
+ case 4:
+ /*tex half bottom and top correction */
+ *top = tex_half_scaled(delta);
+ *bot = -tex_half_scaled(delta);
+ break;
+ default :
+ /*tex above 15: for quickly testing values */
+ *top = 0;
+ *bot = (mode > 15) ? -tex_round_xn_over_d(delta, mode, 1000) : 0;
+ break;
+ }
+}
+
+// static scaled tex_aux_make_op(halfword q, int style, int size, int italic, int forced_no_limits, kernset *kerns)
+// {
+// /*tex for historic reasons we have two flags .. because we need to adapt to the style */
+// int limits = has_noad_option_limits(q);
+// int nolimits = has_noad_option_nolimits(q);
+// if (! limits && ! nolimits && (style == display_style || style == cramped_display_style)) {
+// nolimits = 0;
+// limits = 1;
+// noad_options(q) |= noad_option_limits; /* so we can track it */
+// }
+// if (forced_no_limits) {
+// nolimits = 1;
+// }
+// if (node_type(noad_nucleus(q)) == math_char_node) {
+// halfword x;
+// int shiftaxis = 0;
+// halfword chr = null;
+// halfword fnt = null;
+// halfword autoleft = null;
+// halfword autoright = null;
+// halfword autosize = has_noad_option_auto(q);
+// scaled openupheight = has_noad_option_openupheight(q) ? noad_height(q) : 0;
+// scaled openupdepth = has_noad_option_openupdepth(q) ? noad_depth(q) : 0;
+// if (has_noad_option_adapttoleft(q) && node_prev(q)) {
+// autoleft = node_prev(q);
+// if (node_type(autoleft) != simple_noad) {
+// autoleft = null;
+// } else {
+// autoleft = noad_new_hlist(autoleft);
+// }
+// }
+// if (has_noad_option_adapttoright(q) && node_next(q)) {
+// autoright = noad_nucleus(node_next(q));
+// }
+// tex_aux_fetch(noad_nucleus(q), "operator", &fnt, &chr);
+// /*tex Nicer is actually to just test for |display_style|. */
+// if ((style < text_style) || autoleft || autoright || autosize) {
+// /*tex Try to make it larger in displaystyle. */
+// scaled opsize = tex_get_math_parameter(style, math_parameter_operator_size, NULL);
+// if ((autoleft || autoright || autosize) && (opsize == undefined_math_parameter)) {
+// opsize = 0;
+// }
+// if (opsize != undefined_math_parameter) {
+// /*tex Creating a temporary delimiter is the cleanest way. */
+// halfword y = tex_new_node(delimiter_node, 0);
+// tex_attach_attribute_list_copy(y, noad_nucleus(q));
+// delimiter_small_family(y) = math_family(noad_nucleus(q));
+// delimiter_small_character(y) = math_character(noad_nucleus(q));
+// opsize = tex_aux_math_y_scaled(opsize, style);
+// if (autoright) {
+// /*tex We look ahead and preroll, |autoright| is a noad. */
+// scaledwhd siz = tex_natural_hsizes(autoright, null, 0.0, 0, 0);
+// scaled total = siz.ht + siz.dp;
+// if (total > opsize) {
+// opsize = total;
+// }
+// }
+// if (autoleft && box_total(autoleft) > opsize) {
+// /*tex We look back and check, |autoleft| is a box. */
+// opsize = box_total(autoleft);
+// }
+// /* we need to check for overflow here */
+// opsize += limited_scaled(openupheight);
+// opsize += openupdepth;
+// x = tex_aux_make_delimiter(y, text_size, opsize, 0, style, ! has_noad_option_noaxis(q), noad_options(q), NULL, &italic, 0, has_noad_option_nooverflow(q), NULL);
+// // if (italic) {
+// // if (lmt_math_state.opentype) {
+// // /*tex
+// // As we never added italic correction we don't need to compensate. The ic
+// // is stored in a special field of the node and applied in some occasions.
+// // */
+// // } else if (noad_subscr(q) && ! has_noad_option_limits(q)) { /* todo: control option */
+// // /*tex
+// // Here we (selectively) remove the italic correction that always gets added
+// // in a traditional font. See (**). In \OPENTYPE\ mode we insert italic kerns,
+// // but in traditional mode it's width manipulation. This actually makes sense
+// // because those fonts have a fake width and the italic correction sets that
+// // right.
+// // */
+// // box_list(x) = tex_aux_math_remove_italic_kern(box_list(x), &italic, "operator");
+// // box_width(x) -= italic;
+// // }
+// // }
+// } else {
+// /*tex
+// Where was the weird + 1 coming from? It tweaks the comparison. Anyway, because we
+// do a lookup we don't need to scale the |total| and |opsize|. We have a safeguard
+// against endless loops.
+// */
+// opsize = tex_char_total_from_font(fnt, chr) + openupheight + openupdepth + 1;
+// /*
+// if (opsize) {
+// opsize = tex_aux_math_y_style_scaled(fnt, opsize, size); // we compare unscaled
+// }
+// */
+// while (tex_char_tag_from_font(fnt, chr) == list_tag && tex_char_total_from_font(fnt, chr) < opsize) {
+// halfword rem = tex_char_remainder_from_font(fnt, chr);
+// if (chr != rem && tex_char_exists(fnt, rem)) {
+// chr = rem;
+// math_character(noad_nucleus(q)) = chr;
+// } else {
+// break;
+// }
+// }
+// if (math_kernel_node_has_option(noad_nucleus(q), math_kernel_no_italic_correction)) {
+// italic = 0;
+// } else {
+// italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
+// }
+// x = tex_aux_clean_box(noad_nucleus(q), style, style, math_nucleus_list, 0, NULL);
+// // if (italic) {
+// // if (lmt_math_state.opentype) {
+// // /*tex we never added italic correction unless we had a |mlist_to_hlist| call. */
+// // } else if (noad_subscr(q) && ! has_noad_option_limits(q)) { /* todo: control option */
+// // box_list(x) = tex_aux_math_remove_italic_kern(box_list(x), &italic, "operator");
+// // box_width(x) -= italic;
+// // }
+// // }
+// shiftaxis = 1;
+// }
+// } else {
+// /*tex Non display style. */
+// italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
+// x = tex_aux_clean_box(noad_nucleus(q), style, style, math_nucleus_list, 0, NULL);
+// // if (italic) {
+// // if (lmt_math_state.opentype) {
+// // /*tex We never added italic correction, but it gets ignored anyway. */
+// // box_width(x) -= italic;
+// // } else if (noad_subscr(q) && ! has_noad_option_limits(q)) { /* todo: control option, what does this assume from the font */
+// // /*tex remove italic correction */
+// // box_width(x) -= italic;
+// // }
+// // }
+// box_height(x) += openupheight;
+// box_depth(x) += openupdepth;
+// shiftaxis = 1;
+// }
+// if (shiftaxis) {
+// /*tex center vertically */
+// box_shift_amount(x) = tex_half_scaled(box_height(x) - box_depth(x)) - tex_aux_math_axis(size);
+// }
+// if ((node_type(x) == hlist_node) && (openupheight || openupdepth)) {
+// box_shift_amount(x) -= openupheight/2;
+// box_shift_amount(x) += openupdepth/2;
+// }
+// node_type(noad_nucleus(q)) = sub_box_node;
+// math_list(noad_nucleus(q)) = x;
+// }
+// if (nolimits) {
+// /*tex
+// We end up here when there is an explicit directive or when we're in displaymode without
+// an explicit directive. If in text mode we want to have this mode driven placement tweak
+// we need to use the |\nolimits| directive. Beware: that mode might be changed to a font
+// property or option itself.
+// */
+// // if (lmt_math_state.opentype) {
+// kernset localkerns = { .tr = 0, .br = 0, .tl = 0, .bl = 0 };
+// if (kerns) {
+// localkerns.tr = kerns->tr;
+// localkerns.br = kerns->br;
+// localkerns.tl = kerns->tl;
+// localkerns.bl = kerns->bl;
+// }
+// halfword p = tex_aux_check_nucleus_complexity(q, NULL, style, lmt_math_state.size, &localkerns);
+// if (noad_has_scripts(q)) {
+// scaled top = 0; /*tex Normally this would be: | delta|. */
+// scaled bot = 0; /*tex Normally this would be: |-delta|. */
+// if (localkerns.tr || localkerns.br) {
+// italic = 0;
+// }
+// tex_aux_get_shifts(math_nolimits_mode_par, style, italic, &top, &bot);
+// tex_aux_make_scripts(q, p, 0, style, top, bot, 0, &localkerns);
+// } else {
+// tex_aux_assign_new_hlist(q, p);
+// }
+// italic = 0;
+// // } else {
+// // /*tex similar code as in the caller */
+// // halfword p = tex_aux_check_nucleus_complexity(q, &italic, style, lmt_math_state.size, NULL);
+// // if (noad_has_scripts(q)) {
+// // tex_aux_make_scripts(q, p, italic, style, 0, 0);
+// // } else {
+// // tex_aux_assign_new_hlist(q, p);
+// // }
+// // }
+// } else if (limits) {
+// /*tex
+//
+// The following program builds a vlist box |v| for displayed limits. The width of the box
+// is not affected by the fact that the limits may be skewed.
+//
+// We end up here when we have a limits directive or when that property is set because
+// we're in displaymode.
+// */
+// halfword nucleus = noad_nucleus(q);
+// halfword x = tex_aux_clean_box(noad_supscr(q), tex_math_style_variant(style, math_parameter_superscript_variant), style, math_sup_list, 0, NULL);
+// halfword y = tex_aux_clean_box(nucleus, style, style, math_nucleus_list, 0, NULL);
+// halfword z = tex_aux_clean_box(noad_subscr(q), tex_math_style_variant(style, math_parameter_subscript_variant), style, math_sub_list, 0, NULL);
+// halfword result = tex_new_null_box_node(vlist_node, math_modifier_list);
+// tex_attach_attribute_list_copy(result, q);
+// if (nucleus) {
+// switch (node_type(nucleus)) {
+// case sub_mlist_node:
+// case sub_box_node:
+// {
+// halfword n = math_list(nucleus);
+// if (! n) {
+// /* kind of special */
+// } else if (node_type(n) == hlist_node) {
+// /*tex just a not scaled char */
+// n = box_list(n);
+// while (n) {
+// if (node_type(n) == glyph_node && ! tex_has_glyph_option(n, glyph_option_no_italic_correction)) {
+// if (tex_aux_math_engine_control(glyph_font(n), math_control_apply_boxed_italic_kern)) {
+// italic = tex_aux_math_x_size_scaled(glyph_font(n), tex_char_italic_from_font(glyph_font(n), glyph_character(n)), size);
+// }
+// }
+// n = node_next(n);
+// }
+// } else {
+// /*tex This might need checking. */
+// while (n) {
+// if (node_type(n) == fence_noad && noad_italic(n) > italic) {
+// /*tex we can have dummies, the period ones */
+// italic = tex_aux_math_given_x_scaled(noad_italic(n));
+// }
+// n = node_next(n);
+// }
+// }
+// break;
+// }
+// case math_char_node:
+// {
+// halfword fnt = tex_fam_fnt(math_family(nucleus), size);
+// halfword chr = math_character(nucleus);
+// italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
+// break;
+// }
+// }
+// }
+// /*tex We're still doing limits. */
+// {
+// scaled halfitalic = tex_half_scaled(italic);
+// scaled supwidth = box_width(x);
+// scaled boxwidth = box_width(y);
+// scaled subwidth = box_width(z);
+// box_width(result) = boxwidth;
+// if (supwidth > boxwidth) {
+// boxwidth = supwidth;
+// }
+// if (subwidth > boxwidth) {
+// boxwidth = subwidth;
+// }
+// box_width(result) = boxwidth;
+// x = tex_aux_rebox(x, boxwidth, size);
+// y = tex_aux_rebox(y, boxwidth, size);
+// z = tex_aux_rebox(z, boxwidth, size);
+// /*tex This is only (visually) ok for integrals, but other operators have no italic anyway. */
+// box_shift_amount(x) = halfitalic;
+// box_shift_amount(z) = -halfitalic;
+// if (math_limits_mode_par >= 1) {
+// /*tex
+// This option enforces the real dimensions and avoids longer limits to stick out
+// which is a traditional \TEX\ feature. It's handy to have this for testing. Nicer
+// would be to also adapt the width of the wrapped scripts but these are reboxed
+// with centering so we keep that as it is.
+// */
+// if (supwidth + halfitalic > boxwidth) {
+// box_width(result) += supwidth + halfitalic - boxwidth;
+// }
+// if (subwidth + halfitalic > boxwidth) {
+// box_x_offset(result) = subwidth + halfitalic - boxwidth;
+// box_width(result) += box_x_offset(result);
+// tex_set_box_geometry(result, offset_geometry);
+// }
+// } else {
+// /*tex We keep the possible left and/or right overshoot of limits. */
+// }
+// /*tex Here the target |v| is still empty but we do set the height and depth. */
+// box_height(result) = box_height(y);
+// box_depth(result) = box_depth(y);
+// }
+// /*tex
+//
+// Attach the limits to |y| and adjust |height(v)|, |depth(v)| to account for
+// their presence.
+//
+// We use |shift_up| and |shift_down| in the following program for the amount of
+// glue between the displayed operator |y| and its limits |x| and |z|.
+//
+// The vlist inside box |v| will consist of |x| followed by |y| followed by |z|,
+// with kern nodes for the spaces between and around them; |b| is baseline and |v|
+// is the minumum gap.
+//
+// */
+// if (noad_supscr(q)) {
+// scaled bgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_bgap);
+// scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_vgap);
+// scaled vkern = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_kern);
+// scaled vshift = bgap - box_depth(x);
+// if (vshift < vgap) {
+// vshift = vgap;
+// }
+// if (vshift) {
+// halfword kern = tex_new_kern_node(vshift, vertical_math_kern_subtype);
+// tex_attach_attribute_list_copy(kern, q);
+// tex_couple_nodes(kern, y);
+// tex_couple_nodes(x, kern);
+// } else {
+// tex_couple_nodes(y, x);
+// }
+// if (vkern) {
+// halfword kern = tex_new_kern_node(vkern, vertical_math_kern_subtype);
+// tex_attach_attribute_list_copy(kern, q);
+// tex_couple_nodes(kern, x);
+// box_list(result) = kern;
+// } else {
+// box_list(result) = x;
+// }
+// box_height(result) += vkern + box_total(x) + vshift;
+// } else {
+// box_list(x) = null;
+// tex_flush_node(x);
+// box_list(result) = y;
+// }
+// if (noad_subscr(q)) {
+// scaled bgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_bgap);
+// scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_vgap);
+// scaled vkern = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_kern);
+// scaled vshift = bgap - box_height(z);
+// if (vshift < vgap) {
+// vshift = vgap;
+// }
+// if (vshift) {
+// halfword kern = tex_new_kern_node(vshift, vertical_math_kern_subtype);
+// tex_attach_attribute_list_copy(kern, q);
+// tex_couple_nodes(y, kern);
+// tex_couple_nodes(kern, z);
+// } else {
+// tex_couple_nodes(y, z);
+// }
+// if (vkern) {
+// halfword kern = tex_new_kern_node(vkern, vertical_math_kern_subtype);
+// tex_attach_attribute_list_copy(kern, q);
+// tex_couple_nodes(z, kern);
+// }
+// box_depth(result) += vkern + box_total(z) + vshift;
+// } else {
+// box_list(z) = null;
+// tex_flush_node(z);
+// }
+// if (noad_subscr(q)) {
+// math_list(noad_subscr(q)) = null;
+// tex_flush_node(noad_subscr(q));
+// noad_subscr(q) = null;
+// }
+// if (noad_supscr(q)) {
+// math_list(noad_supscr(q)) = null;
+// tex_flush_node(noad_supscr(q));
+// noad_supscr(q) = null;
+// }
+// tex_aux_assign_new_hlist(q, result);
+// // if (lmt_math_state.opentype) {
+// italic = 0;
+// // }
+// } else {
+// /*tex
+// We end up here when we're not in displaymode and don't have a (no)limits directive.
+// */
+// }
+// return italic;
+// }
+
+static scaled tex_aux_op_no_limits(halfword target, int style, int size, int italic, kernset *kerns)
+{
+ kernset localkerns ;
+ halfword p;
+ (void) size;
+ if (kerns) {
+ tex_math_copy_kerns(&localkerns, kerns);
+ } else {
+ tex_math_wipe_kerns(&localkerns);
+ }
+ p = tex_aux_check_nucleus_complexity(target, NULL, style, lmt_math_state.size, &localkerns);
+ if (noad_has_scripts(target)) {
+ scaled top = 0; /*tex Normally this would be: | delta|. */
+ scaled bot = 0; /*tex Normally this would be: |-delta|. */
+ if (localkerns.topright || localkerns.bottomright) {
+ italic = 0;
+ }
+ tex_aux_get_shifts(math_nolimits_mode_par, style, italic, &top, &bot);
+ tex_aux_make_scripts(target, p, 0, style, top, bot, 0, &localkerns);
+ } else {
+ tex_aux_assign_new_hlist(target, p);
+ }
+ // italic = 0;
+ return 0;
+}
+
+static scaled tex_aux_op_do_limits(halfword target, int style, int size, int italic, kernset *kerns)
+{
+ halfword nucleus = noad_nucleus(target);
+ halfword x = tex_aux_clean_box(noad_supscr(target), tex_math_style_variant(style, math_parameter_superscript_variant), style, math_sup_list, 0, NULL);
+ halfword y = tex_aux_clean_box(nucleus, style, style, math_nucleus_list, 0, NULL);
+ halfword z = tex_aux_clean_box(noad_subscr(target), tex_math_style_variant(style, math_parameter_subscript_variant), style, math_sub_list, 0, NULL);
+ halfword result = tex_new_null_box_node(vlist_node, math_modifier_list);
+ (void) kerns;
+ tex_attach_attribute_list_copy(result, target);
+ if (nucleus) {
+ switch (node_type(nucleus)) {
+ case sub_mlist_node:
+ case sub_box_node:
+ {
+ halfword n = kernel_math_list(nucleus);
+ if (! n) {
+ /* kind of special */
+ } else if (node_type(n) == hlist_node) {
+ /*tex just a not scaled char */
+ n = box_list(n);
+ while (n) {
+ if (node_type(n) == glyph_node && ! tex_has_glyph_option(n, glyph_option_no_italic_correction)) {
+ if (tex_aux_math_engine_control(glyph_font(n), math_control_apply_boxed_italic_kern)) {
+ italic = tex_aux_math_x_size_scaled(glyph_font(n), tex_char_italic_from_font(glyph_font(n), glyph_character(n)), size);
+ }
+ }
+ n = node_next(n);
+ }
+ } else {
+ /*tex This might need checking. */
+ while (n) {
+ if (node_type(n) == fence_noad && noad_italic(n) > italic) {
+ /*tex we can have dummies, the period ones */
+ italic = tex_aux_math_given_x_scaled(noad_italic(n));
+ }
+ n = node_next(n);
+ }
+ }
+ break;
+ }
+ case math_char_node:
+ {
+ halfword fnt = tex_fam_fnt(kernel_math_family(nucleus), size);
+ halfword chr = kernel_math_character(nucleus);
+ italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
+ break;
+ }
+ }
+ }
+ /*tex We're still doing limits. */
+ {
+ scaled halfitalic = tex_half_scaled(italic);
+ scaled supwidth = box_width(x);
+ scaled boxwidth = box_width(y);
+ scaled subwidth = box_width(z);
+ box_width(result) = boxwidth;
+ if (supwidth > boxwidth) {
+ boxwidth = supwidth;
+ }
+ if (subwidth > boxwidth) {
+ boxwidth = subwidth;
+ }
+ box_width(result) = boxwidth;
+ x = tex_aux_rebox(x, boxwidth, size);
+ y = tex_aux_rebox(y, boxwidth, size);
+ z = tex_aux_rebox(z, boxwidth, size);
+ /*tex This is only (visually) ok for integrals, but other operators have no italic anyway. */
+ box_shift_amount(x) = halfitalic;
+ box_shift_amount(z) = -halfitalic;
+ if (math_limits_mode_par >= 1) {
+ /*tex
+ This option enforces the real dimensions and avoids longer limits to stick out
+ which is a traditional \TEX\ feature. It's handy to have this for testing. Nicer
+ would be to also adapt the width of the wrapped scripts but these are reboxed
+ with centering so we keep that as it is.
+ */
+ if (supwidth + halfitalic > boxwidth) {
+ box_width(result) += supwidth + halfitalic - boxwidth;
+ }
+ if (subwidth + halfitalic > boxwidth) {
+ box_x_offset(result) = subwidth + halfitalic - boxwidth;
+ box_width(result) += box_x_offset(result);
+ tex_set_box_geometry(result, offset_geometry);
+ }
+ } else {
+ /*tex We keep the possible left and/or right overshoot of limits. */
+ }
+ /*tex Here the target |v| is still empty but we do set the height and depth. */
+ box_height(result) = box_height(y);
+ box_depth(result) = box_depth(y);
+ }
+ /*tex
+
+ Attach the limits to |y| and adjust |height(v)|, |depth(v)| to account for
+ their presence.
+
+ We use |shift_up| and |shift_down| in the following program for the amount of
+ glue between the displayed operator |y| and its limits |x| and |z|.
+
+ The vlist inside box |v| will consist of |x| followed by |y| followed by |z|,
+ with kern nodes for the spaces between and around them; |b| is baseline and |v|
+ is the minumum gap.
+
+ */
+ if (noad_supscr(target)) {
+ scaled bgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_bgap);
+ scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_vgap);
+ scaled vkern = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_kern);
+ scaled vshift = bgap - box_depth(x);
+ if (vshift < vgap) {
+ vshift = vgap;
+ }
+ if (vshift) {
+ halfword kern = tex_new_kern_node(vshift, vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(kern, y);
+ tex_couple_nodes(x, kern);
+ } else {
+ tex_couple_nodes(y, x);
+ }
+ if (vkern) {
+ halfword kern = tex_new_kern_node(vkern, vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(kern, x);
+ box_list(result) = kern;
+ } else {
+ box_list(result) = x;
+ }
+ box_height(result) += vkern + box_total(x) + vshift;
+ } else {
+ box_list(x) = null;
+ tex_flush_node(x);
+ box_list(result) = y;
+ }
+ if (noad_subscr(target)) {
+ scaled bgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_bgap);
+ scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_vgap);
+ scaled vkern = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_kern);
+ scaled vshift = bgap - box_height(z);
+ if (vshift < vgap) {
+ vshift = vgap;
+ }
+ if (vshift) {
+ halfword kern = tex_new_kern_node(vshift, vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(y, kern);
+ tex_couple_nodes(kern, z);
+ } else {
+ tex_couple_nodes(y, z);
+ }
+ if (vkern) {
+ halfword kern = tex_new_kern_node(vkern, vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(z, kern);
+ }
+ box_depth(result) += vkern + box_total(z) + vshift;
+ } else {
+ box_list(z) = null;
+ tex_flush_node(z);
+ }
+ if (noad_subscr(target)) {
+ kernel_math_list(noad_subscr(target)) = null;
+ tex_flush_node(noad_subscr(target));
+ noad_subscr(target) = null;
+ }
+ if (noad_supscr(target)) {
+ kernel_math_list(noad_supscr(target)) = null;
+ tex_flush_node(noad_supscr(target));
+ noad_supscr(target) = null;
+ }
+ tex_aux_assign_new_hlist(target, result);
+ // italic = 0;
+ return 0;
+}
+
+/*tex
+ The adapt to left or right is sort of fuzzy and might disappear in future versions. After all,
+ we have more fance fence support now.
+*/
+
+static void tex_aux_op_wrapup(halfword target, int style, int size, int italic, kernset *kerns)
+{
+ halfword x;
+ int shiftaxis = 0;
+ halfword chr = null;
+ halfword fnt = null;
+ halfword autoleft = null;
+ halfword autoright = null;
+ halfword autosize = has_noad_option_auto(target);
+ scaled openupheight = has_noad_option_openupheight(target) ? noad_height(target) : 0;
+ scaled openupdepth = has_noad_option_openupdepth(target) ? noad_depth(target) : 0;
+ (void) kerns;
+ if (has_noad_option_adapttoleft(target) && node_prev(target)) {
+ autoleft = node_prev(target);
+ if (node_type(autoleft) != simple_noad) {
+ autoleft = null;
+ } else {
+ autoleft = noad_new_hlist(autoleft);
+ }
+ }
+ if (has_noad_option_adapttoright(target) && node_next(target)) {
+ /* doesn't always work well */
+ autoright = noad_nucleus(node_next(target));
+ }
+ tex_aux_fetch(noad_nucleus(target), "operator", &fnt, &chr);
+ /*tex Nicer is actually to just test for |display_style|. */
+ if ((style < text_style) || autoleft || autoright || autosize) {
+ /*tex Try to make it larger in displaystyle. */
+ scaled opsize = tex_get_math_parameter(style, math_parameter_operator_size, NULL);
+ if ((autoleft || autoright || autosize) && (opsize == undefined_math_parameter)) {
+ opsize = 0;
+ }
+ if (opsize != undefined_math_parameter) {
+ /*tex Creating a temporary delimiter is the cleanest way. */
+ halfword y = tex_new_node(delimiter_node, 0);
+ tex_attach_attribute_list_copy(y, noad_nucleus(target));
+ delimiter_small_family(y) = kernel_math_family(noad_nucleus(target));
+ delimiter_small_character(y) = kernel_math_character(noad_nucleus(target));
+ opsize = tex_aux_math_y_scaled(opsize, style);
+ if (autoright) {
+ /*tex We look ahead and preroll, |autoright| is a noad. */
+ scaledwhd siz = tex_natural_hsizes(autoright, null, 0.0, 0, 0);
+ scaled total = siz.ht + siz.dp;
+ if (total > opsize) {
+ opsize = total;
+ }
+ }
+ if (autoleft && box_total(autoleft) > opsize) {
+ /*tex We look back and check, |autoleft| is a box. */
+ opsize = box_total(autoleft);
+ }
+ /* we need to check for overflow here */
+ opsize += limited_scaled(openupheight);
+ opsize += openupdepth;
+ x = tex_aux_make_delimiter(target, y, text_size, opsize, 0, style, ! has_noad_option_noaxis(target), NULL, &italic, 0, has_noad_option_nooverflow(target), NULL, 0);
+ } else {
+ /*tex
+ Where was the weird + 1 coming from? It tweaks the comparison. Anyway, because we
+ do a lookup we don't need to scale the |total| and |opsize|. We have a safeguard
+ against endless loops.
+ */
+ opsize = tex_char_total_from_font(fnt, chr) + openupheight + openupdepth + 1;
+ /*
+ if (opsize) {
+ opsize = tex_aux_math_y_style_scaled(fnt, opsize, size); // we compare unscaled
+ }
+ */
+ while (tex_char_has_tag_from_font(fnt, chr, list_tag) && tex_char_total_from_font(fnt, chr) < opsize) {
+ halfword rem = tex_char_remainder_from_font(fnt, chr);
+ if (chr != rem && tex_char_exists(fnt, rem)) {
+ chr = rem;
+ kernel_math_character(noad_nucleus(target)) = chr;
+ } else {
+ break;
+ }
+ }
+ if (math_kernel_node_has_option(noad_nucleus(target), math_kernel_no_italic_correction)) {
+ italic = 0;
+ } else {
+ italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
+ }
+ x = tex_aux_clean_box(noad_nucleus(target), style, style, math_nucleus_list, 0, NULL);
+ shiftaxis = 1;
+ }
+ } else {
+ /*tex Non display style. */
+ italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
+ x = tex_aux_clean_box(noad_nucleus(target), style, style, math_nucleus_list, 0, NULL);
+ box_height(x) += openupheight;
+ box_depth(x) += openupdepth;
+ shiftaxis = 1;
+ }
+ if (shiftaxis) {
+ /*tex center vertically */
+ box_shift_amount(x) = tex_half_scaled(box_height(x) - box_depth(x)) - tex_aux_math_axis(size);
+ }
+ if ((node_type(x) == hlist_node) && (openupheight || openupdepth)) {
+ box_shift_amount(x) -= openupheight/2;
+ box_shift_amount(x) += openupdepth/2;
+ }
+ node_type(noad_nucleus(target)) = sub_box_node;
+ kernel_math_list(noad_nucleus(target)) = x;
+}
+
+static scaled tex_aux_make_op(halfword target, int style, int size, int italic, int limits_mode, kernset *kerns)
+{
+ if (limits_mode == limits_horizontal_mode) {
+ /*tex We enforce this and it can't be overruled! */
+ } else if (! has_noad_option_limits(target) && ! has_noad_option_nolimits(target) && (style == display_style || style == cramped_display_style)) {
+ limits_mode = limits_vertical_mode;
+ noad_options(target) |= noad_option_limits; /* so we can track it */
+ } else if (has_noad_option_nolimits(target)) {
+ limits_mode = limits_horizontal_mode;
+ } else if (has_noad_option_limits(target)) {
+ limits_mode = limits_vertical_mode;
+ }
+ if (node_type(noad_nucleus(target)) == math_char_node) {
+ tex_aux_op_wrapup(target, style, size, italic, kerns);
+ }
+ switch (limits_mode) {
+ case limits_horizontal_mode:
+ /*tex
+ We end up here when there is an explicit directive or when we're in displaymode without
+ an explicit directive. If in text mode we want to have this mode driven placement tweak
+ we need to use the |\nolimits| directive. Beware: that mode might be changed to a font
+ property or option itself.
+ */
+ return tex_aux_op_no_limits(target, style, size, italic, kerns); /* italic becomes zero */
+ case limits_vertical_mode:
+ /*tex
+
+ We end up here when we have a limits directive or when that property is set because
+ we're in displaymode. The following program builds a vlist box |v| for displayed limits.
+ The width of the box is not affected by the fact that the limits may be skewed.
+ */
+ return tex_aux_op_do_limits(target, style, size, italic, kerns); /* italic becomes zero */
+ default:
+ /*tex
+ We end up here when we're not in displaymode and don't have a (no)limits directive.
+ */
+ return italic; /* italic is retained */
+ }
+}
+
+/*tex
+
+ A ligature found in a math formula does not create a ligature, because there is no question of
+ hyphenation afterwards; the ligature will simply be stored in an ordinary |glyph_node|, after
+ residing in an |ord_noad|.
+
+ The |type| is converted to |math_text_char| here if we would not want to apply an italic
+ correction to the current character unless it belongs to a math font (i.e., a font with
+ |space=0|).
+
+ No boundary characters enter into these ligatures.
+
+*/
+
+/* How about: ord_noad_type_limits */
+
+// inline static int tex_aux_is_simple_char_noad(halfword p) /* only old school characters */
+// {
+// return (node_type(p) == simple_noad) && (node_type(noad_nucleus(p)) == math_char_node && tex_math_has_class_option(node_subtype(p), check_ligature_class_option));
+// }
+//
+// inline static int tex_aux_have_same_nucleus_fam(halfword p, halfword q)
+// {
+// return math_family(noad_nucleus(p)) == math_family(noad_nucleus(q));
+// }
+//
+// static void tex_aux_make_ord(halfword q, halfword size)
+// {
+// /*tex The left-side character for lig/kern testing. */
+// RESTART:
+// /*tex We can end up here again after a ligature is built. */
+// if (! noad_has_following_scripts(q) && node_type(noad_nucleus(q)) == math_char_node) {
+// halfword p = node_next(q);
+// /*tex */
+// if (p && tex_aux_is_simple_char_noad(p) && tex_aux_have_same_nucleus_fam(p, q)) {
+// halfword chr = null;
+// halfword fnt = null;
+// node_type(noad_nucleus(q)) = math_text_char_node;
+// tex_aux_fetch(noad_nucleus(q), "ordinal", &fnt, &chr);
+// if (tex_aux_math_engine_control(fnt, math_control_apply_ordinary_italic_kern)) {
+// /*
+// We don't have other kerns in opentype math fonts. There are however these
+// staircase kerns that are dealt with elsewhere. But for new math fonts we do
+// need to add italic correction.
+// */
+// if (math_kernel_node_has_option(noad_nucleus(q), math_kernel_no_italic_correction)) {
+// /* go on */
+// } else {
+// scaled kern = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, math_character(noad_nucleus(q))), size);
+// if (kern) {
+// tex_aux_math_insert_italic_kern(q, kern, q, "ord");
+// }
+// }
+// } else if (tex_aux_math_engine_control(fnt, math_control_check_ligature_and_kern)) {
+// if (tex_has_kern(fnt, chr) || tex_has_ligature(fnt, chr)) {
+// /*tex
+//
+// Here we construct ligatures, quite unlikely in new math fonts so maybe we
+// should just not go here for such fonts.
+//
+// If character |a| has a kern with |cur_c|, attach the kern after~|q|; or if
+// it has a ligature with |cur_c|, combine noads |q| and~|p| appropriately;
+// then |return| if the cursor has moved past a noad, or |goto restart|.
+//
+// Note that a ligature between an |ord_noad| and another kind of noad is
+// replaced by an |ord_noad|, when the two noads collapse into one.
+//
+// We could make a parenthesis (say) change shape when it follows certain
+// letters. Presumably a font designer will define such ligatures only when
+// this convention makes sense.
+//
+// */
+// halfword nxt = math_character(noad_nucleus(p));
+// halfword slot;
+// int type = tex_valid_ligature(chr, nxt, &slot);
+// if (type >= 0) {
+// switch (type) {
+// case 1: /*tex \type{=:|} */
+// case 5: /*tex \type{=:|>} */
+// math_character(noad_nucleus(q)) = slot;
+// break;
+// case 2: /*tex \type{|=:} */
+// case 6: /*tex \type{|=:>} */
+// math_character(noad_nucleus(p)) = slot;
+// break;
+// case 3: /*tex \type{|=:|} */
+// case 7: /*tex \type{|=:|>} */
+// case 11: /*tex \type{|=:|>>} */
+// {
+// halfword r = tex_new_node(simple_noad, ordinary_noad_subtype);
+// halfword s = tex_new_node(math_char_node, 0);
+// tex_attach_attribute_list_copy(r, q);
+// tex_attach_attribute_list_copy(s, q);
+// noad_nucleus(r) = s;
+// math_character(noad_nucleus(r)) = slot;
+// math_family(noad_nucleus(r)) = math_family(noad_nucleus(q));
+// tex_couple_nodes(q, r);
+// tex_couple_nodes(r, p);
+// if (type < 11) {
+// node_type(noad_nucleus(r)) = math_char_node;
+// } else {
+// /*tex prevent combination */
+// node_type(noad_nucleus(r)) = math_text_char_node;
+// }
+// }
+// break;
+// default: /*tex |=:| */
+// tex_try_couple_nodes(q, node_next(p));
+// math_character(noad_nucleus(q)) = slot;
+// noad_subscr(q) = noad_subscr(p);
+// noad_supscr(q) = noad_supscr(p);
+// noad_subscr(p) = null ;
+// noad_supscr(p) = null ;
+// tex_flush_node(p);
+// break;
+// }
+// if (type > 3) {
+// return;
+// } else {
+// node_type(noad_nucleus(q)) = math_char_node;
+// goto RESTART; /*tex Inefficient but we never see this branch anyway. */
+// }
+// }
+// {
+// // scaled kern = tex_aux_math_x_size_scaled(fnt, tex_valid_kern(chr, nxt), size);
+// halfword nxtchr = null;
+// halfword nxtfnt = null;
+// tex_aux_fetch(noad_nucleus(p), "ordinal", &nxtfnt, &nxtchr);
+// scaled kern = tex_get_kern(fnt, chr, nxtchr);
+// if (kern) {
+// tex_aux_math_insert_font_kern(q, kern, q, "ord");
+// return;
+// }
+// }
+// }
+// }
+// }
+// }
+// }
+
+
+// $ \mathord {a} $ : ord -> nucleus -> mathchar
+// $ \mathord {ab} $ : ord -> nucleus -> submlist -> ord + ord
+
+/*tex
+ Have there ever been math fonts with kerns and ligatures? If so it had to be between characters
+ within the same font. Maybe this was meant for composed charaters? And the 256 limits of the
+ number of characters didn't help either. This is why we take the freedom to do things a bit
+ different.
+
+ We don't have other kerns in opentype math fonts. There are however these staircase kerns that
+ are dealt with elsewhere. But for new math fonts we do need to add italic correction occasionally
+ and staircase kerns only happen with scripts.
+
+ We could add support for ligatures but we don't need those anyway so it's a waste of time and
+ bytes.
+
+ The ord checker kicks in after every ord but we can consider a special version where we handle
+ |sub_list_node| noads. And we could maybe check on sloped shapes but then we for sure end up
+ in a mess we don't want.
+
+*/
+
+static halfword tex_aux_check_ord(halfword current, halfword size, halfword next)
+{
+ if (! noad_has_following_scripts(current)) {
+ halfword nucleus = noad_nucleus(current);
+ switch (node_type(nucleus)) {
+ case sub_mlist_node:
+ {
+ // I'm not that motivated for this and it should be an engine option anyway then.
+
+ // halfword head = math_list(nucleus);
+ // halfword tail = tex_tail_of_node_list(head);
+ // // doesn't work
+ // if (node_type(head) == simple_noad && node_prev(current) ) {
+ // if (node_type(node_prev(current)) == simple_noad) {
+ // head = tex_aux_check_ord(node_prev(current), size, head);
+ // math_list(nucleus) = head;
+ // }
+ // }
+ // // works
+ // if (node_type(tail) == simple_noad && node_next(current) ) {
+ // tex_aux_check_ord(tail, size, node_next(current));
+ // }
+ break;
+ }
+ case math_char_node:
+ {
+ if (! next) {
+ next = node_next(current);
+ }
+ halfword curchr = null;
+ halfword curfnt = null;
+ tex_aux_fetch(nucleus, "ordinal", &curfnt, &curchr);
+ if (curfnt && curchr) {
+ halfword kern = 0;
+ halfword italic = 0;
+ if (next) {
+ halfword nxtnucleus = noad_nucleus(next);
+ halfword nxtfnt = null;
+ halfword nxtchr = null;
+ if (node_type(nxtnucleus) == math_char_node && kernel_math_family(nucleus) == kernel_math_family(nxtnucleus)) {
+ tex_aux_fetch(nxtnucleus, "ordinal", &nxtfnt, &nxtchr);
+ if (nxtfnt && nxtchr) {
+ halfword mainclass = node_subtype(current);
+ /* todo: ligatures */
+ if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_kern_pair)) {
+ if (math_kernel_node_has_option(nucleus, math_kernel_no_right_pair_kern) || math_kernel_node_has_option(nxtnucleus, math_kernel_no_left_pair_kern)) {
+ /* ignore */
+ } else if (tex_math_has_class_option(mainclass, check_italic_correction_class_option)) {
+ /* ignore */
+ } else if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_italic_kern)) {
+ kern = tex_aux_math_x_size_scaled(curfnt, tex_get_kern(curfnt, curchr, nxtchr), size);
+ }
+ }
+ if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_italic_kern)) {
+ if (math_kernel_node_has_option(nucleus, math_kernel_no_italic_correction)) {
+ /* ignore */
+ } else if (tex_math_has_class_option(mainclass, check_kern_pair_class_option)) {
+ /* ignore */
+ } else if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_italic_kern)) {
+ italic = tex_aux_math_x_size_scaled(curfnt, tex_char_italic_from_font(curfnt, curchr), size);
+ }
+ }
+ }
+ }
+ }
+ if (kern) {
+ current = tex_aux_math_insert_font_kern(current, kern, current, "ord");
+ }
+ if (italic) {
+ // todo : after last unless upright but then we need to signal
+ current = tex_aux_math_insert_italic_kern(current, italic, current, "ord");
+ }
+ }
+ }
+ break;
+ }
+ }
+ return current;
+}
+
+static halfword tex_aux_prepend_hkern_to_new_hlist(halfword box, scaled delta, halfword subtype, const char *trace)
+{
+ halfword list = noad_new_hlist(box);
+ halfword kern = tex_new_kern_node(delta, (quarterword) subtype);
+ tex_attach_attribute_list_copy(kern, box);
+ if (list) {
+ tex_couple_nodes(kern, list);
+ }
+ list = kern;
+ noad_new_hlist(box) = list;
+ tex_aux_trace_kerns(kern, "adding kern", trace);
+ return list;
+}
+
+static void tex_aux_append_hkern_to_box_list(halfword box, scaled delta, halfword subtype, const char *trace)
+{
+ halfword list = box_list(box);
+ halfword kern = tex_new_kern_node(delta, (quarterword) subtype);
+ tex_attach_attribute_list_copy(kern, box);
+ if (list) {
+ tex_couple_nodes(tex_tail_of_node_list(list), kern);
+ } else {
+ list = kern;
+ }
+ box_list(box) = list;
+ box_width(box) += delta;
+ tex_aux_trace_kerns(kern, "adding kern", trace);
+}
+
+static void tex_aux_prepend_hkern_to_box_list(halfword box, scaled delta, halfword subtype, const char *trace)
+{
+ halfword list = box_list(box);
+ halfword kern = tex_new_kern_node(delta, (quarterword) subtype);
+ tex_attach_attribute_list_copy(kern, box);
+ if (list) {
+ tex_couple_nodes(kern, list);
+ }
+ list = kern;
+ box_list(box) = list;
+ box_width(box) += delta;
+ tex_aux_trace_kerns(kern, "adding kern", trace);
+}
+
+/*tex
+
+ The purpose of |make_scripts (q, it)| is to attach the subscript and/or superscript of noad |q|
+ to the list that starts at |new_hlist (q)|, given that subscript and superscript aren't both
+ empty. The superscript will be horizontally shifted over |delta1|, the subscript over |delta2|.
+
+ We set |shift_down| and |shift_up| to the minimum amounts to shift the baseline of subscripts
+ and superscripts based on the given nucleus.
+
+ Note: We need to look at a character but also at the first one in a sub list and there we
+ ignore leading kerns and glue. Elsewhere is code that removes kerns assuming that is italic
+ correction. The heuristics are unreliable for the new fonts so eventualy there will be an
+ option to ignore such corrections. (We now actually have that level of control.)
+
+ Instead of a few mode parameters we now control this via the control options bitset. In this
+ case we cheat a bit as there is no relationship with a font (the first |null| parameter that
+ gets passed here). In the archive we can find all the variants.
+
+*/
+
+static halfword tex_aux_analyze_script(halfword init, scriptdata *data)
+{
+ if (init) {
+ switch (node_type(init)) {
+ case math_char_node :
+ if (tex_aux_math_engine_control(null, math_control_analyze_script_nucleus_char)) {
+ if (tex_aux_fetch(init, "script char", &(data->fnt), &(data->chr))) {
+ return init;
+ } else {
+ goto NOTHING;
+ }
+ } else {
+ break;
+ }
+ case sub_mlist_node:
+ if (tex_aux_math_engine_control(null, math_control_analyze_script_nucleus_list)) {
+ init = kernel_math_list(init);
+ while (init) {
+ switch (node_type(init)) {
+ case kern_node:
+ case glue_node:
+ init = node_next(init);
+ break;
+ case simple_noad:
+ {
+ init = noad_nucleus(init);
+ if (node_type(init) != math_char_node) {
+ return null;
+ } else if (tex_aux_fetch(init, "script list", &(data->fnt), &(data->chr))) {
+ return init;
+ } else {
+ goto NOTHING;
+ }
+ }
+ default:
+ goto NOTHING;
+ }
+ }
+ }
+ break;
+ case sub_box_node:
+ if (tex_aux_math_engine_control(null, math_control_analyze_script_nucleus_box)) {
+ init = kernel_math_list(init);
+ if (init && node_type(init) == hlist_node) {
+ init = box_list(init);
+ }
+ while (init) {
+ switch (node_type(init)) {
+ case kern_node:
+ case glue_node:
+ init = node_next(init);
+ break;
+ case glyph_node:
+ if (tex_aux_fetch(init, "script box", &(data->fnt), &(data->chr))) {
+ return init;
+ } else {
+ goto NOTHING;
+ }
+ default:
+ goto NOTHING;
+ }
+ }
+ }
+ break;
+ }
+ }
+ NOTHING:
+ data->fnt = null;
+ data->chr = null;
+ return null;
+}
+
+/*tex
+
+ These prescripts are kind of special. For instance, should top and bottom scripts be aligned?
+ When there is are two top or two bottom, should we then just use the maxima?
+
+*/
+
+static void tex_aux_get_math_sup_shifts(halfword sup, halfword style, scaled *shift_up)
+{
+ switch (math_scripts_mode_par) {
+ case 1:
+ *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up);
+ break;
+ case 2:
+ *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up);
+ break;
+ case 3:
+ *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up)
+ + tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down)
+ - tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
+ break;
+ case 4:
+ *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up)
+ + tex_half_scaled(tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down)
+ - tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down));
+ break;
+ case 5:
+ *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up)
+ + tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down)
+ - tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
+ break;
+ default:
+ {
+ scaled clr = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up);
+ scaled bot = tex_get_math_y_parameter_checked(style, math_parameter_superscript_bottom_min);
+ if (*shift_up < clr) {
+ *shift_up = clr;
+ }
+ clr = box_depth(sup) + bot;
+ if (*shift_up < clr) {
+ *shift_up = clr;
+ }
+ break;
+ }
+ }
+}
+
+static void tex_aux_get_math_sub_shifts(halfword sub, halfword style, scaled *shift_down)
+{
+ switch (math_scripts_mode_par) {
+ case 1:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
+ break;
+ case 2:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
+ break;
+ case 3:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
+ break;
+ case 4:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down)
+ + tex_half_scaled(tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down)
+ - tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down)) ;
+ break;
+ case 5:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
+ break;
+ default:
+ {
+ scaled clr = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
+ scaled top = tex_get_math_y_parameter_checked(style, math_parameter_subscript_top_max);
+ if (*shift_down < clr) {
+ *shift_down = clr;
+ }
+ clr = box_height(sub) - top;
+ if (*shift_down < clr) {
+ *shift_down = clr;
+ }
+ break;
+ }
+ }
+}
+
+static void tex_aux_get_math_sup_sub_shifts(halfword sup, halfword sub, halfword style, scaled *shift_up, scaled *shift_down)
+{
+ switch (math_scripts_mode_par) {
+ case 1:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
+ break;
+ case 2:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
+ break;
+ case 3:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
+ break;
+ case 4:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down)
+ + tex_half_scaled(tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down)
+ - tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down));
+ break;
+ case 5:
+ *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
+ break;
+ default:
+ {
+ scaled clr = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
+ scaled gap = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_vgap);
+ scaled bot = tex_get_math_y_parameter_checked(style, math_parameter_superscript_subscript_bottom_max);
+ if (*shift_down < clr) {
+ *shift_down = clr;
+ }
+ clr = gap - ((*shift_up - box_depth(sup)) - (box_height(sub) - *shift_down));
+ if (clr > 0) {
+ *shift_down += clr;
+ clr = bot - (*shift_up - box_depth(sup));
+ if (clr > 0) {
+ *shift_up += clr;
+ *shift_down -= clr;
+ }
+ }
+ break;
+ }
+ }
+}
+
+static halfword tex_aux_combine_script(halfword target, halfword width, halfword pre, halfword post, halfword *k1, halfword *k2)
+{
+ *k1 = tex_new_kern_node(-(width + box_width(pre)), horizontal_math_kern_subtype);
+ *k2 = tex_new_kern_node(width, horizontal_math_kern_subtype);
+ tex_couple_nodes(*k1, pre);
+ tex_couple_nodes(pre, *k2);
+ if (post) {
+ tex_couple_nodes(*k2, post);
+ }
+ post = tex_hpack(*k1, 0, packing_additional, direction_unknown, holding_none_option);
+ tex_attach_attribute_list_copy(*k1, target);
+ tex_attach_attribute_list_copy(*k2, target);
+ tex_attach_attribute_list_copy(post, target);
+ node_subtype(post) = math_pre_post_list;
+ return post;
+}
+
+ /*tex
+
+ The following steps are involved:
+
+ We look at the subscript character (_i) or first character in a list (_{ij}). We look at the
+ superscript character (^i) or first character in a list (^{ij}).
+
+ Construct a superscript box |x|. The bottom of a superscript should never descend below the
+ baseline plus one-fourth of the x-height.
+
+ Construct a sub/superscript combination box |x|, with the superscript offset by |delta|. When
+ both subscript and superscript are present, the subscript must be separated from the superscript
+ by at least four times |preset_rule_thickness|. If this condition would be violated, the
+ subscript moves down, after which both subscript and superscript move up so that the bottom
+ of the superscript is at least as high as the baseline plus four-fifths of the x-height.
+
+ Now the horizontal shift for the superscript; the superscript is also to be shifted by |delta1|
+ (the italic correction).
+
+ Construct a subscript box |x| when there is no superscript. When there is a subscript without
+ a superscript, the top of the subscript should not exceed the baseline plus four-fifths of the
+ x-height.
+
+ We start with some helpers that deal with the staircase kerns in \OPENTYPE\ math.
+
+*/
+
+/*tex
+
+ This function tries to find the kern needed for proper cut-ins. The left side doesn't move, but
+ the right side does, so the first order of business is to create a staggered fence line on the
+ left side of the right character.
+
+ If the fonts for the left and right bits of a mathkern are not both new-style fonts, then return
+ a sentinel value meaning: please use old-style italic correction placement
+
+ This code is way to complex as it evolved stepwise and we wanted to keep the post scripts code
+ more or less the same. but ... I'll redo it.
+
+*/
+
+static scaled tex_aux_math_kern_at(halfword fnt, int chr, int side, int value)
+{
+ /*tex We know that the character exists. */
+ charinfo *ci = tex_get_charinfo(fnt, chr);
+ if (ci->math) {
+ scaled *kerns_heights;
+ int n_of_kerns = tex_get_charinfo_math_kerns(ci, side);
+ if (n_of_kerns == 0) {
+ switch (side) {
+ case top_left_kern:
+ return tex_char_top_left_kern_from_font(fnt, chr);
+ case bottom_left_kern:
+ return tex_char_bottom_left_kern_from_font(fnt, chr);
+ break;
+ case top_right_kern:
+ return tex_char_top_right_kern_from_font(fnt, chr);
+ case bottom_right_kern:
+ return tex_char_bottom_right_kern_from_font(fnt, chr);
+ default:
+ return 0;
+ }
+ } else {
+ switch (side) {
+ case top_left_kern:
+ kerns_heights = ci->math->top_left_math_kern_array;
+ break;
+ case bottom_left_kern:
+ kerns_heights = ci->math->bottom_left_math_kern_array;
+ break;
+ case top_right_kern:
+ kerns_heights = ci->math->top_right_math_kern_array;
+ break;
+ case bottom_right_kern:
+ kerns_heights = ci->math->bottom_right_math_kern_array;
+ break;
+ default:
+ /*tex Not reached: */
+ kerns_heights = NULL;
+ return tex_confusion("math kern at");
+ }
+ }
+ if (value < kerns_heights[0]) {
+ return kerns_heights[1];
+ } else {
+ scaled kern = 0;
+ for (int i = 0; i < n_of_kerns; i++) {
+ scaled height = kerns_heights[i * 2];
+ kern = kerns_heights[(i * 2) + 1];
+ if (height > value) {
+ return kern;
+ }
+ }
+ return kern;
+ }
+ } else {
+ return 0;
+ }
+}
+
+inline static scaled tex_aux_max_left_kern_value(scaled *kerns, int n)
+{
+ if (kerns && n > 0) {
+ scaled kern = 0;
+ for (int i = 0; i < n; i++) {
+ scaled value = kerns[(i * 2) + 1];
+ if (value < kern) {
+ kern = value;
+ }
+ }
+ return -kern;
+ } else {
+ return 0;
+ }
+}
+
+static scaled tex_aux_math_left_kern(halfword fnt, int chr)
+{
+ charinfo *ci = tex_get_charinfo(fnt, chr);
+ if (ci->math) {
+ scaled top = 0;
+ scaled bot = 0;
+ {
+ scaled *a = ci->math->top_left_math_kern_array;
+ halfword n = a ? tex_get_charinfo_math_kerns(ci, top_left_kern) : 0;
+ if (n) {
+ top = tex_aux_max_left_kern_value(a, n);
+ } else {
+ top = tex_char_top_left_kern_from_font(fnt, chr);
+ }
+ }
+ {
+ scaled *a = ci->math->bottom_left_math_kern_array;
+ halfword n = a ? tex_get_charinfo_math_kerns(ci, bottom_left_kern) : 0;
+ if (n) {
+ bot = tex_aux_max_left_kern_value(a, n);
+ } else {
+ bot = tex_char_bottom_left_kern_from_font(fnt, chr);
+ }
+ }
+ return top > bot ? top : bot;
+ } else {
+ return 0;
+ }
+}
+
+/*
+
+inline static scaled tex_aux_max_right_kern_value(scaled *kerns, int n)
+{
+ if (kerns && n > 0) {
+ scaled kern = 0;
+ for (int i = 0; i < n; i++) {
+ scaled value = kerns[(i * 2) + 1];
+ if (value > kern) {
+ kern = value;
+ }
+ }
+ return kern;
+ } else {
+ return 0;
+ }
+}
+
+static scaled tex_aux_math_right_kern(halfword fnt, int chr)
+{
+ charinfo *ci = tex_get_charinfo(fnt, chr);
+ if (ci->math) {
+ scaled top = 0;
+ scaled bot = 0;
+ {
+ scaled *a = ci->math->top_right_math_kern_array;
+ halfword n = a ? tex_get_charinfo_math_kerns(ci, top_right_kern) : 0;
+ if (n) {
+ top = tex_aux_max_right_kern_value(a, n);
+ } else {
+ top = tex_char_top_right_kern_from_font(fnt, chr);
+ }
+ }
+ {
+ scaled *a = ci->math->bottom_right_math_kern_array;
+ halfword n = a ? tex_get_charinfo_math_kerns(ci, bottom_right_kern) : 0;
+ if (n) {
+ bot = tex_aux_max_right_kern_value(a, n);
+ } else {
+ bot = tex_char_bottom_right_kern_from_font(fnt, chr);
+ }
+ }
+ return top > bot ? top : bot;
+ } else {
+ return 0;
+ }
+}
+*/
+
+static scaled tex_aux_find_math_kern(halfword l_f, int l_c, halfword r_f, int r_c, int cmd, scaled shift, int *found)
+{
+ if (tex_aux_math_engine_control(l_f, math_control_staircase_kern) &&
+ tex_aux_math_engine_control(r_f, math_control_staircase_kern) &&
+ /* tex_aux_has_opentype_metrics(l_f) && tex_aux_has_opentype_metrics(r_f) && */
+ tex_char_exists(l_f, l_c) && tex_char_exists(r_f, r_c)) {
+ scaled krn_l = 0;
+ scaled krn_r = 0;
+ scaled krn = 0;
+ switch (cmd) {
+ case superscript_cmd:
+ /*tex bottom of superscript */
+ {
+ scaled corr_height_top = tex_char_height_from_font(l_f, l_c);
+ scaled corr_height_bot = -tex_char_depth_from_font(r_f, r_c) + shift;
+ krn_l = tex_aux_math_kern_at(l_f, l_c, top_right_kern, corr_height_top);
+ krn_r = tex_aux_math_kern_at(r_f, r_c, bottom_left_kern, corr_height_top);
+ krn = krn_l + krn_r;
+ krn_l = tex_aux_math_kern_at(l_f, l_c, top_right_kern, corr_height_bot);
+ krn_r = tex_aux_math_kern_at(r_f, r_c, bottom_left_kern, corr_height_bot);
+ }
+ break;
+ case subscript_cmd:
+ /*tex top of subscript */
+ {
+ scaled corr_height_top = tex_char_height_from_font(r_f, r_c) - shift;
+ scaled corr_height_bot = -tex_char_depth_from_font(l_f, l_c);
+ krn_l = tex_aux_math_kern_at(l_f, l_c, bottom_right_kern, corr_height_top);
+ krn_r = tex_aux_math_kern_at(r_f, r_c, top_left_kern, corr_height_top);
+ krn = krn_l + krn_r;
+ krn_l = tex_aux_math_kern_at(l_f, l_c, bottom_right_kern, corr_height_bot);
+ krn_r = tex_aux_math_kern_at(r_f, r_c, top_left_kern, corr_height_bot);
+ }
+ break;
+ default:
+ return tex_confusion("find math kern");
+ }
+ *found = 1;
+ if ((krn_l + krn_r) < krn) {
+ krn = krn_l + krn_r;
+ }
+ return krn ? tex_aux_math_x_size_scaled(l_f, krn, lmt_math_state.size) : 0;
+ } else {
+ return MATH_KERN_NOT_FOUND;
+ }
+}
+
+static int tex_aux_get_sup_kern(halfword kernel, scriptdata *sup, scaled shift_up, scaled supshift, scaled *supkern, kernset *kerns)
+{
+ int found = 0;
+ *supkern = MATH_KERN_NOT_FOUND;
+ if (sup->node) {
+ *supkern = tex_aux_find_math_kern(glyph_font(kernel), glyph_character(kernel), sup->fnt, sup->chr, superscript_cmd, shift_up, &found);
+ if (*supkern == MATH_KERN_NOT_FOUND) {
+ *supkern = supshift;
+ } else {
+ if (*supkern) {
+ tex_aux_trace_kerns(*supkern, "superscript kern", "regular");
+ }
+ *supkern += supshift;
+ }
+ return found;
+ }
+ if (kerns && kerns->topright) {
+ *supkern = kerns->topright;
+ if (*supkern == MATH_KERN_NOT_FOUND) {
+ *supkern = supshift;
+ } else {
+ if (*supkern) {
+ tex_aux_trace_kerns(*supkern, "superscript kern", "kernset top right");
+ }
+ *supkern += supshift;
+ }
+ return found;
+ }
+ *supkern = supshift;
+ return found;
+}
+
+static int tex_aux_get_sub_kern(halfword kernel, scriptdata *sub, scaled shift_down, scaled subshift, scaled *subkern, kernset *kerns)
+{
+ int found = 0;
+ *subkern = MATH_KERN_NOT_FOUND;
+ if (sub->node) {
+ *subkern = tex_aux_find_math_kern(glyph_font(kernel), glyph_character(kernel), sub->fnt, sub->chr, subscript_cmd, shift_down, &found);
+ if (*subkern == MATH_KERN_NOT_FOUND) {
+ *subkern = subshift;
+ } else {
+ if (*subkern) {
+ tex_aux_trace_kerns(*subkern, "subscript kern", "regular");
+ }
+ *subkern += subshift;
+ }
+ return found;
+ }
+ if (kerns && kerns->bottomright) {
+ *subkern = kerns->bottomright;
+ if (*subkern == MATH_KERN_NOT_FOUND) {
+ *subkern = subshift;
+ } else {
+ if (*subkern) {
+ tex_aux_trace_kerns(*subkern, "superscript kern", "kernset bottom right");
+ }
+ *subkern += subshift;
+ }
+ return found;
+ }
+ *subkern = subshift;
+ return found;
+}
+
+/*tex
+
+ The code is quite ugly because these staircase kerns can only be calculated when we know the
+ heights and depths but when we pack the pre/post scripts we already relatiev position them so
+ we need to manipulate kerns. I need to figure out why we have slight rounding errors in the
+ realignments of prescripts. Anyway, because prescripts are not really part of \TEX\ we have
+ some freedom in dealing with them.
+
+ This code is now a bit too complex due to some (probably by now) redundant analysis so at some
+ point I will rewrite it.
+
+*/
+
+inline static scaled tex_aux_insert_italic_now(halfword target, halfword kernel, scaled italic)
+{
+ switch (node_type(noad_nucleus(target))) {
+ case math_char_node:
+ case math_text_char_node:
+ {
+ halfword fam = noad_family(noad_nucleus(target));
+ if (fam != unused_math_family) {
+ halfword fnt = tex_fam_fnt(fam, lmt_math_state.size);
+ if (! tex_aux_math_engine_control(fnt, math_control_apply_script_italic_kern)) {
+ /*tex We ignore the correction. */
+ italic = 0;
+ } else if (noad_subscr(target)) {
+ /*tex We will add the correction before the superscripts and/or primes. */
+ } else {
+ /*tex We can add the correction the kernel and then forget about it. */
+ tex_aux_math_insert_italic_kern(kernel, italic, noad_nucleus(target), "scripts");
+ italic = 0;
+ }
+ } else {
+ /*tex We have a weird case, so we ignore the correction. */
+ italic = 0;
+ }
+ }
+ break;
+ }
+ return italic;
+}
+
+static inline int tex_aux_raise_prime_composed(halfword target)
+{
+ int mainclass = -1 ;
+ /* maybe also mainclass */
+ switch (node_type(target)) {
+ case simple_noad:
+ mainclass = node_subtype(target);
+ break;
+ case radical_noad:
+ mainclass = radical_noad_subtype;
+ break;
+ case fraction_noad:
+ mainclass = fraction_noad_subtype;
+ break;
+ case accent_noad:
+ mainclass = accent_noad_subtype;
+ break;
+ case fence_noad:
+ /* we could be more granular and do open / close nut for now assume symmetry */
+ mainclass = fenced_noad_subtype;
+ break;
+ }
+ return mainclass >= 0 ? tex_math_has_class_option(mainclass, raise_prime_option) : 0;
+}
+
+static void tex_aux_make_scripts(halfword target, halfword kernel, scaled italic, int style, scaled supshift, scaled subshift, scaled supdrop, kernset *kerns)
+{
+ halfword result = null;
+ halfword preresult = null;
+ scaled prekern = 0;
+ scaled primekern = 0;
+ scaled shift_up = 0;
+ scaled shift_down = 0;
+ scaled prime_up = 0;
+ scriptdata postsubdata = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0 };
+ scriptdata postsupdata = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0 };
+ scriptdata presubdata = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0 };
+ scriptdata presupdata = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0 };
+ scriptdata primedata = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0 };
+ halfword maxleftkern = 0;
+ // halfword maxrightkern = 0;
+ scaled leftslack = 0;
+ scaled rightslack = 0;
+ scaledwhd kernelsize = { .wd = 0, .ht = 0, .dp = 0, .ic = 0 };
+ // scaled primewidth = 0;
+ scaled topovershoot = 0;
+ scaled botovershoot = 0;
+ int italicmultiplier = 1; /* This was a hard coded 2 so it needs more checking! */
+ int splitscripts = 0;
+ quarterword primestate = prime_unknown_location;
+ /*tex
+ This features was added when MS and I found that the Latin Modern (and other) fonts have
+ rather badly configured script (calligraphic) shapes. There is no provision for proper
+ anchoring subscripts and superscripts can overlap with for instance wide accents especially
+ when there is not much granularity in them. For that we now register the overshoot of
+ accents and compensate for them here.
+
+ One assumption is that the shape is somewhat italic and that an overshoot makes it even
+ more so. The two factors default to zero, so it only works when the right parameters are
+ set.
+
+ It's a mess. By adding more and more and also trying to be a bit like old \TEX\ we now have
+ too many kerns.
+
+ */
+ if (node_type(target) == accent_noad) {
+ scaled top = tex_get_math_parameter_default(style, math_parameter_accent_top_overshoot, 0);
+ scaled bot = tex_get_math_parameter_default(style, math_parameter_accent_bottom_overshoot, 0);
+ topovershoot = scaledround(accent_top_overshoot(target) * top / 100.0);
+ botovershoot = scaledround(accent_top_overshoot(target) * bot / 100.0);
+ }
+ /*tex
+ So this is somewhat weird. We pass the kernel and also some italic and then act upon the
+ target again. This is a bit messy side effect of the transition from old to new fonts. We
+ also have to make sure that we don't add the correction too soon, that is, before the
+ subscript.
+ */
+ if (italic) {
+ italic = tex_aux_insert_italic_now(target, kernel, italic);
+ }
+ /*tex
+ In some cases we need to split the scripts, for instance when we have fenced material that
+ can get split over lines.
+ */
+ if (node_type(target) == simple_noad) {
+ switch (node_subtype(target)) {
+ case fenced_noad_subtype:
+ splitscripts = tex_math_has_class_option(fenced_noad_subtype, unpack_class_option);
+ break;
+ case ghost_noad_subtype:
+ splitscripts = has_noad_option_unpacklist(target);
+ break;
+ }
+ }
+ /*tex
+ When we have a single character we need to deal with kerning based on staircase kerns, but
+ we also can have explicit kerns defined with single characters, which is more a \CONTEXT\
+ feature as it is not in \OPENTYPE\ fonts.
+ */
+ tex_aux_assign_new_hlist(target, kernel);
+ kernelsize = tex_natural_hsizes(kernel, null, 0.0, 0, 0);
+ if (kerns) {
+ /* todo: option */
+ if (kerns->height) {
+ kernelsize.ht = kerns->height;
+ }
+ if (kerns->depth) {
+ kernelsize.dp = kerns->depth;
+ }
+ }
+ switch (node_type(kernel)) {
+ case glyph_node:
+ postsubdata.node = tex_aux_analyze_script(noad_subscr(target), &postsubdata);
+ postsupdata.node = tex_aux_analyze_script(noad_supscr(target), &postsupdata);
+ primedata.node = tex_aux_analyze_script(noad_prime(target), &primedata);
+ maxleftkern = tex_aux_math_left_kern(glyph_font(kernel), glyph_character(kernel));
+ // maxrightkern = tex_aux_math_right_kern(glyph_font(kernel), glyph_character(kernel));
+ prime_up = tex_get_math_y_parameter_default(style, math_parameter_prime_shift_drop, 0);
+ shift_up = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_drop);
+ shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_drop);
+ break; // fallthrough
+ default:
+ kernelsize.ht -= supdrop; /* new */
+ prime_up = kernelsize.ht - tex_get_math_y_parameter_default(style, math_parameter_prime_shift_drop, 0);
+ shift_up = kernelsize.ht - tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_drop);
+ shift_down = kernelsize.dp + tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_drop);
+ break;
+ }
+ /*tex
+ Next we're doing some analysis, needed because of all these parameters than control horizontal and vertical
+ spacing. We start with primes.
+ */
+ if (noad_prime(target)) {
+ /* todo extra */
+ scaled shift = tex_get_math_y_parameter_default(style, math_parameter_prime_shift_up, 0);
+ scaled raise = tex_get_math_y_parameter_default(style, tex_aux_raise_prime_composed(target) ? math_parameter_prime_raise_composed : math_parameter_prime_raise, 0);
+ scaled distance = tex_get_math_x_parameter_default(style, math_parameter_prime_space_after, 0);
+ // scaled width = tex_get_math_x_parameter_default(style, math_parameter_prime_width, 0);
+ primedata.box = tex_aux_clean_box(noad_prime(target), (has_noad_option_nosupscript(target) ? style : tex_math_style_variant(style, math_parameter_prime_variant)), style, math_sup_list, 0, NULL);
+ box_shift_amount(primedata.box) -= prime_up ? prime_up : shift;
+ box_shift_amount(primedata.box) -= scaledround(box_height(primedata.box) * raise / 100.0);
+ kernel_math_list(noad_prime(target)) = null;
+ tex_flush_node(noad_prime(target));
+ noad_prime(target) = null;
+ if (noad_supscr(target)) {
+ primestate = prime_at_end_location;
+ } else if (noad_subscr(target)) {
+ primestate = prime_above_sub_location;
+ } else {
+ primestate = prime_at_begin_location;
+ }
+ if (distance) {
+ tex_aux_append_hkern_to_box_list(primedata.box, distance, horizontal_math_kern_subtype, "prime distance");
+ }
+ primedata.slack = distance;
+ switch (primestate) {
+ /* [prime] [super/sub] */
+ case prime_at_begin_location:
+ {
+ /* supshift ? */
+ tex_aux_get_sup_kern(kernel, &primedata, shift_up, supshift, &primekern, kerns);
+ if (italic) {
+ /* why no injection */
+ primekern += italic;
+ italic = 0;
+ }
+ }
+ break;
+ /* [prime/sub] [super] */
+ case prime_above_sub_location:
+ {
+ /* supshift ? */
+ tex_aux_get_sup_kern(kernel, &primedata, shift_up, supshift, &primekern, kerns);
+ if (italic) {
+ /* why no injection */
+ primekern += italic;
+ italic = 0;
+ }
+ if (primekern) {
+ tex_aux_prepend_hkern_to_box_list(primedata.box, primekern, math_shape_kern_subtype, "prime kern");
+ /* now width added */
+ primekern = 0; /* added */
+ }
+ }
+ break;
+ /* [super/sub] [prime] */
+ case prime_at_end_location:
+ {
+ primekern = 0;
+ }
+ break;
+ }
+ }
+ /*tex
+ Each of the scripts gets treated. Traditionally a super and subscript are looked and and
+ vercially spaced out together which in turn results in the staricase kerns needing that
+ information. Prescripts we handle differently: they are always aligned, so there the
+ maximum kern wins.
+ */
+ postsupdata.shifted = noad_supscr(target) && has_noad_option_shiftedsupscript(target);
+ postsubdata.shifted = noad_subscr(target) && has_noad_option_shiftedsubscript(target);
+ presupdata.shifted = noad_supprescr(target) && has_noad_option_shiftedsupprescript(target);
+ presubdata.shifted = noad_subprescr(target) && has_noad_option_shiftedsubprescript(target);
+ /*
+ When we have a shifted super or subscript (stored in the prescripts) we don't need to kern
+ the super and subscripts. What to do with the shifts?
+ */
+ if (noad_supscr(target)) {
+ halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_superscript_shift);
+ postsupdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_superscript_space);
+ postsupdata.slack += tex_get_math_x_parameter_checked(style, math_parameter_space_after_script);
+ postsupdata.box = tex_aux_clean_box(noad_supscr(target), (has_noad_option_nosupscript(target) ? style : tex_math_style_variant(style, math_parameter_superscript_variant)), style, math_sup_list, 0, NULL);
+ if (extra) {
+ box_height(postsupdata.box) += extra;
+ box_shift_amount(postsupdata.box) -= extra;
+ }
+ if (postsupdata.slack) {
+ tex_aux_append_hkern_to_box_list(postsupdata.box, postsupdata.slack, horizontal_math_kern_subtype, "post sup slack");
+ }
+ kernel_math_list(noad_supscr(target)) = null;
+ tex_flush_node(noad_supscr(target));
+ noad_supscr(target) = null;
+ }
+ if (noad_subscr(target)) {
+ halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_subscript_shift);
+ postsubdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_subscript_space);
+ postsubdata.slack += tex_get_math_x_parameter_checked(style, math_parameter_space_after_script);
+ postsubdata.box = tex_aux_clean_box(noad_subscr(target), (has_noad_option_nosubscript(target) ? style : tex_math_style_variant(style, math_parameter_subscript_variant)), style, math_sub_list, 0, NULL);
+ if (extra) {
+ box_depth(postsubdata.box) += extra;
+ box_shift_amount(postsubdata.box) += extra;
+ }
+ if (postsubdata.slack) {
+ tex_aux_append_hkern_to_box_list(postsubdata.box, postsubdata.slack, horizontal_math_kern_subtype, "post sub slack");
+ }
+ kernel_math_list(noad_subscr(target)) = null;
+ tex_flush_node(noad_subscr(target));
+ noad_subscr(target) = null;
+ }
+ if (noad_supprescr(target)) {
+ halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_superprescript_shift);
+ presupdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_superprescript_space);
+ presupdata.slack += tex_get_math_x_parameter_default(style, math_parameter_space_before_script, 0);
+ presupdata.box = tex_aux_clean_box(noad_supprescr(target), (has_noad_option_nosupprescript(target) ? style : tex_math_style_variant(style, math_parameter_superscript_variant)), style, math_sup_list, 0, NULL);
+ if (maxleftkern) {
+ tex_aux_append_hkern_to_box_list(presupdata.box, maxleftkern, math_shape_kern_subtype, "max left shape");
+ }
+ if (extra) {
+ box_height(presupdata.box) += extra;
+ box_shift_amount(presupdata.box) -= extra;
+ }
+ if (presupdata.slack) {
+ tex_aux_prepend_hkern_to_box_list(presupdata.box, presupdata.slack, horizontal_math_kern_subtype, "pre sup slack");
+ }
+ kernel_math_list(noad_supprescr(target)) = null;
+ tex_flush_node(noad_supprescr(target));
+ noad_supprescr(target) = null;
+ }
+ if (noad_subprescr(target)) {
+ halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_subprescript_shift);
+ presubdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_subprescript_space);
+ presubdata.slack += tex_get_math_x_parameter_default(style, math_parameter_space_before_script, 0);
+ presubdata.box = tex_aux_clean_box(noad_subprescr(target), (has_noad_option_nosubprescript(target) ? style : tex_math_style_variant(style, math_parameter_subscript_variant)), style, math_sub_list, 0, NULL);
+ if (maxleftkern) {
+ tex_aux_append_hkern_to_box_list(presubdata.box, maxleftkern, math_shape_kern_subtype, "max left shape");
+ }
+ if (extra) {
+ box_depth(presubdata.box) += extra;
+ box_shift_amount(presubdata.box) += extra;
+ }
+ if (presubdata.slack) {
+ tex_aux_prepend_hkern_to_box_list(presubdata.box, presubdata.slack, horizontal_math_kern_subtype, "pre sub slack");
+ }
+ kernel_math_list(noad_subprescr(target)) = null;
+ tex_flush_node(noad_subprescr(target));
+ noad_subprescr(target) = null;
+ }
+ /*tex
+ When we're here, the kerns are in the boxes. We now register the state of scripts in the
+ noad for (optional) later usage.
+ */
+ if (presupdata.box) {
+ noad_script_state(target) |= pre_super_script_state;
+ }
+ if (presubdata.box) {
+ noad_script_state(target) |= pre_sub_script_state;
+ }
+ if (postsupdata.box) {
+ noad_script_state(target) |= post_super_script_state;
+ }
+ if (postsubdata.box) {
+ noad_script_state(target) |= post_sub_script_state;
+ }
+ if (primedata.box) {
+ noad_script_state(target) |= prime_script_state;
+ }
+ /* */
+ if (primestate == prime_above_sub_location) {
+ rightslack = box_width(primedata.box) > box_width(postsubdata.box) ? primedata.slack : postsubdata.slack;
+ } else if (postsupdata.box) {
+ if (postsubdata.box) {
+ /* todo: take deltas */
+ rightslack = box_width(postsupdata.box) > box_width(postsubdata.box) ? postsupdata.slack : postsubdata.slack;
+ } else {
+ rightslack = postsupdata.slack;
+ }
+ } else if (postsubdata.box) {
+ rightslack = postsubdata.slack;
+ }
+
+ if (primestate == prime_above_sub_location) {
+ halfword list = noad_new_hlist(target);
+ if (list) {
+ /*tex We want to keep the size for tracing! */
+ halfword overshoot = box_width(primedata.box) - box_width(postsubdata.box);
+ halfword primebox = tex_hpack(primedata.box, 0, packing_additional, direction_unknown, holding_none_option);
+ tex_attach_attribute_list_copy(primebox, primedata.box);
+ box_width(primebox) = 0;
+ tex_couple_nodes(tex_tail_of_node_list(list), primebox);
+ primedata.box = null;
+ if (overshoot > 0) {
+ tex_aux_append_hkern_to_box_list(postsubdata.box, overshoot, math_shape_kern_subtype, "prime overshoot kern");
+ }
+ } else {
+ list = primedata.box;
+ }
+ noad_new_hlist(target) = list;
+ }
+
+ if (presupdata.box) {
+ if (presubdata.box) {
+ /* todo: take deltas */
+ leftslack = box_width(presupdata.box) > box_width(presubdata.box) ? presupdata.slack : presubdata.slack;
+ } else {
+ leftslack = presupdata.slack;
+ }
+ } else if (presubdata.box) {
+ leftslack = presubdata.slack;
+ }
+ switch (primestate) {
+ case prime_at_begin_location:
+ kernelsize.wd += box_width(primedata.box);
+ break;
+ case prime_above_sub_location:
+ /* only excess */
+ break;
+ }
+ if (postsupdata.box || postsubdata.box) {
+ /*tex
+ The post scripts determine the shifts. An option can be to use the max of pre/post.
+ */
+ scaled supkern = 0;
+ scaled subkern = 0;
+ if (! splitscripts) {
+ if (presupdata.box) {
+ prekern = box_width(presupdata.box);
+ postsupdata.box = tex_aux_combine_script(target, kernelsize.wd, presupdata.box, postsupdata.box, &presupdata.kern, &postsupdata.kern);
+ presupdata.box = null;
+ }
+ if (presubdata.box) {
+ // test: what with negative extra kerns and what with a negative width
+ if (box_width(presubdata.box) > prekern) {
+ prekern = box_width(presubdata.box);
+ }
+ postsubdata.box = tex_aux_combine_script(target, kernelsize.wd, presubdata.box, postsubdata.box, &presubdata.kern, &postsubdata.kern);
+ presubdata.box = null;
+ }
+ }
+ /*tex
+ We want to retain the kern because it is a visual thing but it could be an option to
+ only add the excess over the shift. We're talking tiny here.
+
+ We could be clever and deal with combinations of shifted but lets play safe and let
+ the user worry about it. The sub index always wins.
+ */
+ if (postsubdata.box && postsupdata.shifted) {
+ halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_subscript_shift_distance);
+ halfword amount = box_width(postsupdata.box) + shift;
+ tex_aux_prepend_hkern_to_box_list(postsubdata.box, amount, horizontal_math_kern_subtype, "post shifted");
+ } else if (postsupdata.box && postsubdata.shifted) {
+ halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_superscript_shift_distance);
+ halfword amount = box_width(postsubdata.box) + shift;
+ tex_aux_prepend_hkern_to_box_list(postsupdata.box, amount, horizontal_math_kern_subtype, "post shifted");
+ }
+ if (presubdata.box && presupdata.shifted) {
+ halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_subprescript_shift_distance);
+ halfword amount = box_width(presupdata.box) + shift;
+ tex_aux_append_hkern_to_box_list(presubdata.box, amount, horizontal_math_kern_subtype, "pre shifted");
+ } else if (presupdata.box && presubdata.shifted) {
+ halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_superprescript_shift_distance);
+ halfword amount = box_width(presubdata.box) + shift;
+ tex_aux_append_hkern_to_box_list(presupdata.box, amount, horizontal_math_kern_subtype, "pre shifted");
+ }
+ /* */
+ if (postsupdata.box) {
+ tex_aux_get_math_sup_shifts(postsupdata.box, style, &shift_up);
+ if (postsubdata.box) {
+ tex_aux_get_math_sup_sub_shifts(postsupdata.box, postsubdata.box, style, &shift_up, &shift_down);
+ tex_aux_get_sup_kern(kernel, &postsupdata, shift_up, supshift, &supkern, kerns);
+ tex_aux_get_sub_kern(kernel, &postsubdata, shift_down, subshift, &subkern, kerns);
+ if (primestate == prime_at_begin_location) {
+ primekern += supkern ;
+ subkern = 0;
+ supkern = 0;
+ } else {
+ if (supkern) {
+ tex_aux_prepend_hkern_to_box_list(postsupdata.box, supkern, math_shape_kern_subtype, "post sup shape");
+ }
+ if (subkern) {
+ tex_aux_prepend_hkern_to_box_list(postsubdata.box, subkern, math_shape_kern_subtype, "post sub shape");
+ }
+ }
+ if (italic) {
+ tex_aux_prepend_hkern_to_box_list(postsupdata.box, italic, italic_kern_subtype, "italic");
+ }
+ if (presubdata.kern) {
+ kern_amount(presubdata.kern) += -subkern;
+ kern_amount(postsubdata.kern) += subkern;
+ }
+ if (presupdata.kern) {
+ /* italic needs checking */
+ kern_amount(presupdata.kern) += -supkern - italicmultiplier * italic;
+ kern_amount(postsupdata.kern) += supkern + italicmultiplier * italic;
+ }
+ {
+ halfword kern = tex_new_kern_node((shift_up - box_depth(postsupdata.box)) - (box_height(postsubdata.box) - shift_down), vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, target);
+ tex_couple_nodes(postsupdata.box, kern);
+ tex_couple_nodes(kern, postsubdata.box);
+ result = tex_vpack(postsupdata.box, 0, packing_additional, max_dimen, (singleword) math_direction_par, holding_none_option);
+ tex_attach_attribute_list_copy(result, target);
+ node_subtype(result) = math_scripts_list;
+ box_shift_amount(result) = shift_down;
+ }
+ } else {
+ tex_aux_get_sup_kern(kernel, &postsupdata, shift_up, supshift, &supkern, kerns);
+ if (primestate == prime_at_begin_location) {
+ primekern += supkern ;
+ supkern = 0;
+ } else if (supkern) {
+ tex_aux_prepend_hkern_to_box_list(postsupdata.box, supkern, math_shape_kern_subtype, "post sup shape");
+ }
+ box_shift_amount(postsupdata.box) = -shift_up;
+ result = postsupdata.box;
+ if (presupdata.kern) {
+ kern_amount(presupdata.kern) += -supkern - subkern - italicmultiplier * italic;
+ kern_amount(postsupdata.kern) += supkern + subkern + italicmultiplier * italic;
+ }
+ }
+ } else {
+ tex_aux_get_math_sub_shifts(postsubdata.box, style, &shift_down);
+ tex_aux_get_sub_kern(kernel, &postsubdata, shift_down, subshift, &subkern, kerns);
+ if (primestate == prime_at_begin_location) {
+ subkern = 0;
+ } else if (subkern) {
+ tex_aux_prepend_hkern_to_box_list(postsubdata.box, subkern, math_shape_kern_subtype, "post sub shape");
+ }
+ box_shift_amount(postsubdata.box) = shift_down;
+ result = postsubdata.box;
+ if (presubdata.kern) {
+ kern_amount(presubdata.kern) += -subkern;
+ kern_amount(postsubdata.kern) += subkern;
+ }
+ }
+ /* */
+ if (! splitscripts) {
+ if (topovershoot) {
+ /* todo: tracing */
+ if (noad_script_state(target) & pre_super_script_state) {
+ kern_amount(postsubdata.kern) -= topovershoot;
+ kern_amount(postsupdata.kern) -= topovershoot;
+ }
+ if (noad_script_state(target) & post_sub_script_state) {
+ kern_amount(presupdata.kern) += topovershoot;
+ }
+ }
+ if (botovershoot) {
+ /* todo: tracing, yet untested */
+ if (noad_script_state(target) & pre_sub_script_state) {
+ kern_amount(presubdata.kern) -= botovershoot;
+ kern_amount(presupdata.kern) -= botovershoot;
+ }
+ if (noad_script_state(target) & post_sub_script_state) {
+ kern_amount(presubdata.kern) += botovershoot;
+ }
+ }
+ goto PICKUP;
+ }
+ }
+ if (presubdata.box) {
+ if (presupdata.box) {
+ tex_aux_get_math_sup_shifts(presupdata.box, style, &shift_up);
+ tex_aux_get_math_sup_sub_shifts(presupdata.box, presubdata.box, style, &shift_up, &shift_down);
+ prekern = box_width(presupdata.box);
+ // test: what with negative extra kerns and what with a negative width
+ if (! splitscripts) {
+ if (box_width(presubdata.box) > prekern) {
+ prekern = box_width(presubdata.box);
+ }
+ presupdata.box = tex_aux_combine_script(target, kernelsize.wd, presupdata.box, null, &presupdata.kern, &postsupdata.kern);
+ presubdata.box = tex_aux_combine_script(target, kernelsize.wd, presubdata.box, null, &presubdata.kern, &postsubdata.kern);
+ }
+ {
+ halfword k = tex_new_kern_node((shift_up - box_depth(presupdata.box)) - (box_height(presubdata.box) - shift_down), vertical_math_kern_subtype);
+ tex_attach_attribute_list_copy(k, target);
+ tex_couple_nodes(presupdata.box, k);
+ tex_couple_nodes(k, presubdata.box);
+ preresult = tex_vpack(presupdata.box, 0, packing_additional, max_dimen, (singleword) math_direction_par, holding_none_option);
+ tex_attach_attribute_list_copy(preresult, target);
+ node_subtype(preresult) = math_scripts_list;
+ box_shift_amount(preresult) = shift_down;
+ }
+ } else {
+ tex_aux_get_math_sub_shifts(presubdata.box, style, &shift_down);
+ if (! splitscripts) {
+ prekern = box_width(presubdata.box);
+ presubdata.box = tex_aux_combine_script(target, kernelsize.wd, presubdata.box, null, &presubdata.kern, &postsubdata.kern);
+ }
+ box_shift_amount(presubdata.box) = shift_down;
+ preresult = presubdata.box;
+ }
+ } else if (presupdata.box) {
+ tex_aux_get_math_sup_shifts(presupdata.box, style, &shift_up);
+ if (! splitscripts) {
+ prekern = box_width(presupdata.box);
+ presupdata.box = tex_aux_combine_script(target, kernelsize.wd, presupdata.box, null, &presupdata.kern, &postsupdata.kern);
+ }
+ box_shift_amount(presupdata.box) = -shift_up;
+ preresult = presupdata.box;
+ }
+ PICKUP:
+ if (primestate == prime_at_begin_location) {
+ halfword list = noad_new_hlist(target);
+ if (primekern) {
+ tex_aux_prepend_hkern_to_box_list(primedata.box, primekern, math_shape_kern_subtype, "prime");
+ }
+ if (list) {
+ tex_couple_nodes(tex_tail_of_node_list(list), primedata.box);
+ } else {
+ list = primedata.box;
+ }
+ noad_new_hlist(target) = list;
+ }
+ if (splitscripts) {
+ halfword list = noad_new_hlist(target);
+ if (preresult) {
+ if (list) {
+ tex_couple_nodes(preresult, list);
+ }
+ list = preresult;
+ }
+ if (result) {
+ if (list) {
+ tex_couple_nodes(tex_tail_of_node_list(list), result);
+ } else {
+ list = result;
+ }
+ }
+ noad_new_hlist(target) = list;
+ } else {
+ if (preresult) {
+ result = preresult;
+ }
+ if (prekern) {
+ /* must become horizontal kern */
+ halfword list = tex_aux_prepend_hkern_to_new_hlist(target, prekern, horizontal_math_kern_subtype, "pre compensation");
+ tex_couple_nodes(tex_tail_of_node_list(list), result);
+ } else if (noad_new_hlist(target)) {
+ tex_couple_nodes(tex_tail_of_node_list(noad_new_hlist(target)), result);
+ } else {
+ noad_new_hlist(target) = result;
+ }
+ }
+ if (primestate == prime_at_end_location) {
+ tex_couple_nodes(tex_tail_of_node_list(result), primedata.box);
+ rightslack = primedata.slack;
+ }
+ if (math_slack_mode_par > 0) {
+ noad_left_slack(target) = leftslack;
+ noad_right_slack(target) = rightslack;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: script slack, left %D, right %D]", leftslack, pt_unit, rightslack, pt_unit);
+ tex_end_diagnostic();
+ }
+ }
+}
+
+/*tex
+
+ The |make_left_right| function constructs a left or right delimiter of the required size and
+ returns the value |open_noad| or |close_noad|. The |left_noad_side| and |right_noad_side| will
+ both be based on the original |style|, so they will have consistent sizes.
+
+*/
+
+static halfword tex_aux_make_left_right(halfword target, int style, scaled max_d, scaled max_h, int size, delimiterextremes *extremes)
+{
+ halfword tmp;
+ scaled ic = 0;
+ int stack = 0;
+ halfword mainclass = get_noad_main_class(target);
+ halfword leftclass = get_noad_left_class(target);
+ halfword rightclass = get_noad_right_class(target);
+ scaled height = tex_aux_math_given_y_scaled(noad_height(target));
+ scaled depth = tex_aux_math_given_y_scaled(noad_depth(target));
+ int leftoperator = node_type(target) == fence_noad && node_subtype(target) == left_operator_side;
+ if (extremes) {
+ extremes->tfont = null_font;
+ extremes->bfont = null_font;
+ extremes->tchar = 0;
+ extremes->tchar = 0;
+ extremes->height = 0;
+ extremes->depth = 0;
+ }
+ tex_aux_set_current_math_size(style);
+ if (height || depth || has_noad_option_exact(target)) {
+ halfword lst;
+ scaled delta = height + depth;
+ tmp = tex_aux_make_delimiter(target, fence_delimiter_list(target), size, delta, 0, style, 0, &stack, &ic, 0, has_noad_option_nooverflow(target), extremes, 0);
+/* do extremes here */
+ noad_italic(target) = ic;
+ /*tex
+ Beware, a stacked delimiter has a shift but no corrected height/depth (yet).
+ */
+ if (stack) {
+ box_shift_amount(tmp) = depth;
+ }
+ if (has_noad_option_exact(target)) {
+ height = box_height(tmp) - box_shift_amount(tmp);
+ depth = box_depth(tmp) + box_shift_amount(tmp);
+ }
+ if (has_noad_option_axis(target)) {
+ halfword axis = tex_aux_math_axis(size);
+ height += axis;
+ depth -= axis;
+ box_shift_amount(tmp) -= axis;
+ }
+ lst = tex_new_node(hlist_node, 0);
+ tex_attach_attribute_list_copy(lst, target);
+ box_dir(lst) = dir_lefttoright ;
+ box_height(lst) = height;
+ box_depth(lst) = depth;
+ box_width(lst) = box_width(tmp);
+ box_list(lst) = tmp;
+ tmp = lst;
+ } else {
+ int axis = ! has_noad_option_noaxis(target);
+ scaled delta = 0;
+ if (leftoperator && has_noad_option_auto(target)) {
+ /*tex Todo: option for skipping this. */
+ if (style < text_style) {
+ scaled s = scaledround(tex_get_math_parameter(style, math_parameter_operator_size, NULL));
+ if (s > max_h + max_d) {
+ max_h = scaledround(s / 2.0);
+ max_d = max_h;
+ delta = max_h + max_d;
+ }
+ }
+ }
+ if (! delta) {
+ delta = tex_aux_get_delimiter_height(max_h, max_d, axis, size, style); // todo: pass scaled axis
+ }
+ tmp = tex_aux_make_delimiter(target, fence_delimiter_list(target), size, delta, 0, style, axis, &stack, &ic, 0, has_noad_option_nooverflow(target), extremes, 0);
+ }
+ /* delimiter is wiped */
+ noad_height(target) = height;
+ noad_depth(target) = depth;
+ fence_delimiter_list(target) = null;
+ noad_italic(target) = ic;
+ /* */
+ if (noad_source(target)) {
+ box_source_anchor(tmp) = noad_source(target);
+ // box_anchor(tmp) = left_origin_anchor;
+ tex_set_box_geometry(tmp, anchor_geometry);
+ }
+ /* */
+ if (leftoperator) {
+ halfword s = tex_new_node(sub_box_node, 0);
+ kernset kerns;
+ tex_math_wipe_kerns(&kerns);
+ tex_flush_node_list(noad_supscr(target));
+ tex_flush_node_list(noad_subscr(target));
+ tex_flush_node_list(noad_nucleus(target));
+ if (kernel_math_list(fence_delimiter_top(target))) {
+ noad_supscr(target) = fence_delimiter_top(target);
+ fence_delimiter_top(target) = null;
+ }
+ if (kernel_math_list(fence_delimiter_bottom(target))) {
+ noad_subscr(target) = fence_delimiter_bottom(target);
+ fence_delimiter_bottom(target) = null;
+ }
+ kernel_math_list(s) = tmp;
+ noad_nucleus(target) = s;
+ /* maybe elsewhere as the above case */
+ if (extremes && extremes->tfont) {
+ if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_top_kern_class_option)) {
+ kerns.topright = tex_char_top_right_kern_from_font(extremes->tfont, extremes->tchar);
+ }
+ if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_bottom_kern_class_option)) {
+ kerns.bottomright = tex_char_bottom_right_kern_from_font(extremes->bfont, extremes->bchar);
+ }
+ if (tex_math_has_class_option(fenced_noad_subtype, prefer_delimiter_dimensions_class_option)) {
+ kerns.height = extremes->height;
+ kerns.depth = extremes->depth;
+ }
+ }
+ tex_aux_make_op(target, style, size, ic, limits_unknown_mode, &kerns);
+ /* otherwise a leak: */
+ kernel_math_list(s) = null;
+ tex_flush_node(s);
+ } else {
+ tex_aux_assign_new_hlist(target, tmp);
+ }
+ /* */
+ switch (node_subtype(target)) {
+ case left_fence_side:
+ if (leftclass != unset_noad_class) {
+ return leftclass;
+ } else if (mainclass != unset_noad_class) {
+ return mainclass;
+ } else {
+ return open_noad_subtype;
+ }
+ case middle_fence_side:
+ if (mainclass != unset_noad_class) {
+ return mainclass;
+ } else {
+ return middle_noad_subtype;
+ }
+ case right_fence_side:
+ if (rightclass != unset_noad_class) {
+ return rightclass;
+ } else if (mainclass != unset_noad_class) {
+ return mainclass;
+ } else {
+ return close_noad_subtype;
+ }
+ case left_operator_side:
+ if (leftclass != unset_noad_class) {
+ return leftclass;
+ } else if (mainclass != unset_noad_class) {
+ return mainclass;
+ } else {
+ return operator_noad_subtype;
+ }
+ default:
+ if (mainclass != unset_noad_class) {
+ return mainclass;
+ } else {
+ /*tex So one can best set the class! */
+ return ordinary_noad_subtype;
+ }
+ }
+}
+
+inline static int tex_aux_fallback_math_spacing_class(halfword style, halfword class)
+{
+ unsigned parent = (unsigned) count_parameter(first_math_class_code + class);
+ switch (style) {
+ case display_style: case cramped_display_style: return (parent >> 24) & 0xFF;
+ case text_style: case cramped_text_style: return (parent >> 16) & 0xFF;
+ case script_style: case cramped_script_style: return (parent >> 8) & 0xFF;
+ case script_script_style: case cramped_script_script_style: return (parent >> 0) & 0xFF;
+ default: return 0;
+ }
+}
+
+static halfword tex_aux_math_spacing_glue(halfword ltype, halfword rtype, halfword style, scaled mmu)
+{
+ halfword c = tex_to_math_spacing_parameter(ltype, rtype);
+ halfword s = c;
+ for (int i = 1; i <= 2; i++) {
+ if (s >= 0) {
+ halfword d = 0;
+ halfword x = tex_get_math_parameter(style, s, &d);
+ if (x) {
+ switch (d) {
+ case no_val_level:
+ break;
+ case dimen_val_level:
+ if (x) {
+ x = tex_aux_math_dimen(x, inter_math_skip_glue, c);
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: inter atom kern, left %n, right %n, resolved %i, amount %D]", ltype, rtype, s, kern_amount(x), pt_unit);
+ tex_end_diagnostic();
+ }
+ return x;
+ }
+ goto NONE;
+ case glue_val_level:
+ if (! tex_glue_is_zero(x)) {
+ x = tex_aux_math_glue(x, inter_math_skip_glue, c);
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: inter atom glue, left %n, right %n, resolved %i, amount %P]", ltype, rtype, s, glue_amount(x), glue_stretch(x), NULL, NULL, NULL, glue_shrink(x));
+ tex_end_diagnostic();
+ }
+ return x;
+ }
+ goto NONE;
+ case mu_val_level:
+ if (! tex_math_glue_is_zero(x)) {
+ x = tex_aux_math_muglue(x, inter_math_skip_glue, mmu, c, style);
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: inter atom (mu) glue, left %n, right %n, resolved %i, amount %P]", ltype, rtype, s, glue_amount(x), glue_stretch(x), NULL, NULL, NULL, glue_shrink(x));
+ tex_end_diagnostic();
+ }
+ return x;
+ }
+ goto NONE;
+ default:
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: inter atom (mu) glue, left %n, right %n, resolved %i, unset]", ltype, rtype, s);
+ tex_end_diagnostic();
+ }
+ goto NONE;
+ }
+ }
+ /* try again */
+ {
+ halfword lparent = tex_aux_fallback_math_spacing_class(style, ltype);
+ halfword rparent = tex_aux_fallback_math_spacing_class(style, rtype);
+ /*tex Let's try the parents (one level). */
+ if (lparent != ltype || rparent != rtype) {
+ s = tex_to_math_spacing_parameter(lparent, rtype);
+ if (tex_has_math_parameter(style, s)) {
+ goto FOUND;
+ }
+ s = tex_to_math_spacing_parameter(ltype, rparent);
+ if (tex_has_math_parameter(style, s)) {
+ goto FOUND;
+ }
+ s = tex_to_math_spacing_parameter(lparent, rparent);
+ if (tex_has_math_parameter(style, s)) {
+ goto FOUND;
+ }
+ }
+ /*tex We fall back on the |all| classes. */
+ s = tex_to_math_spacing_parameter(ltype, math_all_class);
+ if (tex_has_math_parameter(style, s)) {
+ goto FOUND;
+ }
+ s = tex_to_math_spacing_parameter(math_all_class, rtype);
+ if (tex_has_math_parameter(style, s)) {
+ goto FOUND;
+ }
+ s = tex_to_math_spacing_parameter(lparent, math_all_class);
+ if (tex_has_math_parameter(style, s)) {
+ goto FOUND;
+ }
+ s = tex_to_math_spacing_parameter(math_all_class, rparent);
+ if (tex_has_math_parameter(style, s)) {
+ goto FOUND;
+ }
+ /*tex Now we're lost. */
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: inter atom fallback, left %n, right %n, left parent %n, right parent %n, not resolved]", ltype, rtype, lparent, rparent);
+ tex_end_diagnostic();
+ }
+ goto NONE;
+ FOUND:
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: inter atom fallback, left %n, right %n, left parent %n, right parent %n, resolved %i]", ltype, rtype, lparent, rparent, s);
+ tex_end_diagnostic();
+ }
+ }
+ } else {
+ /* tex_confusion("math atom spacing"); */
+ goto NONE;
+ }
+ }
+ NONE:
+ if (math_spacing_mode_par && c >= 0) {
+ if (math_spacing_mode_par == 1 && (ltype == math_begin_class || rtype == math_end_class)) {
+ return null;
+ } else {
+ return tex_aux_math_dimen(0, inter_math_skip_glue, c);
+ }
+ } else {
+ return null;
+ }
+}
+
+inline static int tex_aux_fallback_math_ruling_class(halfword style, halfword class)
+{
+ unsigned parent = (unsigned) count_parameter(first_math_atom_code + class);
+ switch (style) {
+ case display_style: case cramped_display_style: return (parent >> 24) & 0xFF;
+ case text_style: case cramped_text_style: return (parent >> 16) & 0xFF;
+ case script_style: case cramped_script_style: return (parent >> 8) & 0xFF;
+ case script_script_style: case cramped_script_script_style: return (parent >> 0) & 0xFF;
+ default: return 0;
+ }
+}
+
+static halfword tex_aux_math_ruling(halfword ltype, halfword rtype, halfword style)
+{
+ halfword c = tex_to_math_rules_parameter(ltype, rtype);
+ halfword s = c;
+ for (int i = 1; i <= 2; i++) {
+ if (s >= 0) {
+ halfword x = tex_get_math_parameter(style, s, NULL);
+ if (x != MATHPARAMDEFAULT) {
+ return x;
+ } else {
+ halfword lparent = tex_aux_fallback_math_ruling_class(style, ltype);
+ halfword rparent = tex_aux_fallback_math_ruling_class(style, rtype);
+ if (lparent != ltype || rparent != rtype) {
+ s = tex_to_math_rules_parameter(lparent, rparent);
+ } else {
+ return MATHPARAMDEFAULT;
+ }
+ }
+ } else {
+ return MATHPARAMDEFAULT;
+ }
+ }
+ return MATHPARAMDEFAULT;
+}
+
+halfword tex_math_spacing_glue(halfword ltype, halfword rtype, halfword style)
+{
+ halfword mu = tex_get_math_quad_size_scaled(lmt_math_state.size);
+ halfword sg = tex_aux_math_spacing_glue(ltype, rtype, style, mu);
+ if (node_type(sg) == glue_node) {
+ tex_add_glue_option(sg, glue_option_no_auto_break);
+ }
+ return sg;
+}
+
+/*tex
+
+ This is a bit complex function and it can beter be merged into the caller and be more specific
+ there. The delta parameter can have a value already. When it keeps it value the caller can add
+ is as italic correction. However, when we have no scripts we do it here.
+
+ Also, in some cases a new glyph is made while we alredy have one. The fetch routine also sets
+ |lmt_math_state.opentype| so we can use it here. The complexity of the muxed machinery makes
+ this complexity test also complex.
+
+*/
+
+static halfword tex_aux_check_nucleus_complexity(halfword target, scaled *italic, halfword style, halfword size, kernset *kerns)
+{
+ halfword nucleus = noad_nucleus(target);
+ if (nucleus) {
+ if (italic) {
+ *italic = 0;
+ }
+ switch (node_type(nucleus)) {
+ case math_char_node:
+ case math_text_char_node:
+ {
+ halfword chr = null;
+ halfword fnt = null;
+ if (tex_aux_fetch(nucleus, "(text) char", &fnt, &chr)) {
+ /*tex We make a math glyph from an ordinary one. */
+ quarterword subtype = 0;
+ switch (node_subtype(nucleus)) {
+ case ordinary_noad_subtype: subtype = glyph_math_ordinary_subtype; break;
+ case operator_noad_subtype: subtype = glyph_math_operator_subtype; break;
+ case binary_noad_subtype: subtype = glyph_math_binary_subtype; break;
+ case relation_noad_subtype: subtype = glyph_math_relation_subtype; break;
+ case open_noad_subtype: subtype = glyph_math_open_subtype; break;
+ case close_noad_subtype: subtype = glyph_math_close_subtype; break;
+ case punctuation_noad_subtype: subtype = glyph_math_punctuation_subtype; break;
+ case variable_noad_subtype: subtype = glyph_math_variable_subtype; break;
+ case active_noad_subtype: subtype = glyph_math_active_subtype; break;
+ case inner_noad_subtype: subtype = glyph_math_inner_subtype; break;
+ case over_noad_subtype: subtype = glyph_math_over_subtype; break;
+ case under_noad_subtype: subtype = glyph_math_under_subtype; break;
+ case fraction_noad_subtype: subtype = glyph_math_fraction_subtype; break;
+ case radical_noad_subtype: subtype = glyph_math_radical_subtype; break;
+ case middle_noad_subtype: subtype = glyph_math_middle_subtype; break;
+ case accent_noad_subtype: subtype = glyph_math_accent_subtype; break;
+ case fenced_noad_subtype: subtype = glyph_math_fenced_subtype; break;
+ case ghost_noad_subtype: subtype = glyph_math_ghost_subtype; break;
+ default:
+ if (node_subtype(nucleus) < math_begin_class) {
+ /*tex
+ So at least we can recongize them and have some slack for
+ new ones below this boundary. Nicer would be to be in range
+ but then we have to ditch the normal glyph subtypes. Maybe
+ we should move all classes above this edge.
+ */
+ subtype = glyph_math_extra_subtype + node_subtype(nucleus);
+ }
+ break;
+
+ }
+ halfword glyph = tex_aux_new_math_glyph(fnt, chr, subtype);
+ tex_attach_attribute_list_copy(glyph, nucleus);
+ if (node_type(nucleus) == math_char_node) {
+ glyph_properties(glyph) = kernel_math_properties(nucleus);
+ glyph_group(glyph) = kernel_math_group(nucleus);
+ glyph_index(glyph) = kernel_math_index(nucleus);
+ if (math_kernel_node_has_option(nucleus, math_kernel_auto_discretionary)) {
+ tex_add_glyph_option(glyph, glyph_option_math_discretionary);
+ }
+ if (math_kernel_node_has_option(nucleus, math_kernel_full_discretionary)) {
+ tex_add_glyph_option(glyph, glyph_option_math_italics_too);
+ }
+ }
+ /*tex
+ Do we have a correction at all? In opentype fonts we normally set the
+ delta to zero.
+ */
+ if (math_kernel_node_has_option(nucleus, math_kernel_no_italic_correction)) {
+ /*tex
+ This node is flagged not to have italic correction.
+ */
+ } else if (tex_aux_math_followed_by_italic_kern(target, "complexity")) {
+ /*tex
+ For some reason there is (already) an explicit italic correction so we
+ don't add more here. I need a use case.
+ */
+ } else if (tex_aux_math_engine_control(fnt, math_control_apply_text_italic_kern)) {
+ /*tex
+ This is a bit messy and needs a more fundamental cleanup giving the
+ kind of control that we want.
+ */
+ if (italic) {
+ *italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
+ if (*italic) {
+ if (node_type(nucleus) == math_text_char_node) {
+ if (tex_aux_math_engine_control(fnt, math_control_check_text_italic_kern)) {
+ /*tex
+ We add no italic correction in mid-word of (opentype)
+ text font. This is kind of fragile so it might go away
+ or become an option.
+ */
+ if (chr == letter_cmd) {
+ *italic = 0;
+ }
+ }
+ if (tex_aux_math_engine_control(fnt, math_control_check_space_italic_kern)) {
+ /*tex
+ We're now in the traditional branch. it is a bit weird
+ test based on space being present in an old school math
+ font. For now we keep this.
+ */
+ if (tex_get_font_space(fnt)) {
+ /*tex
+ We add no italic correction in mid-word (traditional)
+ text font. In the case of a math font, the correction
+ became part of the width.
+ */
+ *italic = 0;
+ }
+ }
+ }
+ if (*italic && ! noad_has_following_scripts(target)) {
+ /*tex
+ Here we add a correction but then also have to make sure that it
+ doesn't happen later on so we zero |delta| afterwards. The call
+ handles the one script only case (maybe delegate the next too).
+ */
+ tex_aux_math_insert_italic_kern(glyph, *italic, nucleus, "check");
+ *italic = 0;
+ }
+ }
+ }
+ }
+ return glyph;
+ } else {
+ return tex_new_node(hlist_node, unknown_list);
+ }
+ }
+ case sub_box_node:
+ return kernel_math_list(nucleus);
+ case sub_mlist_node:
+ {
+ halfword list = kernel_math_list(nucleus);
+ halfword package = null;
+ halfword fenced = node_type(target) == simple_noad && node_subtype(target) == fenced_noad_subtype;
+ int unpack = tex_math_has_class_option(node_subtype(target), unpack_class_option) || has_noad_option_unpacklist(target);
+ // todo: check has_noad_option_unpacklist vs hpack later
+ // halfword result = tex_mlist_to_hlist(list, fenced || has_noad_option_unpacklist(q), style, unset_noad_class, unset_noad_class); /*tex Here we're nesting. */
+ halfword result = tex_mlist_to_hlist(list, unpack, style, unset_noad_class, unset_noad_class, kerns); /*tex Here we're nesting. */
+ tex_aux_set_current_math_size(style);
+ package = tex_hpack(result, 0, packing_additional, direction_unknown, holding_none_option);
+ if (fenced) {
+ node_subtype(package) = math_fence_list;
+ // } else if (has_noad_option_unpacklist(q)) {
+ } else if (unpack) {
+ node_subtype(package) = math_list_list;
+ } else if (noad_class_main(target) == unset_noad_class) {
+ node_subtype(package) = math_pack_list;
+ } else {
+ node_subtype(package) = 0x100 + noad_class_main(target);
+ }
+ tex_attach_attribute_list_copy(package, nucleus);
+ return package;
+ }
+ case hlist_node:
+ /* really */
+ break;
+ default:
+ tex_confusion("check nucleus complexity");
+ }
+ } else {
+ tex_normal_warning("math", "recovering from missing nucleus, best check it out");
+ noad_nucleus(target) = tex_aux_fake_nucleus(ghost_noad_subtype);
+ }
+ return tex_new_node(hlist_node, unknown_list);
+}
+
+/*tex
+ The main reason for keeping the node is that original \TEX\ has no prev links but we do have
+ these in \LUATEX. But it is anyway okay to keep this a signal.
+*/
+
+static halfword tex_aux_make_choice(halfword current, halfword style)
+{
+ halfword prv = node_prev(current);
+ halfword nxt = node_next(current);
+ halfword signal = tex_new_node(style_node, former_choice_math_style);
+ /*tex We replace choice by signal encoded in a style noad, it is no longer a cast! */
+ tex_try_couple_nodes(prv, signal);
+ tex_try_couple_nodes(signal, nxt);
+ switch (node_subtype(current)) {
+ case normal_choice_subtype:
+ {
+ halfword choice = null;
+ switch (style) {
+ case display_style:
+ case cramped_display_style:
+ choice = choice_display_mlist(current);
+ choice_display_mlist(current) = null;
+ break;
+ case text_style:
+ case cramped_text_style:
+ choice = choice_text_mlist(current);
+ choice_text_mlist(current) = null;
+ break;
+ case script_style:
+ case cramped_script_style:
+ choice = choice_script_mlist(current);
+ choice_script_mlist(current) = null;
+ break;
+ case script_script_style:
+ case cramped_script_script_style:
+ choice = choice_script_script_mlist(current);
+ choice_script_script_mlist(current) = null;
+ break;
+ }
+ /*tex We inject the choice list after the signal. */
+ if (choice) {
+ tex_couple_nodes(signal, choice);
+ tex_try_couple_nodes(tex_tail_of_node_list(choice), nxt);
+ }
+ }
+ break;
+ case discretionary_choice_subtype:
+ {
+ halfword disc = tex_new_disc_node(normal_discretionary_code);
+ halfword pre = choice_pre_break(current);
+ halfword post = choice_post_break(current);
+ halfword replace = choice_no_break(current);
+ choice_pre_break(current) = null;
+ choice_post_break(current) = null;
+ choice_no_break(current) = null;
+ if (pre) {
+ pre = tex_mlist_to_hlist(pre, 0, style, unset_noad_class, unset_noad_class, NULL);
+ tex_set_disc_field(disc, pre_break_code, pre);
+ }
+ if (post) {
+ post = tex_mlist_to_hlist(post, 0, style, unset_noad_class, unset_noad_class, NULL);
+ tex_set_disc_field(disc, post_break_code, post);
+ }
+ if (replace) {
+ replace = tex_mlist_to_hlist(replace, 0, style, unset_noad_class, unset_noad_class, NULL);
+ tex_set_disc_field(disc, no_break_code, replace);
+ }
+ disc_class(disc) = choice_class(current);
+ disc = tex_math_make_disc(disc);
+ tex_couple_nodes(signal, disc);
+ tex_try_couple_nodes(disc, nxt);
+ }
+ break;
+ }
+ /*tex We flush the old choice node */
+ tex_flush_node(current);
+ return signal;
+}
+
+/*tex
+ This is just a \quote {fixer}. Todo: prepend the top and/or bottom to the super/subscript,
+ but we also need to hpack then. Problem: how to determine the slack here? However, slack
+ is less important because we normally have binding right text here.
+*/
+
+static int tex_aux_make_fenced(halfword current, halfword current_style, halfword size, noad_classes *fenceclasses)
+{
+ halfword nucleus = noad_nucleus(current);
+ (void) current_style;
+ (void) size;
+ if (nucleus) {
+ halfword list = kernel_math_list(nucleus);
+ if (list && node_type(list) == fence_noad && node_subtype(list) == left_operator_side) {
+ fenceclasses->main = noad_class_main(list);
+ fenceclasses->left = noad_class_left(list);
+ fenceclasses->right = noad_class_right(list);
+ if (noad_supscr(current) && ! kernel_math_list(fence_delimiter_top(list))) {
+ halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
+ node_subtype(n) = math_char_node;
+ noad_nucleus(n) = noad_supscr(current);
+ kernel_math_list(fence_delimiter_top(list)) = n;
+ noad_supscr(current) = null;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_str("[math: promoting supscript to top delimiter]");
+ tex_end_diagnostic();
+ }
+ }
+ if (noad_subscr(current) && ! kernel_math_list(fence_delimiter_bottom(list))) {
+ halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
+ node_subtype(n) = math_char_node;
+ noad_nucleus(n) = noad_subscr(current);
+ kernel_math_list(fence_delimiter_bottom(list)) = n;
+ noad_subscr(current) = null;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_str("[math: promoting subscript to bottom delimiter]");
+ tex_end_diagnostic();
+ }
+ }
+ /*tex
+ Now we remove the dummy right one. If something is in between we assume it's on
+ purpose.
+ */
+ {
+ halfword nxt = node_next(list);
+ if (nxt && node_type(nxt) == fence_noad && node_subtype(nxt) == right_fence_side) {
+ /* todo : check for delimiter . or 0 */
+ node_next(list) = null;
+ tex_flush_node_list(nxt);
+ }
+ }
+ return 1; /* we had a growing one */
+ }
+ }
+ return 0;
+}
+
+static void tex_aux_finish_fenced(halfword current, halfword main_style, scaled max_depth, scaled max_height, kernset *kerns)
+{
+ delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
+ noad_analyzed(current) = (singleword) tex_aux_make_left_right(current, main_style, max_depth, max_height, lmt_math_state.size, &extremes);
+ if (kerns && extremes.tfont) {
+ switch (node_subtype(current)) {
+ case left_fence_side:
+ case extended_left_fence_side:
+ if (tex_math_has_class_option(fenced_noad_subtype, carry_over_left_top_kern_class_option)) {
+ kerns->topleft = tex_char_top_left_kern_from_font(extremes.tfont, extremes.tchar);
+ }
+ if (tex_math_has_class_option(fenced_noad_subtype, carry_over_left_bottom_kern_class_option)) {
+ kerns->bottomleft = tex_char_bottom_left_kern_from_font(extremes.bfont, extremes.bchar);
+ }
+ if (tex_math_has_class_option(fenced_noad_subtype, prefer_delimiter_dimensions_class_option)) {
+ kerns->height = extremes.height;
+ kerns->depth = extremes.depth;
+ }
+ break;
+ case right_fence_side:
+ case extended_right_fence_side:
+ case left_operator_side:
+ case no_fence_side:
+ if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_top_kern_class_option)) {
+ kerns->topright = tex_char_top_right_kern_from_font(extremes.tfont, extremes.tchar);
+ }
+ if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_bottom_kern_class_option)) {
+ kerns->bottomright = tex_char_bottom_right_kern_from_font(extremes.bfont, extremes.bchar);
+ }
+ if (tex_math_has_class_option(fenced_noad_subtype, prefer_delimiter_dimensions_class_option)) {
+ kerns->height = extremes.height;
+ kerns->depth = extremes.depth;
+ }
+ break;
+ }
+ }
+}
+
+/*tex
+
+ Here is the overall plan of |mlist_to_hlist|, and the list of its local variables. In
+ \LUAMETATEX\ we could actually use the fact that we have a double linked list. Because we have
+ a more generic class and penalty handling the two stages are clearly separated, also variable
+ wise.
+
+*/
+
+static halfword tex_aux_unroll_noad(halfword tail, halfword l, quarterword s)
+{
+ while (l) {
+ halfword n = node_next(l);
+ node_next(l) = null;
+ if (node_type(l) == hlist_node && (s < 0 || node_subtype(l) == s) && ! box_source_anchor(l)) {
+ if (box_list(l)) {
+ tex_couple_nodes(tail, box_list(l));
+ tail = tex_tail_of_node_list(tail);
+ box_list(l) = null;
+ }
+ tex_flush_node(l);
+ } else {
+ tex_couple_nodes(tail, l);
+ tail = l;
+ }
+ l = n;
+ }
+ return tail;
+}
+
+static halfword tex_aux_unroll_list(halfword tail, halfword l)
+{
+ while (l) {
+ halfword n = node_next(l);
+ node_next(l) = null;
+ if (node_type(l) == hlist_node && ! box_source_anchor(l)) {
+ if (box_list(l)) {
+ switch (node_subtype(l)) {
+ case hbox_list:
+ case container_list:
+ case math_list_list: /* in case of a ghost (we could remap subtype instead) */
+ tex_couple_nodes(tail, box_list(l));
+ tail = tex_tail_of_node_list(tail);
+ box_list(l) = null;
+ break;
+ default:
+ tex_couple_nodes(tail, l);
+ tail = l;
+ break;
+ }
+ }
+ tex_flush_node(l);
+ } else {
+ tex_couple_nodes(tail, l);
+ tail = l;
+ }
+ l = n;
+ }
+ return tail;
+}
+
+inline static void tex_aux_wipe_noad(halfword n)
+{
+ if (tex_nodetype_has_attributes(node_type(n))) {
+ remove_attribute_list(n);
+ }
+ tex_reset_node_properties(n);
+ tex_free_node(n, get_node_size(node_type(n)));
+}
+
+static halfword tex_aux_append_ghost(halfword ghost, halfword p)
+{
+ halfword l = noad_new_hlist(ghost);
+ if (l) {
+ if (has_noad_option_unpacklist(ghost)) {
+ /* always anyway */
+ p = tex_aux_unroll_noad(p, l, math_list_list);
+ } else if (has_noad_option_unrolllist(ghost)) {
+ p = tex_aux_unroll_list(p, l);
+ } else {
+ if (node_type(l) == hlist_node && ! node_next(l)) {
+ node_subtype(l) = math_ghost_list;
+ }
+ tex_couple_nodes(p, l);
+ p = tex_tail_of_node_list(p);
+ }
+ noad_new_hlist(ghost) = null;
+ }
+ tex_aux_wipe_noad(ghost);
+ return p;
+}
+
+static halfword tex_aux_get_plus_glyph(halfword current)
+{
+ if (node_type(current) == simple_noad) {
+ halfword list = noad_new_hlist(current);
+ if (list && node_type(list) == hlist_node) {
+ list = box_list(list);
+ }
+ if (list && node_type(list) == glue_node) {
+ list = node_next(list);
+ }
+ if (list && node_type(list) == glyph_node && ! node_next(list)) {
+ return list;
+ }
+ }
+ return null;
+}
+
+static void tex_aux_show_math_list(const char *fmt, halfword list)
+{
+ tex_begin_diagnostic();
+ tex_print_format(fmt, lmt_math_state.level);
+ tex_show_node_list(list, tracing_math_par >= 3 ? max_integer : show_box_depth_par, tracing_math_par >= 3 ? max_integer : show_box_breadth_par);
+ tex_print_ln();
+ tex_end_diagnostic();
+}
+
+static void tex_aux_wrapup_nucleus_and_add_scripts(halfword current, halfword nxt, int current_style, halfword *italic, kernset *kerns)
+{
+ halfword p;
+ p = tex_aux_check_nucleus_complexity(current, italic, current_style, lmt_math_state.size, kerns);
+ if (p && noad_source(current)) {
+ switch (node_type(p)) {
+ case hlist_node:
+ case vlist_node:
+ if (! box_source_anchor(p)) {
+ box_source_anchor(p) = noad_source(current);
+ tex_set_box_geometry(p, anchor_geometry);
+ }
+ break;
+ default:
+ /*tex Todo: maybe pack and assign! */
+ break;
+ }
+ }
+ if (noad_has_scripts(current)) {
+ scaled drop = 0;
+ if (node_type(current) == accent_noad && noad_has_superscripts(current)) {
+ drop = tex_get_math_y_parameter_default(current_style, math_parameter_accent_superscript_drop, 0);
+ drop += scaledround(kerns->toptotal * tex_get_math_parameter_default(current_style, math_parameter_accent_superscript_percent, 0) / 100.0);
+ }
+ tex_aux_make_scripts(current, p, *italic, current_style, 0, 0, drop, kerns);
+ } else {
+ /*tex
+ Adding italic correction here is kind of fuzzy because some characters already have
+ that built in. However, we also add it in the scripts so if it's optional here it
+ also should be there. The compexity tester can have added it in which case delta
+ is zero.
+ */
+ if (nxt && *italic) {
+ if (node_type(nxt) == simple_noad && tex_math_has_class_option(node_subtype(nxt), no_italic_correction_class_option)) {
+ *italic = 0;
+ }
+ if (*italic) {
+ /* If we want it as option we need the fontor store it in the noad. */
+ tex_aux_math_insert_italic_kern(p, *italic, current, "final");
+ }
+ }
+ tex_aux_assign_new_hlist(current, p);
+ }
+}
+
+/*tex
+
+ This function is called recursively, for instance for wrapped content in fence, accent, fraction
+ and radical noads. Especially the fences introduce some messy code but I might clean that up
+ stepwise. We don't want to get away too much from the original.
+
+ Because we have more than two passes, and the function became way larger, it has been split up
+ in smaller functions.
+
+*/
+
+typedef struct mliststate {
+ halfword mlist;
+ int penalties;
+ int main_style;
+ int beginclass;
+ int endclass;
+ kernset *kerns;
+ halfword scale;
+ scaled max_height;
+ scaled max_depth;
+} mliststate;
+
+static void tex_mlist_to_hlist_set_boundaries(mliststate *state)
+{
+ halfword b = tex_aux_fake_nucleus((quarterword) state->beginclass);
+ halfword e = tex_aux_fake_nucleus((quarterword) state->endclass);
+ if (state->mlist) {
+ tex_couple_nodes(b, state->mlist);
+ }
+ state->mlist = b;
+ tex_couple_nodes(tex_tail_of_node_list(state->mlist), e);
+ state->beginclass = unset_noad_class;
+ state->endclass = unset_noad_class;
+}
+
+static void tex_mlist_to_hlist_preroll_radicals(mliststate *state)
+{
+ halfword current = state->mlist;
+ halfword current_style = state->main_style;
+ halfword height = 0;
+ halfword depth = 0;
+ tex_aux_set_current_math_size(current_style);
+ tex_aux_set_current_math_scale(state->scale);
+ if (tracing_math_par >= 2) {
+ tex_aux_show_math_list("[math: radical sizing pass, level %i]", state->mlist);
+ }
+ while (current) {
+ switch (node_type(current)) {
+ case radical_noad:
+ {
+ halfword body = null;
+ tex_aux_preroll_radical(current, current_style, lmt_math_state.size);
+ body = noad_new_hlist(current);
+ if (box_height(body) > height) {
+ height = box_height(body);
+ }
+ if (box_depth(body) > depth) {
+ depth = box_depth(body);
+ }
+ }
+ break;
+ case style_node:
+ tex_aux_make_style(current, &current_style, NULL);
+ break;
+ case parameter_node:
+ tex_def_math_parameter(node_subtype(current), parameter_name(current), parameter_value(current), cur_level + lmt_math_state.level, indirect_math_regular);
+ break;
+ }
+ current = node_next(current);
+ }
+ /*tex
+ A positive value is assigned, a negative value subtracted and a value of maxdimen will use
+ the maximum found dimensions. Todo: use an option to control this instead.
+ */
+ current = state->mlist;
+ while (current) {
+ if (node_type(current) == radical_noad) {
+ switch (node_subtype(current)) {
+ case normal_radical_subtype:
+ case radical_radical_subtype:
+ case root_radical_subtype:
+ case rooted_radical_subtype:
+ {
+ halfword body = noad_new_hlist(current);
+ if (radical_height(current) == max_dimen) {
+ box_height(body) = height;
+ } else if (radical_height(current) < 0) {
+ box_height(body) += radical_height(current);
+ if (box_height(body) < 0) {
+ box_height(body) += 0;
+ }
+ } else if (radical_height(current)) {
+ box_height(body) = radical_height(current);
+ }
+ if (radical_depth(current) == max_dimen) {
+ box_depth(body) = depth;
+ } else if (radical_depth(current) < 0) {
+ box_depth(body) += radical_depth(current);
+ if (box_depth(body) < 0) {
+ box_depth(body) += 0;
+ }
+ } else if (radical_depth(current)) {
+ box_depth(body) = radical_depth(current);
+ }
+ }
+ break;
+ }
+ }
+ current = node_next(current);
+ }
+}
+
+static void tex_mlist_to_hlist_preroll_dimensions(mliststate *state)
+{
+ halfword current = state->mlist;
+ scaled current_mu = 0;
+ halfword current_style = state->main_style;
+ int blockrulebased = 0;
+ /*tex We set the math unit width corresponding to |size|: */
+ tex_aux_set_current_math_size(current_style);
+ tex_aux_set_current_math_scale(state->scale);
+ current_mu = tex_get_math_quad_size_scaled(lmt_math_state.size);
+ if (tracing_math_par >= 2) {
+ tex_aux_show_math_list("[math: first pass, level %i]", state->mlist);
+ }
+ while (current) {
+ /*tex The italic correction offset for subscript and superscript: */
+ scaled italic = 0;
+ halfword nxt = node_next(current);
+ noad_classes fenceclasses = { unset_noad_class, unset_noad_class, unset_noad_class };
+ kernset localkerns;
+ tex_math_wipe_kerns(&localkerns);
+ /*tex
+ At some point we had nicely cleaned up switch driven code here but we ended up with a
+ more generic approach. The reference is still in the pre-2022 zips and git repository.
+
+ The fact that we have configurable atom spacing (with inheritance) means that we can
+ now have a rather simple switch without any remapping and RESWITCH magic.
+ */
+ if (blockrulebased > 0) {
+ blockrulebased -= 1;
+ }
+ switch (node_type(current)) {
+ case simple_noad:
+ /*tex
+ Because we have added features we no longer combine the case in clever ways to
+ minimize code. Let the compiler do that for us. We could be generic and treat
+ all the same but for now we just emulate some of traditional \TEX's selectivity.
+ */
+ if (blockrulebased > 0) {
+ noad_options(current) |= noad_option_no_ruling;
+ blockrulebased = 0;
+ }
+ switch (node_subtype(current)) {
+ case under_noad_subtype:
+ tex_aux_make_under(current, current_style, lmt_math_state.size, math_rules_fam_par);
+ break;
+ case over_noad_subtype:
+ tex_aux_make_over(current, current_style, lmt_math_state.size, math_rules_fam_par);
+ break;
+ case vcenter_noad_subtype:
+ tex_aux_make_vcenter(current, current_style, lmt_math_state.size);
+ break;
+ case fenced_noad_subtype:
+ if (tex_aux_make_fenced(current, current_style, lmt_math_state.size, &fenceclasses)) {
+ /*tex We have a left operator so we fall through! */
+ } else {
+ break;
+ }
+ case operator_noad_subtype:
+ /* compatibility */
+ if (! (has_noad_option_limits(current) || has_noad_option_nolimits(current))) {
+ /* otherwise we don't enter the placement function */
+ noad_options(current) |= (current_style == display_style || current_style == cramped_display_style) ? noad_option_limits : noad_option_no_limits;
+ }
+ goto PROCESS;
+ default:
+ /* setting both forces check */
+ if ((has_noad_option_limits(current) && has_noad_option_nolimits(current))) {
+ if (current_style == display_style || current_style == cramped_display_style) {
+ noad_options(current) = unset_option(noad_options(current), noad_option_no_limits);
+ noad_options(current) |= noad_option_limits;
+ } else {
+ noad_options(current) = unset_option(noad_options(current), noad_option_limits);
+ noad_options(current) |= noad_option_no_limits;
+ }
+ }
+ PROCESS:
+ if ( // node_subtype(q) == operator_noad_subtype
+ // ||
+ has_noad_option_limits(current) || has_noad_option_nolimits(current)
+ || has_noad_option_openupheight(current) || has_noad_option_openupdepth(current)
+ || has_noad_option_adapttoleft(current) || has_noad_option_adapttoright(current)
+ ) {
+ if (node_subtype(current) == fenced_noad_subtype && ! noad_has_scripts(current)) {
+ /*tex
+ This is a special case: the right counterpart of the left operator
+ can trigger a boxing of all that comes before so we need to enforce
+ nolimits. Mikael Sundqvist will reveal all this in the CMS manual.
+ */
+ italic = tex_aux_make_op(current, current_style, lmt_math_state.size, 0, limits_horizontal_mode, NULL);
+ } else {
+ italic = tex_aux_make_op(current, current_style, lmt_math_state.size, 0, limits_unknown_mode, NULL);
+ }
+ /* tex_math_has_class_option(node_subtype(current),keep_correction_class_code) */
+ if (node_subtype(current) != operator_noad_subtype) {
+ italic = 0;
+ }
+ if (fenceclasses.main != unset_noad_class) {
+ noad_class_main(current) = fenceclasses.main;
+ }
+ if (fenceclasses.left != unset_noad_class) {
+ noad_class_left(current) = fenceclasses.left;
+ }
+ if (fenceclasses.right != unset_noad_class) {
+ noad_class_right(current) = fenceclasses.right;
+ }
+ if (has_noad_option_limits(current) || has_noad_option_nolimits(current)) {
+ goto CHECK_DIMENSIONS;
+ }
+ } else {
+ // tex_aux_make_ord(current, lmt_math_state.size);
+ tex_aux_check_ord(current, lmt_math_state.size, null);
+ }
+ break;
+ }
+ break;
+ case fence_noad:
+ {
+ /* why still ... */
+ current_style = state->main_style;
+ tex_aux_set_current_math_size(current_style);
+ current_mu = tex_get_math_quad_size_scaled(lmt_math_state.size);
+ /* ... till here */
+ goto DONE_WITH_NODE;
+ }
+ case fraction_noad:
+ tex_aux_make_fraction(current, current_style, lmt_math_state.size, state->kerns);
+ goto CHECK_DIMENSIONS;
+ case radical_noad:
+ tex_aux_make_radical(current, current_style, lmt_math_state.size, &localkerns);
+ break;
+ case accent_noad:
+ tex_aux_make_accent(current, current_style, lmt_math_state.size, &localkerns);
+ break;
+ case style_node:
+ tex_aux_make_style(current, &current_style, &current_mu);
+ goto DONE_WITH_NODE;
+ case choice_node:
+ current = tex_aux_make_choice(current, current_style);
+ goto DONE_WITH_NODE;
+ case parameter_node:
+ /* maybe not needed as we do a first pass */
+ tex_def_math_parameter(node_subtype(current), parameter_name(current), parameter_value(current), cur_level + lmt_math_state.level, indirect_math_regular);
+ goto DONE_WITH_NODE;
+ case insert_node:
+ case mark_node:
+ case adjust_node:
+ case boundary_node:
+ case whatsit_node:
+ case penalty_node:
+ case disc_node:
+ case par_node: /* for local boxes */
+ goto DONE_WITH_NODE;
+ case rule_node:
+ tex_aux_check_math_strut_rule(current, current_style);
+ if (rule_height(current) > state->max_height) {
+ state->max_height = rule_height(current);
+ }
+ if (rule_depth(current) > state->max_depth) {
+ state->max_depth = rule_depth(current);
+ }
+ goto DONE_WITH_NODE;
+ case glue_node:
+ if (node_subtype(current) == rulebased_math_glue) {
+ blockrulebased = 2;
+ }
+ tex_aux_make_glue(current, current_mu, current_style);
+ goto DONE_WITH_NODE;
+ case kern_node:
+ tex_aux_make_kern(current, current_mu, current_style);
+ goto DONE_WITH_NODE;
+ default:
+ tex_confusion("mlist to hlist, case 1");
+ }
+ /*tex
+ When we get to the following part of the program, we have \quote {fallen through} from
+ cases that did not lead to |check_dimensions| or |done_with_noad| or |done_with_node|.
+ Thus, |q|~points to a noad whose nucleus may need to be converted to an hlist, and
+ whose subscripts and superscripts need to be appended if they are present.
+
+ If |nucleus(q)| is not a |math_char|, the variable |italic| is the amount by which a
+ superscript should be moved right with respect to a subscript when both are present.
+ */
+ tex_aux_wrapup_nucleus_and_add_scripts(current, nxt, current_style, &italic, &localkerns);
+ // {
+ // kernset kerns;
+ // halfword p;
+ // tex_math_copy_kerns(&kerns, &localkerns);
+ // p = tex_aux_check_nucleus_complexity(current, &italic, current_style, lmt_math_state.size, &kerns);
+ // if (p && noad_source(current)) {
+ // switch (node_type(p)) {
+ // case hlist_node:
+ // case vlist_node:
+ // if (! box_source_anchor(p)) {
+ // box_source_anchor(p) = noad_source(current);
+ // tex_set_box_geometry(p, anchor_geometry);
+ // }
+ // break;
+ // default:
+ // /*tex Todo: maybe pack and assign! */
+ // break;
+ // }
+ // }
+ // if (noad_has_scripts(current)) {
+ // scaled drop = 0;
+ // if (node_type(current) == accent_noad && noad_has_superscripts(current)) {
+ // drop = tex_get_math_y_parameter_default(current_style, math_parameter_accent_superscript_drop, 0);
+ // drop += scaledround(localkerns.toptotal * tex_get_math_parameter_default(current_style, math_parameter_accent_superscript_percent, 0) / 100.0);
+ // }
+ // tex_aux_make_scripts(current, p, italic, current_style, 0, 0, drop, &kerns);
+ // } else {
+ // /*tex
+ // Adding italic correction here is kind of fuzzy because some characters already have
+ // that built in. However, we also add it in the scripts so if it's optional here it
+ // also should be there. The compexity tester can have added it in which case delta
+ // is zero.
+ // */
+ // if (nxt && italic) {
+ // if (node_type(nxt) == simple_noad && tex_math_has_class_option(node_subtype(nxt), no_italic_correction_class_option)) {
+ // italic = 0;
+ // }
+ // if (italic) {
+ // /* If we want it as option we need the fontor store it in the noad. */
+ // tex_aux_math_insert_italic_kern(p, italic, current, "final");
+ // }
+ // }
+ // tex_aux_assign_new_hlist(current, p);
+ // }
+ // }
+ CHECK_DIMENSIONS:
+ {
+ scaledwhd siz = tex_natural_hsizes(noad_new_hlist(current), null, normal_glue_multiplier, normal_glue_sign, normal_glue_sign);
+ if (siz.ht > state->max_height) {
+ state->max_height = siz.ht;
+ }
+ if (siz.dp > state->max_depth) {
+ state->max_depth = siz.dp;
+ }
+ }
+ DONE_WITH_NODE:
+ if ((node_type(current) == simple_noad) && noad_new_hlist(current)) {
+ if (has_noad_option_phantom(current) || has_noad_option_void(current)) {
+ noad_new_hlist(current) = tex_aux_make_list_phantom(noad_new_hlist(current), has_noad_option_void(current), get_attribute_list(current));
+ }
+ }
+ current = node_next(current);
+ }
+}
+
+static void tex_mlist_to_hlist_size_fences(mliststate *state)
+{
+ halfword current = state->mlist;
+ halfword current_style = state->main_style;
+ tex_aux_set_current_math_size(current_style);
+ tex_aux_set_current_math_scale(state->scale);
+ if (tracing_math_par >= 2) {
+ tex_aux_show_math_list("[math: fence sizing pass, level %i]", state->mlist);
+ }
+ while (current) {
+ switch (node_type(current)) {
+ case fence_noad:
+ tex_aux_finish_fenced(current, current_style, state->max_depth, state->max_height, state->kerns);
+ break;
+ case style_node:
+ tex_aux_make_style(current, &current_style, NULL);
+ break;
+ case parameter_node:
+ /* tricky as this is sort of persistent, we need to reset it at the start */
+ tex_def_math_parameter(node_subtype(current), parameter_name(current), parameter_value(current), cur_level + lmt_math_state.level, indirect_math_regular);
+ break;
+ }
+ current = node_next(current);
+ }
+}
+
+static void tex_mlist_to_hlist_finalize_list(mliststate *state)
+{
+ halfword recent = null; /*tex Watch out: can be wiped, so more a signal! */
+ int recent_type = 0;
+ int recent_subtype = ordinary_noad_subtype;
+ halfword current_style = state->main_style;
+ halfword fenced = null;
+ halfword recent_left_slack = 0;
+ halfword recent_right_slack = 0;
+ halfword recent_class_overload = unset_noad_class;
+ halfword recent_script_state = 0;
+ halfword recent_plus_glyph = null;
+ scaled current_mu = 0;
+ halfword current = state->mlist;
+ halfword p = temp_head;
+ halfword ghost = null;
+ node_next(p) = null;
+ tex_aux_set_current_math_size(current_style);
+ tex_aux_set_current_math_scale(state->scale);
+ current_mu = tex_get_math_quad_size_scaled(lmt_math_state.size);
+ if (math_penalties_mode_par) {
+ state->penalties = 1; /* move to caller ? */
+ }
+ if (tracing_math_par >= 2) {
+ tex_aux_show_math_list("[math: second pass, level %i]", state->mlist);
+ }
+ RESTART:
+ while (current) {
+ /*tex
+ If node |q| is a style node, change the style and |goto delete_q|; otherwise if it is
+ not a noad, put it into the hlist, advance |q|, and |goto done|; otherwise set |s| to
+ the size of noad |q|, set |t| to the associated type (|ord_noad.. inner_noad|), and set
+ |pen| to the associated penalty.
+
+ Just before doing the big |case| switch in the second pass, the program sets up default
+ values so that most of the branches are short.
+
+ We need to remain somewhat compatible so we still handle some open and close fence
+ setting (marked as safeguard) here but as we (1) take the class from the delimiter,
+ when set, or (2) derive it from the fence subtype, we don't really need it. In some
+ cases, like with bars that serve a dual purpose, it will always be a mess.
+
+ */
+ /*tex the effective |type| of noad |q| during the second pass */
+ halfword current_type = simple_noad;
+ /*tex the effective |subtype| of noad |q| during the second pass */
+ halfword current_subtype = ordinary_noad_subtype;
+ /*tex penalties to be inserted */
+ halfword post_penalty = infinite_penalty;
+ halfword pre_penalty = infinite_penalty;
+ /*tex experiment */
+ halfword current_left_slack = 0;
+ halfword current_right_slack = 0;
+ halfword current_script_state = 0;
+ halfword current_plus_glyph = 0;
+ halfword old_recent = 0;
+ halfword old_current = 0;
+ HERE:
+ switch (node_type(current)) {
+ case simple_noad:
+ if (node_subtype(current) == ghost_noad_subtype) {
+ /* for now, what to do with edges */
+ halfword nxt = node_next(current);
+ if (ghost) {
+ // check for noad_new_hlist(ghost)
+ halfword p = tex_tail_of_node_list(noad_new_hlist(ghost));
+ noad_class_right(ghost) = noad_class_right(current);
+ p = tex_aux_append_ghost(current, p);
+ noad_new_hlist(ghost) = tex_head_of_node_list(p);
+ } else {
+ ghost = current;
+ }
+ current = nxt;
+ if (current) {
+ goto HERE;
+ } else {
+ goto RESTART;
+ }
+ } else {
+ current_subtype = node_subtype(current);
+ current_left_slack = noad_left_slack(current);
+ current_right_slack = noad_right_slack(current);
+ current_script_state = noad_script_state(current);
+ switch (current_subtype) {
+ case fenced_noad_subtype:
+ {
+ // halfword list = noad_new_hlist(current);
+ // if (list && ! noad_nucleus(current) && ! noad_has_scripts(current)) { // scripts test will go
+ fenced = current;
+ if (get_noad_right_class(fenced) != unset_noad_class) {
+ current_subtype = get_noad_left_class(fenced);
+ } else if (get_noad_main_class(fenced) != unset_noad_class) { // needs testing by MS
+ current_subtype = get_noad_main_class(fenced);
+ } else {
+ current_subtype = open_noad_subtype; /* safeguard, see comment above */
+ }
+ // }
+ break;
+ }
+ default:
+ {
+ halfword list = noad_new_hlist(current);
+ if (list && tex_is_math_disc(list)) {
+ current_type = simple_noad;
+ current_subtype = disc_class(box_list(list));
+ }
+ if (list && noad_source(current)) {
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: packing due to source field %D]", noad_source(current));
+ tex_end_diagnostic();
+ }
+ switch (node_type(list)) {
+ case hlist_node:
+ case vlist_node:
+ if (! box_source_anchor(list)) {
+ box_source_anchor(list) = noad_source(current);
+ tex_set_box_geometry(list, anchor_geometry);
+ }
+ break;
+ default:
+ list = tex_hpack(list, 0, packing_additional, direction_unknown, holding_none_option);
+ tex_attach_attribute_list_copy(list, current);
+ box_source_anchor(list) = noad_source(current);
+ tex_set_box_geometry(list, anchor_geometry);
+ noad_new_hlist(current) = list;
+ node_subtype(list) = math_pack_list;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (get_noad_left_class(current) != unset_noad_class) {
+ current_subtype = get_noad_left_class(current);
+ } else if (get_noad_main_class(current) != unset_noad_class) {
+ current_subtype = get_noad_main_class(current);
+ }
+ }
+ break;
+ case radical_noad:
+ switch (node_subtype(current)) {
+ case normal_radical_subtype:
+ case radical_radical_subtype:
+ case root_radical_subtype:
+ case rooted_radical_subtype:
+ case delimited_radical_subtype:
+ current_type = simple_noad;
+ current_subtype = radical_noad_subtype;
+ break;
+ case under_delimiter_radical_subtype:
+ case delimiter_under_radical_subtype:
+ current_type = simple_noad;
+ current_subtype = under_noad_subtype;
+ break;
+ case over_delimiter_radical_subtype:
+ case delimiter_over_radical_subtype:
+ current_type = simple_noad;
+ current_subtype = over_noad_subtype;
+ break;
+ case h_extensible_radical_subtype:
+ current_type = simple_noad;
+ current_subtype = accent_noad_subtype;
+ break;
+ }
+ break;
+ case accent_noad:
+ current_type = simple_noad; /*tex Same kind of fields. */
+ current_subtype = accent_noad_subtype;
+ current_left_slack = noad_left_slack(current);
+ current_right_slack = noad_right_slack(current);
+ break;
+ case fraction_noad:
+ current_type = simple_noad; /*tex Same kind of fields. */
+ current_subtype = fraction_noad_subtype; /* inner_noad_type */
+ break;
+ case fence_noad:
+ current_type = simple_noad; /*tex Same kind of fields. */
+ current_subtype = noad_analyzed(current);
+ fenced = current;
+ break;
+ case style_node:
+ tex_aux_make_style(current, &current_style, &current_mu);
+ recent = current;
+ current = node_next(current);
+ tex_aux_wipe_noad(recent);
+ goto RESTART;
+ case parameter_node:
+ tex_def_math_parameter(node_subtype(current), parameter_name(current), parameter_value(current), cur_level + lmt_math_state.level, indirect_math_regular);
+ recent = current;
+ current = node_next(current);
+ tex_aux_wipe_noad(recent);
+ goto RESTART;
+ case glue_node:
+ switch (node_subtype(current)) {
+ case conditional_math_glue:
+ case rulebased_math_glue:
+ {
+ halfword t = current;
+ current = node_next(current);
+ tex_flush_node(t);
+ goto MOVEON;
+ }
+ default:
+ break;
+ }
+ // case glyph_node:
+ case disc_node:
+ case hlist_node:
+ case boundary_node:
+ case whatsit_node:
+ case penalty_node:
+ case rule_node:
+ case adjust_node:
+ case insert_node:
+ case mark_node:
+ case par_node:
+ case kern_node:
+ tex_couple_nodes(p, current);
+ p = current;
+ current = node_next(current);
+ node_next(p) = null;
+ MOVEON:
+ if (current) {
+ /*tex These nodes are invisible! */
+ switch (node_type(p)) {
+ case boundary_node:
+ case adjust_node:
+ case insert_node:
+ case mark_node:
+ case par_node:
+ goto HERE;
+ case rule_node:
+ if (node_subtype(p) == strut_rule_subtype) {
+ goto HERE;
+ }
+ }
+ }
+ continue;
+ // goto NEXT_NODE;
+ default:
+ tex_confusion("mlist to hlist, case 2");
+ }
+ /*tex
+ Apply some logic. The hard coded pairwise comparison is replaced by a generic one
+ because we can have more classes. For a while spacing and pairing was under a mode
+ control but that made no sense. We start with the begin class.
+ */
+ recent_class_overload = get_noad_right_class(current);
+ if (current_type == simple_noad && state->beginclass == unset_noad_class) {
+ if (noad_new_hlist(current)) {
+ tex_flush_node(noad_new_hlist(current));
+ noad_new_hlist(current) = null;
+ }
+ state->beginclass = current_subtype;
+ /* */
+ recent_type = current_type;
+ recent_subtype = current_subtype;
+ recent = current;
+ current = node_next(current);
+ goto WIPE;
+ }
+ /*tex
+ This is a special case where a sign starts something marked as (like) numeric, in
+ which there will be different spacing applied.
+ */
+ if (tex_math_has_class_option(current_subtype, look_ahead_for_end_class_option)) {
+ halfword endhack = node_next(current);
+ if (endhack && node_type(endhack) == simple_noad && (node_subtype(endhack) == math_end_class || get_noad_main_class(endhack) == math_end_class)) {
+ halfword value = tex_aux_math_ruling(current_subtype, math_end_class, current_style);
+ if (value != MATHPARAMDEFAULT) {
+ // recent_subtype = (value >> 16) & 0xFF;
+ // current_subtype = value & 0xFF;
+ current_subtype = (value >> 16) & 0xFF;
+ }
+
+ }
+ }
+ old_recent = recent_subtype;
+ old_current = current_subtype;
+ if (current_subtype != unset_noad_class && recent_subtype != unset_noad_class && current_type == simple_noad) {
+ if (recent_type == simple_noad && ! has_noad_option_noruling(current)) {
+ halfword value = tex_aux_math_ruling(recent_subtype, current_subtype, current_style);
+ if (value != MATHPARAMDEFAULT) {
+ recent_subtype = (value >> 16) & 0xFF;
+ current_subtype = value & 0xFF;
+ }
+ }
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ if (old_recent != recent_subtype || old_current != current_subtype) {
+ tex_print_format("[math: atom ruling, recent %n, current %n, new recent %n, new current %n]", old_recent, old_current, recent_subtype, current_subtype);
+ } else {
+ tex_print_format("[math: atom ruling, recent %n, current %n]", old_recent, old_current);
+ }
+ tex_end_diagnostic();
+ }
+ }
+ /*tex Now we set the inter-atom penalties: */
+ if (ghost && ! has_noad_option_right(ghost)) {
+ p = tex_aux_append_ghost(ghost, p);
+ ghost = null;
+ }
+ if (current_type == simple_noad) {
+ pre_penalty = tex_aux_math_penalty(state->main_style, 1, current_subtype);
+ post_penalty = tex_aux_math_penalty(state->main_style,0, current_subtype);
+ }
+ /*tex Dirty trick: */ /* todo: use kerns info */
+ current_plus_glyph = tex_aux_get_plus_glyph(current);
+ /*tex Append inter-element spacing based on |r_type| and |t| */
+ if (current_plus_glyph && recent_script_state) {
+ /*tex This is a very special case and used {x^2 / 3| kind of situations: */
+ halfword plus = tex_aux_checked_left_kern(current_plus_glyph, recent_script_state, current_subtype);
+ if (plus) {
+ halfword kern = tex_new_kern_node(plus, math_shape_kern_subtype);
+ tex_attach_attribute_list_copy(kern, current);
+ tex_couple_nodes(p, kern);
+ p = kern;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: state driven left shape kern %p]", plus, pt_unit);
+ tex_end_diagnostic();
+ }
+ }
+ }
+ if (recent_type > 0) {
+ halfword last = node_type(p); /* can be temp */
+ halfword glue = tex_aux_math_spacing_glue(recent_subtype, current_subtype, current_style, current_mu);
+ halfword kern = null;
+ if (glue) {
+ tex_attach_attribute_list_copy(glue, current);
+ }
+ if (recent_right_slack) {
+ halfword kern = tex_new_kern_node(-recent_right_slack, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, current);
+ tex_couple_nodes(p, kern);
+ p = kern;
+ if (current_subtype >= 0 && tex_math_has_class_option(current_subtype, no_pre_slack_class_option)) {
+ /* */
+ } else if (! glue) {
+ glue = tex_aux_math_dimen(recent_right_slack, inter_math_skip_glue, -2);
+ } else {
+ glue_amount(glue) += recent_right_slack;
+ }
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: migrating right slack %p]", recent_right_slack, pt_unit);
+ tex_end_diagnostic();
+ }
+ recent_right_slack = 0;
+ }
+ if (recent_plus_glyph && current_script_state) {
+ /*tex This is a very special case and used {x^2 / 3| kind of situations: */
+ halfword plus = tex_aux_checked_right_kern(recent_plus_glyph, current_script_state, recent_subtype);
+ if (plus) {
+ halfword kern = tex_new_kern_node(plus, math_shape_kern_subtype);
+ tex_attach_attribute_list_copy(kern, current);
+ tex_couple_nodes(p, kern);
+ p = kern;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: state driven right shape kern %p]", plus, pt_unit);
+ tex_end_diagnostic();
+ }
+ }
+ }
+ if (current_left_slack) {
+ kern = tex_new_kern_node(-current_left_slack, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(kern, p);
+ /* tex_couple_nodes(node_prev(p), kern); */ /* close to the molecule */
+ /* tex_couple_nodes(kern, p); */ /* close to the molecule */
+ if (recent_subtype >= 0 && tex_math_has_class_option(recent_subtype, no_post_slack_class_option)) {
+ /* */
+ } else if (! glue) {
+ glue = tex_aux_math_dimen(current_left_slack, inter_math_skip_glue, -1);
+ } else {
+ glue_amount(glue) += current_left_slack;
+ }
+ current_left_slack = 0;
+ }
+ /*tex
+ Do we still want this check in infinite.
+ */
+ if (state->penalties && pre_penalty < infinite_penalty && node_type(last) != penalty_node) {
+ /*tex no checking of prev node type */
+ halfword penalty = tex_new_penalty_node(pre_penalty, math_pre_penalty_subtype);
+ tex_attach_attribute_list_copy(penalty, current);
+ tex_couple_nodes(p, penalty);
+ p = penalty;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: pre penalty, left %n, right %n, amount %i]", recent_subtype, current_subtype, penalty_amount(penalty));
+ tex_end_diagnostic();
+ }
+ }
+ if (tex_math_has_class_option(current_subtype, remove_italic_correction_class_option)) {
+ if (node_type(p) == kern_node && node_subtype(p) == italic_kern_subtype) {
+ halfword prv = node_prev(p);
+ if (prv) {
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: removing italic correction %D between %i and %i]", kern_amount(p), recent_subtype, current_subtype);
+ tex_end_diagnostic();
+ }
+ tex_flush_node(p);
+ p = prv;
+ }
+ }
+ }
+ if (glue) {
+ tex_couple_nodes(p, glue);
+ p = glue;
+ }
+ if (kern) {
+ tex_couple_nodes(p, kern);
+ p = kern;
+ }
+ }
+ if (ghost) {
+ p = tex_aux_append_ghost(ghost, p);
+ ghost = null;
+ }
+ {
+ halfword l = noad_new_hlist(current);
+ if (! l) {
+ /* curious */
+ } else if (node_type(l) == hlist_node && box_source_anchor(l)) {
+ tex_couple_nodes(p, l);
+ } else if (fenced) {
+ /*tex Watch out: we can have |[prescripts] [fencelist] [postscripts]| */
+ if (tex_math_has_class_option(fenced_noad_subtype, unpack_class_option)) {
+ p = tex_aux_unroll_noad(p, l, math_fence_list);
+ } else {
+ tex_couple_nodes(p, l);
+ }
+ } else if (has_noad_option_unpacklist(current) || tex_math_has_class_option(current_subtype, unpack_class_option)) {
+ /*tex So here we only unpack a math list. */
+ p = tex_aux_unroll_noad(p, l, math_list_list);
+ } else if (has_noad_option_unrolllist(current)) {
+ p = tex_aux_unroll_list(p, l);
+ } else if (tex_is_math_disc(l)) {
+ /* hm, temp nodes here */
+ tex_couple_nodes(p, box_list(l));
+ box_list(l) = null;
+ tex_flush_node(l);
+ } else if (current_type == simple_noad && current_subtype == math_end_class) {
+ if (noad_new_hlist(current)) {
+ tex_flush_node(noad_new_hlist(current));
+ noad_new_hlist(current) = null;
+ }
+ } else {
+ tex_couple_nodes(p, l);
+ }
+ p = tex_tail_of_node_list(p);
+ if (fenced) {
+ if (get_noad_right_class(fenced) != unset_noad_class) {
+ current_subtype = get_noad_right_class(fenced);
+ } else if (get_noad_main_class(fenced) != unset_noad_class) { // needs testing by MS
+ current_subtype = get_noad_main_class(fenced);
+ } else {
+ current_subtype = close_noad_subtype; /* safeguard, see comment above */
+ }
+ fenced = null;
+ }
+ noad_new_hlist(current) = null;
+ }
+ /*tex
+ Append any |new_hlist| entries for |q|, and any appropriate penalties. We insert a
+ penalty node after the hlist entries of noad |q| if |pen| is not an \quote {infinite}
+ penalty, and if the node immediately following |q| is not a penalty node or a
+ |rel_noad| or absent entirely. We could combine more here but for beter understanding
+ we keep the branches seperated. This code is not performance sentitive anyway.
+
+ We can actually drop the omit check because we pair by class.
+ */
+ if (state->penalties && node_next(current) && post_penalty < infinite_penalty) {
+ halfword recent = node_next(current);
+ recent_type = node_type(recent);
+ recent_subtype = node_subtype(recent);
+ /* todo: maybe also check the mainclass of the recent */
+ if ((recent_type != penalty_node) && ! (recent_type == simple_noad && tex_math_has_class_option(recent_subtype, omit_penalty_class_option))) {
+ halfword z = tex_new_penalty_node(post_penalty, math_post_penalty_subtype);
+ tex_attach_attribute_list_copy(z, current);
+ tex_couple_nodes(p, z);
+ p = z;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: post penalty, left %n, right %n, amount %i]", recent_subtype, current_subtype, penalty_amount(z));
+ tex_end_diagnostic();
+ }
+ }
+ }
+ if (recent_class_overload != unset_noad_class) {
+ current_type = simple_noad;
+ current_subtype = recent_class_overload;
+ }
+ if (current_type == simple_noad && current_subtype != math_end_class) {
+ state->endclass = current_subtype;
+ }
+ recent_type = current_type;
+ recent_subtype = current_subtype;
+ recent_left_slack = current_left_slack;
+ recent_right_slack = current_right_slack;
+ recent_script_state = current_script_state;
+ recent_plus_glyph = current_plus_glyph;
+ // if (first && recent_left_slack) {
+ if (p == temp_head && recent_left_slack) {
+ halfword k = tex_new_kern_node(-recent_left_slack, horizontal_math_kern_subtype);
+ halfword h = node_next(temp_head);
+ tex_attach_attribute_list_copy(k, p);
+ tex_couple_nodes(k, h);
+ node_next(temp_head) = k;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: nilling recent left slack %p]", recent_left_slack);
+ tex_end_diagnostic();
+ }
+ }
+ recent = current;
+ current = node_next(current);
+ if (! current && recent_right_slack) {
+ halfword k = tex_new_kern_node(-recent_right_slack, horizontal_math_kern_subtype);
+ tex_attach_attribute_list_copy(k, p);
+ tex_couple_nodes(p, k);
+ p = k;
+ if (tracing_math_par >= 2) {
+ tex_begin_diagnostic();
+ tex_print_format("[math: nilling recent right slack %p]", recent_right_slack);
+ tex_end_diagnostic();
+ }
+ }
+ // first = 0;
+ /*tex
+ The m|-|to|-|hlist conversion takes place in|-|place, so the various dependant fields
+ may not be freed (as would happen if |flush_node| was called). A low|-|level |free_node|
+ is easier than attempting to nullify such dependant fields for all possible node and
+ noad types.
+ */
+ WIPE:
+ tex_aux_wipe_noad(recent);
+ }
+ if (tracing_math_par >= 3) {
+ tex_aux_show_math_list("[math: result, level %i]", node_next(temp_head));
+ }
+}
+
+halfword tex_mlist_to_hlist(halfword mlist, int penalties, int main_style, int beginclass, int endclass, kernset *kerns) /* classes should be quarterwords */
+{
+ /*tex
+ We start with a little housekeeping. There are now only two variables that live across the
+ two passes. We actually could split this function in two. For practical reasons we have
+ collected all relevant state parameters in a structure. The values in there can be adapted
+ in this state.
+ */
+ mliststate state;
+ state.mlist = mlist;
+ state.penalties = penalties;
+ state.main_style = main_style;
+ state.beginclass = beginclass == unset_noad_class ? math_begin_class : beginclass;
+ state.endclass = endclass == unset_noad_class ? math_end_class : endclass;;
+ state.kerns = kerns;
+ state.scale = glyph_scale_par;
+ state.max_height = 0;
+ state.max_depth = 0;
+ if (state.kerns) {
+ tex_math_wipe_kerns(state.kerns);
+ }
+ ++lmt_math_state.level;
+ /*tex
+ Here we can deal with end_class spacing: we can inject a dummy current atom with no content and
+ just a class. In fact, we can always add a begin and endclass. A nucleus is kind of mandate.
+ */
+ tex_mlist_to_hlist_set_boundaries(&state);
+ /*tex
+ This first pass processes the bodies of radicals so that we can normalize them when height
+ and/or depth are set.
+ */
+ tex_mlist_to_hlist_preroll_radicals(&state);
+ /*
+ Make a second pass over the mlist. This is needed in order to get the maximum height and
+ depth in order to make fences match.
+ */
+ tex_mlist_to_hlist_preroll_dimensions(&state);
+ /*tex
+ The fence sizing is done in the third pass. Using a dedicated pass permits experimenting.
+ */
+ tex_mlist_to_hlist_size_fences(&state);
+ /*tex
+ Make a fourth pass over the mlist; traditionally this was the second pass. We removing all
+ noads and insert the proper spacing (glue) and penalties. The binary checking is gone and
+ replaced by generic arbitrary inter atom mapping control, so for the hard coded older logic
+ one has to check the (development) git repository.
+
+ The original comment for this pass is: \quotation {We have now tied up all the loose ends of
+ the first pass of |mlist_to_hlist|. The second pass simply goes through and hooks everything
+ together with the proper glue and penalties. It also handles the |fence_noad|s that might be
+ present, since |max_hl| and |max_d| are now known. Variable |p| points to a node at the
+ current end of the final hlist.} However, in \LUAMETATEX\ the fence sizing has already be
+ done in the previous pass.
+ */
+ tex_mlist_to_hlist_finalize_list(&state);
+ /*tex
+ We're done now and can restore the possibly changed values as well as provide some feedback
+ about the result.
+ */
+ tex_unsave_math_data(cur_level + lmt_math_state.level);
+ cur_list.math_begin = state.beginclass;
+ cur_list.math_end = state.endclass;
+ glyph_scale_par = state.scale;
+ --lmt_math_state.level;
+ node_prev(node_next(temp_head)) = null;
+ return node_next(temp_head);
+}