texmlist.c /size: 351 Kb    last modification: 2024-01-16 10:22
1/*
2    See license.txt in the root of this project.
3*/
4
5
6/*tex 
7
8    Before we get to the code is is good to notice that what we have here is an extension of the 
9    math rendering as implemented in \TEX. There are several reason for additions and changes. One 
10    is that we need to support \OPENTYPE\ fonts and after more than a decade struggling with the
11    fact that many fonts are a mix between what the \OPENTYPE\ specification describes (its 
12    accuracy evolved a bit over time) and what traditional \TEX\ fonts do. Think of the role that 
13    italic correction plays in \TEX\ and kerns in \OPENTYPE\ math. 
14
15    In \LUATEX\ this has resulted in a way to control various elements of the engine so that one 
16    can adapt to the font. Here even more properties can be controlled. However, in the end we 
17    decided that fonts are too inconsistent and unlikely to be fixed, so in \CONTEXT\ we settled 
18    on a combination of engine control and patching fonts runtime (aka goodie tweaks). This does
19    actually mean that in principle we can remove most of the code related to italic correction 
20    and simplify kerning, which makes for cleaner code. We can then also ditch many control 
21    options. It could also make some of the resulting constructs les scomplex (this is something
22    that might happen eventually anyway).
23
24    In addition to this, the machinery below also has opened up atoms, fractions, accents, fences, 
25    radicals and more beyond what \LUATEX\ already added. We have more classes (including user 
26    ones), more spacing and penalty control, positioning features, etc. Most of that is not 
27    described here but in documents that come with \CONTEXT\ and articles in user group journals. 
28
29    It is unlikely that users will set up the engine beyond what a macro package provides, which 
30    should be reasonable defaults. Keep in mind that the math machinery has never be part of 
31    discussing extensions to \TEX\ and that the math subengine was never touched. So, while 
32    \CONTEXT\ users sort of expect this upgrade path, this is not true for other macro packages, 
33    especially when they are considered standard and provide standard behaviour. This means that 
34    we can go a bit further in setting up the engine (by options and parametsrs) in ways that 
35    provide better output. This means that even with the same input, the output that \CONTEXT\
36    produces will look different and hopefully somewhat better. 
37
38    One can of course wonder why we think the rendering can be improved and part of the answer is 
39    in the fact that we have double linked node lists. This means that we can go back and forward 
40    over the result. Another apects is that we have less constraints in memory and processor 
41    capabilities, so we can carry around more information and spend more time on analysing and 
42    calculations. Nodes are way bigger and the font system is more dynamic, which comes at a 
43    price not possible when original \TEX\ showed up. One can therefore only admire even more 
44    what Don Knuth came up with, which still performs very well, and what is the robust core of 
45    what we see below!
46
47    HH & MS 
48
49*/
50
51/*tex
52
53    The code here has to deal with traditional \TEX\ fonts as well as the more modern \OPENTYPE\
54    fonts. In \TEX\ fonts the spacing between and construction of glyphs is determined by font
55    parameters, kerns, italic correction and linked lists of glyphs that make extensibles. In
56    \OPENTYPE\ fonts kerns are replaced by so called staircase kerns, italics are used differently
57    and extensibles are made from other glyphs, as in traditional \TEX\ fonts.
58
59    In traditional \TEX\ the italic correction is added to the width of the glyph. This is part of
60    the engine design and this is also reflected in the width metric of the font. In \OPENTYPE\ math
61    this is different. There the italic correction had more explicit usage. The 1.7 spec says:
62
63    \startitemize
64
65    \startitem
66        {\em italic correction:} When a run of slanted characters is followed by a straight
67        character (such as an operator or a delimiter), the italics correction of the last glyph is
68        added to its advance width.
69
70        When positioning limits on an N-ary operator (e.g., integral sign), the horizontal position
71        of the upper limit is moved to the right by half the italics correction, while the position
72        of the lower limit is moved to the left by the same distance. Comment HH: this is is only
73        true when we have a real italic integral where the top part stick out right and the bottom
74        part left. So, that's only 'one' n-ary operator.
75
76        When positioning superscripts and subscripts, their default horizontal positions are also
77        different by the amount of the italics correction of the preceding glyph.
78    \stopitem
79
80    \startitem
81        {\em math kerning:} Set the default horizontal position for the superscript as shifted
82        relative to the position of the subscript by the italics correction of the base glyph.
83    \stopitem
84
85    \stopitemize
86
87    Before this was specified we had to gamble a bit and assume that cambria was the font
88    benchmark and trust our eyes (and msword) for the logic. I must admit that I have been
89    fighting these italics in fonts (and the heuristics that \LUAMETATEX\ provided) right from the
90    start (for instance by using \LUA\ based postprocessing) but by now we know more and have more
91    fonts to test with. More fonts are handy because not all fonts are alike when it comes to
92    italics. Axis are another area of concern, as it looks like \OPENTYPE\ math fonts often already
93    apply that shift.
94
95    Now, one can think of cheating. Say that we add the italic correction to the widths and then
96    make the italic correction zero for all these shapes except those that have a slope, in which
97    case we negate tot correction. Unfortunately that doesn't work well because the traditional
98    code path {\em assumes} the too narrow shape: it doesn't compensate subscripts. Also, keep in
99    mind that in for instance Pagella (etc), at least in the pre 2022 versions, even upright
100    characters have italic corrections! It looks like they are used as kerns in a way similar to
101    staircase kerns. So, here, when we add the correction we incorrectly flag it as italic but we
102    have no way to distinguish them from regular kerns. When the gyre fonts never get corrected
103    we're stick with the two code paths forever.
104
105    Blocking italic correction via the glyph options is supported (not yet for other constructs
106    but that might happen). All this italic stuff makes the source a bit messy. Maybe the other
107    things will be controlled via a noad option.
108
109    The above description is no longer accurate but we keep it for historic reasons. We now
110    follow a reverse approach: we just assume \OPENTYPE\ but also expect the needed features to
111    be enabled explicitly. That means that for instance \quote {out of the box} the engine will
112    not apply italic correction.
113
114    In 2021-2022 Mikael Sundqvist and I (Hans Hagen) spent about a year investigating how we could
115    improve the rendering of math. Quite a bit of research went into that and we decided to get rid 
116    of some old font code and concentrate on the \OPENTYPE\ fonts, although we found some flaws and 
117    inconsistencies in them. The solution was to assume a Cambria alike font and adapt the other 
118    fonts runtime using so called goodie files that are part of the \CONTEXT\ font loading code. 
119    That way we could enforce some consistency and compentate for e.g. problematic  dimensions like
120    widths and italic corrections as well as bad top accents and values of font parameters that 
121    interfered with what we had in mind. We added plenty extra ones as well as extra kern options. 
122    Combined with a more rich model for inter atom spacing we could improve the look and feel a lot. 
123
124    When the engine got updated a couple of options came and went. An example of this is delimiter 
125    options. For instance we tracked if a delimiter was actually changes and could then react to that 
126    wrt italic corrections. In the new approach we no longer handle that because assume decent fonts 
127    or at least tweaked ones (read: \CONTEXT\ font goodies being applied). So in the end those extra
128    delimiter options got removed or were just handled by the noad options. The code is still in the 
129    repository. Also some options related to tracing injected kerns became defaults because we had 
130    them always turned on. 
131
132*/
133
134/* 
135
136    There is a persistent issue with operators and italic correction. For n_ary ones the italic 
137    correction is used for positioning scripts above or below (shifted horizontally) or 
138    at the right top and bottom (also shifted horizontally). That assumes a proper width that 
139    doesn't need italic correction itself. On the other hand, in the case of arbitrary characters 
140    that want to be operators the italic correction can be part of th ewidth (take the |f| as 
141    example). Now, the most problematic one is the integral and especially in Latin Modern where 
142    it is very slanted. In \CONTEXT\ we use the left operator feature to deal with all this. 
143
144    We considered a special class for operators where italic correction is used for positioning 
145    but in the end we rejected that. We now: 
146
147    \startitemize
148    \startitem 
149        asume properly bounded characters (sum, product, integral) and most are upright anyway
150    \stopitem 
151    \startitem  
152        top and bottom scripts are centered 
153    \stopitem 
154    \startitem 
155        right and left scripts are bound tight to the edge 
156    \stopitem 
157    \startitem  
158        italic correction can be completely ignored 
159    \stopitem 
160    \startitem  
161        top and bottom anchors (set up in the goodie) control optional displacements 
162    \stopitem 
163    \startitem  
164        top right and top left kerns (set up in the goodie) control optional displacements 
165    \stopitem 
166    \stopitemize
167
168    We already did the kerns for some fonts using information in the goodie file, and now we 
169    also use the top and bottom anchors. In fact, the only real exception is Latin Modern, so 
170    instead of messing up the code with exceptions and tricky controls we now have a few lines 
171    in (basically) one goodie file. 
172
173    An class option can be set to add italic corrections to operators so in the case of the 
174    integral, where it is used for positioning, it can then be used to calculate anchors, but 
175    that is then done in the goodie file. Keep in mind that these anchors are an engine feature.
176
177    For most math fonts all works out of the box, only fonts with highly asymetrical integral 
178    signs are touched by this, but fonts like that likely need tweaks anyway. 
179
180    For the record: the specificaton only talks about possible application so we can basically do 
181    as we like. All works fine for Cambria and the \TEX\ community didn't make sure that better 
182    features were added (like anchors) for their shapes. 
183
184    In the end, from the perspective of ConTeXt, the italics code can completely go away which 
185    means that we also no longer have to stare at somewhat fuzzy code originating in dealing 
186    with italics. It depends on how well our latest heuristic tweaks work out. 
187
188*/
189
190# include "luametatex.h"
191
192/*tex
193
194    We have some more function calls and local so we have replace |cur_style| by |style| where that
195    makes sense. The same is true for some local variables. This makes it a bit easier to
196    distinguish with the more global variables stored in state structures.
197
198    It's a stepwise process ... occasionally I visit this file and change the short variable names
199    to more verbose. There is also relatively new scaling code that needs checking.
200
201*/
202
203static void tex_aux_append_hkern_to_box_list (halfword q, scaled delta, halfword subtype, const char *trace);
204static void tex_aux_prepend_hkern_to_box_list(halfword q, scaled delta, halfword subtype, const char *trace);
205
206/*tex
207
208    \LUAMETATEX\ makes a bunch of extensions cf.\ the |MATH| table in \OPENTYPE, but some of the
209    |MathConstants| values have no matching usage in \LUAMETATEX\ right now.
210
211    \startitemize
212
213        \startitem
214            |ScriptPercentScaleDown| |ScriptScriptPercentScaleDown|: These should be handled by the
215            macro package, on the engine side there are three separate fonts.
216        \stopitem
217
218        \startitem
219            |DelimitedSubFormulaMinHeight|: This is perhaps related to word's natural math input?
220            We have no idea what to do about it.
221        \stopitem
222
223        \startitem
224            |MathLeading|: \LUAMETATEX\ does not currently handle multi line displays, and the
225            parameter does not seem to make much sense elsewhere.
226        \stopitem
227
228        \startitem
229            |FlattenedAccentBaseHeight|: This is based on the |flac| |GSUB| feature. It would not
230            be hard to support that, but proper math accent placements cf.\ |MATH| needs support
231            for |MathTopAccentAttachment| table to be implemented first. We actually do support 
232            it in \LUAMETATEX. 
233        \stopitem
234
235    \stopitemize
236
237    Old-style fonts do not define the |radical_rule|. This allows |make_radical| to select the
238    backward compatibility code, but it also means that we can't raise an error here.
239
240    Occasionally I visit this file and make some variables more verbose.
241
242    In the meantime some experimental and obsolete code has been removed but it can be found in 
243    the development repository if really needed. It makes no sense to keep code around that has 
244    been replaced or improved otherwise. Some code we keep commented for a while before it is 
245    flushed out. 
246
247*/
248
249typedef struct scriptdata {
250    halfword node;   
251    halfword fnt;  
252    halfword chr;  
253    halfword box;
254    scaled   kern; 
255    scaled   slack;
256    int      shifted;
257    int      whatever;
258} scriptdata;
259
260typedef struct delimiterextremes {
261    scaled tfont;
262    scaled tchar;
263    scaled bfont;
264    scaled bchar;
265    scaled height;
266    scaled depth;
267} delimiterextremes; 
268
269typedef enum limits_modes {
270    limits_unknown_mode,    
271    limits_vertical_mode,   // limits 
272    limits_horizontal_mode, // no limits 
273} limits_modes;
274
275inline static void tex_math_wipe_kerns(kernset *kerns) {
276    if (kerns) { 
277        kerns->topright = 0;
278        kerns->topleft = 0;
279        kerns->bottomright = 0;
280        kerns->bottomleft = 0;
281        kerns->height = 0;
282        kerns->depth = 0;
283        kerns->toptotal = 0;
284        kerns->bottomtotal = 0;
285        kerns->dimensions = 0;
286        kerns->font = null_font;
287        kerns->character = 0;
288        kerns->padding = 0;
289    }
290}
291
292inline static void tex_math_copy_kerns(kernset *kerns, kernset *parent) {
293    if (kerns && parent) { 
294        kerns->topright = parent->topright;
295        kerns->topleft = parent->topleft;
296        kerns->bottomright = parent->bottomright;
297        kerns->bottomleft = parent->bottomleft;
298        kerns->height = parent->height;
299        kerns->depth = parent->depth;
300        kerns->toptotal = parent->toptotal;
301        kerns->bottomtotal = parent->bottomtotal;
302        kerns->dimensions = parent->dimensions;
303        kerns->font = parent->font;
304        kerns->character = parent->character;
305    }
306}
307
308/*tex
309    When the style changes, the following piece of program computes associated information:
310*/
311
312inline static halfword tex_aux_set_style_to_size(halfword style)
313{
314    switch (style) {
315        case script_style:
316        case cramped_script_style:
317            return script_size;
318        case script_script_style:
319        case cramped_script_script_style:
320            return script_script_size;
321        default:
322            return text_size;
323    }
324}
325
326inline static void tex_aux_set_current_math_scale(halfword scale)
327{
328    glyph_scale_par = scale;   
329    lmt_math_state.scale = glyph_scale_par;    
330}
331
332inline static void tex_aux_set_current_math_size(halfword style)
333{
334    lmt_math_state.size = tex_aux_set_style_to_size(style);
335}
336
337inline static void tex_aux_make_style(halfword current, halfword *current_style, halfword *current_mu)
338{ 
339    halfword style = node_subtype(current);
340    switch (style) {
341        case scaled_math_style:
342            tex_aux_set_current_math_scale(style_scale(current));   
343            break;
344        default:
345            if (is_valid_math_style(style)) {
346                if (current_style) { 
347                    *current_style = style;
348                }
349                tex_aux_set_current_math_size(style);
350                if (current_mu) { 
351                    *current_mu = scaledround(tex_get_math_parameter(style, math_parameter_quad, NULL) / 18.0);
352                 // *current_mu = scaledround((double) tex_get_math_quad_style(style) / 18.0);
353                }
354            }
355            break;
356    }
357}
358
359/*tex 
360    There is no need to be more subtle, if needed we can always do some extensive checking for the 
361    combined styles. Basically this is just a catch for |\allmathstyles|. Also keep in mind that
362    there no grouping inside a formula: we can cook up something but in the end one always has some 
363    synchronization problem because the next atom is likely outside the group anyway. 
364*/
365
366inline static void tex_aux_set_parameter(halfword current, halfword style)
367{ 
368    if (is_valid_math_style(node_subtype(current))) { 
369        style = node_subtype(current);
370    }
371    tex_def_math_parameter(style, parameter_name(current), parameter_value(current), cur_level + lmt_math_state.level, indirect_math_regular, 0);
372}
373
374void tex_set_math_text_font(halfword style, int usetextfont)
375{
376     halfword size = tex_aux_set_style_to_size(style);
377     halfword font = tex_fam_fnt(cur_fam_par, size);
378     halfword scale = tex_get_math_font_scale(font, size);
379     switch (usetextfont) {
380         case math_atom_text_font_option:
381             scale = scaledround((double) scale * (double) lmt_font_state.fonts[font]->size / (double) lmt_font_state.fonts[cur_font_par]->size);
382             break;
383         case math_atom_math_font_option:
384             update_tex_font(0, font);
385             break;
386     }
387     update_tex_glyph_scale(scale);
388}
389
390static halfword tex_aux_math_penalty_what(int pre, halfword cls, halfword pre_code, halfword post_code)
391{
392    halfword value = count_parameter(pre ? (pre_code + cls) : (post_code + cls));
393    if (value == infinite_penalty) {
394        unsigned parent = (unsigned) count_parameter(first_math_parent_code + cls);
395        cls = pre ? ((parent >> 8) & 0xFF) : (parent & 0xFF);
396        if (! valid_math_class_code(cls)) {
397            return infinite_penalty;
398        }
399        value = count_parameter(pre ? (pre_code + cls) : (post_code + cls));
400    }
401    return value;
402}
403
404static halfword tex_aux_math_penalty(int main_style, int pre, halfword cls)
405{
406    switch (main_style) {
407        case display_style:
408        case cramped_display_style:
409            {    
410                halfword value = tex_aux_math_penalty_what(pre, cls, first_math_display_pre_penalty_code, first_math_display_post_penalty_code);
411                if (value != infinite_penalty) {
412                    return value;
413                } else { 
414                    break;
415                }
416            }
417    }
418    return tex_aux_math_penalty_what(pre, cls, first_math_pre_penalty_code, first_math_post_penalty_code);
419}
420
421inline static scaled limited_scaled(long l) {
422    if (l > max_dimension) {
423        return max_dimension;
424    } else if (l < -max_dimension) {
425        return -max_dimension;
426    } else {
427        return (scaled) l;
428    }
429}
430
431inline static scaled limited_rounded(double d) {
432    long l = scaledround(d);
433    if (l > max_dimension) {
434        return max_dimension;
435    } else if (l < -max_dimension) {
436        return -max_dimension;
437    } else {
438        return (scaled) l;
439    }
440}
441
442inline static int tex_aux_math_engine_control(halfword fnt, halfword control)
443{
444 // if (fnt && (math_font_control_par & math_control_use_font_control) == math_control_use_font_control) {
445    if (fnt && (font_mathcontrol(fnt) & math_control_use_font_control) == math_control_use_font_control) {
446        /*tex 
447            This is only for old fonts and it might go away eventually. Not all control options relate to 
448            a font.
449        */
450        return (font_mathcontrol(fnt) & control) == control;
451    }
452    return (math_font_control_par & control) == control;
453}
454
455/*
456
457    Todo: When we pass explicit dimensions (keyword driven) we use a different helper so that, if
458    needed we can add debug messages. These values {\em are} scaled according to the glyph scaling
459    so basically they are relative measures. Maybe we need an extra parameter to control this.
460
461*/
462
463inline static scaled tex_aux_math_math_scale(scaled v)
464{
465    return v ? scaledround(0.001 * lmt_math_state.scale * v) : 0;
466}
467
468inline static scaled tex_aux_math_glyph_scale(scaled v)
469{
470    return v ? scaledround(0.001 * glyph_scale_par * v) : 0;
471}
472
473inline static scaled tex_aux_math_x_scaled(scaled v, int style)
474{
475    scaled scale = tex_get_math_parameter(style, math_parameter_x_scale, NULL);
476    return v ? limited_rounded(0.000000001 * glyph_scale_par * glyph_x_scale_par * v * scale) : 0;
477}
478
479inline static scaled tex_aux_math_given_x_scaled(scaled v)
480{
481    return v;
482}
483
484inline static scaled tex_aux_math_y_scaled(scaled v, int style)
485{
486    scaled scale = tex_get_math_parameter(style, math_parameter_y_scale, NULL);
487    return v ? limited_rounded(0.000000001 * glyph_scale_par * glyph_y_scale_par * v * scale) : 0;
488}
489
490inline static scaled tex_aux_math_given_y_scaled(scaled v)
491{
492    return v;
493}
494
495scaled tex_math_parameter_x_scaled(int style, int param)
496{
497    scaled scale = tex_get_math_parameter(style, math_parameter_x_scale, NULL);
498    scaled value = tex_get_math_parameter(style, param, NULL);
499    return value ? limited_rounded(0.000000001 * glyph_scale_par * glyph_x_scale_par * value * scale) : 0;
500}
501
502scaled tex_math_parameter_y_scaled(int style, int param)
503{
504    scaled value = tex_get_math_parameter(style, math_parameter_y_scale, NULL);
505    scaled scale = tex_get_math_parameter(style, param, NULL);
506    return value ? limited_rounded(0.000000001 * glyph_scale_par * glyph_y_scale_par * value * scale) : 0;
507}
508
509inline static scaled tex_aux_math_axis(halfword size)
510{
511    scaled v = tex_math_axis_size(size); /* already scaled to size and x_scale */
512    return v ? limited_rounded(0.000001 * glyph_scale_par * glyph_y_scale_par * v) : 0;
513}
514
515inline static scaled tex_aux_math_x_size_scaled(halfword f, scaled v, halfword size)
516{
517    return v ? limited_rounded(0.000000001 * tex_get_math_font_scale(f, size) * glyph_scale_par * glyph_x_scale_par * v) : 0;
518}
519
520inline static scaled tex_aux_math_y_size_scaled(halfword f, scaled v, halfword size)
521{
522    return v ? limited_rounded(0.000000001 * tex_get_math_font_scale(f, size) * glyph_scale_par * glyph_y_scale_par * v) : 0;
523}
524
525halfword tex_math_font_char_ht(halfword fnt, halfword chr, halfword style)
526{
527    return tex_aux_math_y_size_scaled(fnt, tex_char_height_from_font(fnt, chr), tex_aux_set_style_to_size(style));
528}
529
530halfword tex_math_font_char_dp(halfword fnt, halfword chr, halfword style)
531{
532    return tex_aux_math_y_size_scaled(fnt, tex_char_depth_from_font(fnt, chr), tex_aux_set_style_to_size(style));
533}
534
535inline static halfword tex_aux_new_math_glyph(halfword fnt, halfword chr, quarterword subtype) {
536    halfword scale = scaling_factor;
537    halfword glyph = tex_new_glyph_node(subtype, fnt, tex_get_math_char(fnt, chr, lmt_math_state.size, &scale, math_direction_par), null); /* todo: data */;
538    set_glyph_options(glyph, glyph_options_par);
539    glyph_scale(glyph) = tex_aux_math_glyph_scale(scale);
540    glyph_x_scale(glyph) = glyph_x_scale_par;
541    glyph_y_scale(glyph) = glyph_y_scale_par;
542    glyph_protected(glyph) = glyph_protected_math_code;
543    return glyph;
544}
545
546halfword tex_new_math_glyph(halfword fnt, halfword chr) {
547    return tex_aux_new_math_glyph(fnt, chr, 0);
548}
549
550static void tex_aux_trace_kerns(halfword kern, const char *what, const char *detail)
551{
552    if (tracing_math_par >= 2) {
553        tex_begin_diagnostic();
554        tex_print_format("[math: %s, %s, amount %p]", what, detail, kern_amount(kern));
555        tex_end_diagnostic();
556    }
557}
558
559static halfword tex_aux_math_insert_font_kern(halfword current, scaled amount, halfword attributetemplate, const char *trace)
560{
561    /*tex Maybe |math_font_kern|, also to prevent expansion. */
562    halfword kern = tex_new_kern_node(amount, font_kern_subtype);
563    tex_attach_attribute_list_copy(kern, attributetemplate ? attributetemplate : current);
564    if (node_next(current)) {
565        tex_couple_nodes(kern, node_next(current));
566    }
567    tex_couple_nodes(current, kern);
568    tex_aux_trace_kerns(kern, "adding font kern", trace);
569    return kern; 
570}
571
572static halfword tex_aux_math_insert_italic_kern(halfword current, scaled amount, halfword attributetemplate, const char *trace)
573{
574    /*tex Maybe |math_italic_kern|. */
575    halfword kern = tex_new_kern_node(amount, italic_kern_subtype);
576    tex_attach_attribute_list_copy(kern, attributetemplate ? attributetemplate : current);
577    if (node_next(current)) {
578        tex_couple_nodes(kern, node_next(current));
579    }
580    tex_couple_nodes(current, kern);
581    tex_aux_trace_kerns(kern, "adding italic kern", trace);
582    return kern;
583}
584
585static int tex_aux_math_followed_by_italic_kern(halfword current, const char *trace)
586{
587    if (current) {
588        halfword next = node_next(current);
589        if (next && node_type(next) == kern_node && node_subtype(next) == italic_kern_subtype) {
590            tex_aux_trace_kerns(next, "ignoring italic kern", trace);
591            return 1;
592        }
593    }
594    return 0;
595}
596
597inline static int tex_aux_checked_left_kern_fnt_chr(halfword fnt, halfword chr, halfword state, halfword subtype, halfword size)
598{
599    halfword top = 0;
600    halfword bot = 0;
601    halfword hastop = (state & prime_script_state) || (state & post_super_script_state);
602    halfword hasbot = state & post_sub_script_state;
603    if (hastop && tex_math_has_class_option(subtype, left_top_kern_class_option)) {
604        top = tex_aux_math_x_size_scaled(fnt, tex_char_top_left_kern_from_font(fnt, chr), size);
605    }
606    if (hasbot && tex_math_has_class_option(subtype, left_bottom_kern_class_option)) {
607        bot = tex_aux_math_x_size_scaled(fnt, tex_char_bottom_left_kern_from_font(fnt, chr), size);
608    }
609    if (hastop && hasbot) {
610        return top > bot ? top : bot;
611    } else if (hastop) {
612        return top;
613    } else {
614        return bot;
615    }
616}
617
618inline static int tex_aux_checked_left_kern(halfword list, halfword state, halfword subtype, halfword size)
619{
620    if (list && node_type(list) == glyph_node) { 
621        return tex_aux_checked_left_kern_fnt_chr(glyph_font(list), glyph_character(list), state, subtype, size);
622    } else {
623        return 0;
624    }
625}
626
627inline static int tex_aux_checked_right_kern_fnt_chr(halfword fnt, halfword chr, halfword state, halfword subtype, halfword size)
628{
629    halfword top = 0;
630    halfword bot = 0;
631    halfword hastop = state & pre_super_script_state;
632    halfword hasbot = state & pre_sub_script_state;
633    if (hastop && tex_math_has_class_option(subtype, right_top_kern_class_option)) {
634        top = tex_aux_math_x_size_scaled(fnt, tex_char_top_right_kern_from_font(fnt, chr), size);
635    }
636    if (hasbot && tex_math_has_class_option(subtype, right_bottom_kern_class_option)) {
637        bot = tex_aux_math_x_size_scaled(fnt, tex_char_bottom_right_kern_from_font(fnt, chr), size);
638    }
639    if (hastop && hasbot) {
640        return top < bot ? bot : top;
641    } else if (hastop) {
642        return top;
643    } else {
644        return bot;
645    }
646}
647
648inline static int tex_aux_checked_right_kern(halfword list, halfword state, halfword subtype, halfword size)
649{
650    if (list && node_type(list) == glyph_node) { 
651        return tex_aux_checked_right_kern_fnt_chr(glyph_font(list), glyph_character(list), state, subtype, size);
652    } else {
653        return 0;
654    }
655}
656
657static scaled tex_aux_check_rule_thickness(halfword target, int size, halfword *fam, halfword control, halfword param)
658{
659    halfword family = noad_family(target);
660    if (family != unused_math_family) {
661        halfword font = tex_fam_fnt(family, size);
662        if (tex_aux_math_engine_control(font, control)) {
663            scaled thickness = tex_get_font_math_parameter(font, size, param);
664            if (thickness != undefined_math_parameter) {
665                *fam = family;
666                return thickness;
667            }
668        }
669    }
670    return undefined_math_parameter;
671}
672
673/*tex Fake character */
674
675// static bool tex_aux_has_fake_nucleus(halfword n)
676// {
677//     return n && node_type(n) == simple_noad 
678//         && noad_nucleus(n) && node_type(noad_nucleus(n)) == math_char_node
679//         && math_kernel_node_has_option(noad_nucleus(n), math_kernel_ignored_character);
680// }
681
682static halfword tex_aux_fake_nucleus(quarterword cls)
683{
684    halfword n = tex_new_node(simple_noad, cls);
685    halfword q = tex_new_node(math_char_node, 0);
686    set_noad_classes(n, cls);
687    noad_nucleus(n) = q;
688    math_kernel_node_set_option(q, math_kernel_ignored_character);
689    return n;
690}
691
692/*tex For tracing purposes we add a kern instead of just adapting the width. */
693
694static void tex_aux_fake_delimiter(halfword result)
695{
696    halfword amount = tex_aux_math_given_x_scaled(null_delimiter_space_par);
697    if (amount) {
698        box_width(result) = amount;
699        box_list(result) = tex_new_kern_node(amount, horizontal_math_kern_subtype);
700        tex_attach_attribute_list_copy(box_list(result), result);
701    }
702}
703
704/*tex 
705    A few helpers: 
706*/
707
708inline static int tex_aux_has_delimiter(halfword delimiter, halfword size) 
709{
710    return (
711        delimiter && (
712            (tex_fam_fnt(delimiter_small_family(delimiter), size) && delimiter_small_character(delimiter)) ||
713            (tex_fam_fnt(delimiter_large_family(delimiter), size) && delimiter_large_character(delimiter))
714        )
715    );
716}
717
718inline static int tex_aux_has_extensible(halfword delimiter, halfword size)
719{
720    if (delimiter && delimiter_small_character(delimiter)) {
721        halfword curfnt = tex_fam_fnt(delimiter_small_family(delimiter), size);
722        if (curfnt != null_font) {
723            return tex_char_extensible_recipe_front_last(curfnt, delimiter_small_character(delimiter)) ? 1 : 0;
724        }
725    }
726    return 0;
727}
728
729/*tex 
730    A variant on a suggestion on the list based on analysis by Ulrik Vieth is in the mean 
731    adapted. We keep these 500 and 2 because then we can use similar values. 
732*/
733
734static scaled tex_aux_get_delimiter_height(scaled height, scaled depth, int axis, int size, int style)
735{
736    scaled delta1 = height + depth;
737    scaled delta2 = depth;
738    scaled delta3 = 0;
739    halfword percent = tex_get_math_parameter_default(style, math_parameter_delimiter_percent, 0);
740    scaled shortfall = tex_get_math_y_parameter_default(style, math_parameter_delimiter_shortfall, 0);
741    if (axis) {
742        delta2 += tex_aux_math_axis(size);
743    }
744    delta1 -= delta2;
745    if (delta2 > delta1) {
746        /*tex |delta1| is max distance from axis */
747        delta1 = delta2;
748    }
749    delta3 = scaledround((delta1 / 500.0) * delimiter_factor_par * (percent / 100.0)); /* (total/2) * fraction  */
750    delta2 = 2 * delta1 - delimiter_shortfall_par - shortfall;                         /* (total*2) - shortfall */
751    return (delta3 < delta2) ? delta2 : delta3;                                        /* maximum of these two  */
752}
753
754/*tex
755
756    In order to convert mlists to hlists, i.e., noads to nodes, we need several subroutines that
757    are conveniently dealt with now.
758
759    Let us first introduce the macros that make it easy to get at the parameters and other font
760    information. A size code, which is a multiple of 256, is added to a family number to get an
761    index into the table of internal font numbers for each combination of family and size. (Be
762    alert: size codes get larger as the type gets smaller.) In the meantime we use different
763    maxima and packing as in \LUATEX.
764
765*/
766
767static const char *tex_aux_math_size_string(int s)
768{
769    switch (s) {
770        case script_script_size: return "scriptscriptfont";
771        case script_size:        return "scriptfont";
772        default:                 return "textfont";
773    }
774}
775
776/*tex Here is a simple routine that creates a flat copy of a nucleus. */
777
778static halfword tex_aux_math_clone(halfword n)
779{
780    if (n) {
781        halfword result = tex_new_node(node_type(n), 0);
782        tex_attach_attribute_list_copy(result, n);
783        tex_math_copy_char_data(result, n, 0);
784        return result;
785    } else {
786        return null;
787    }
788}
789
790/*tex
791    A helper used in void or phantom situations. We replace the content by a rule so that we still
792    have some content (handy for tracing).
793*/
794
795static halfword tex_aux_make_list_phantom(halfword source, int nowidth, halfword att)
796{
797    halfword target = null;
798    switch (node_type(source)) {
799        case hlist_node:
800            target = tex_new_node(hlist_node, node_subtype(source));
801            break;
802        case vlist_node:
803            target = tex_new_node(vlist_node, node_subtype(source));
804            break;
805    }
806    if (target) {
807        halfword rule = tex_new_rule_node(empty_rule_subtype);
808        tex_attach_attribute_list_attribute(target, att);
809        tex_attach_attribute_list_attribute(rule, att);
810        rule_width(rule) = nowidth ? 0 : box_width(source);
811        rule_height(rule) = box_height(source);
812        rule_depth(rule) = box_depth(source);
813        box_dir(target) = dir_lefttoright ;
814        box_height(target) = rule_height(rule);
815        box_depth(target) = rule_depth(rule);
816        box_width(target) = rule_width(rule);
817        box_shift_amount(target) = box_shift_amount(source);
818        box_list(target) = rule;
819        tex_flush_node_list(source);
820        return target;
821    } else {
822        return source;
823    }
824}
825
826/*tex
827
828    Here is a function that returns a pointer to a rule node having a given thickness |t|. The rule
829    will extend horizontally to the boundary of the vlist that eventually contains it.
830
831*/
832
833static halfword tex_aux_fraction_rule(scaled width, scaled height, halfword att, quarterword ruletype, halfword size, halfword fam)
834{
835    halfword rule = null;
836    int callback_id = lmt_callback_defined(math_rule_callback);
837    if (callback_id > 0) {
838        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);
839        if (rule && node_type(rule) != hlist_node) {
840            rule = tex_hpack(rule, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
841            node_subtype(rule) = math_rule_list;
842            tex_attach_attribute_list_attribute(rule, att);
843         }
844    }
845    if (! rule) {
846        if (math_rules_mode_par) {
847            rule = tex_new_rule_node(ruletype);
848            rule_data(rule) = tex_fam_fnt(fam, size); // we have font/fam/chr fields, why not use these  
849        } else {
850            rule = tex_new_rule_node(normal_rule_subtype);
851        }
852        rule_height(rule) = height;
853        rule_depth(rule) = 0;
854        tex_attach_attribute_list_attribute(rule, att);
855    }
856    return rule;
857}
858
859/*tex
860
861    The |overbar| function returns a pointer to a vlist box that consists of a given box |b|, above
862    which has been placed a kern of height |k| under a fraction rule of thickness |t| under
863    additional space of height |ht|.
864
865*/
866
867static 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, halfword attr);
868
869static halfword tex_aux_overbar(halfword box, scaled gap, scaled height, scaled krn, halfword att, quarterword index, halfword size, halfword fam, halfword topdelimiter, halfword style)
870{
871    halfword rule = (topdelimiter  > 0 && tex_aux_has_extensible(topdelimiter, size))
872        ? tex_aux_make_delimiter(null, topdelimiter, size, box_width(box), 1, style, 0, NULL, NULL, 0, 0, NULL, 0, att)
873        : tex_aux_fraction_rule(box_width(box), height, att, index, size, fam);
874    /*tex Safeguard: */
875    if (topdelimiter > 0 && box_width(rule) > box_width(box)) {
876        halfword delta = (box_width(rule) - box_width(box)) / 2;
877        tex_aux_prepend_hkern_to_box_list(box, delta, horizontal_math_kern_subtype, "narrow delimiter");
878        tex_aux_append_hkern_to_box_list(box, delta, horizontal_math_kern_subtype, "narrow delimiter");
879        box_width(box) = box_width(rule);
880    }
881    if (topdelimiter < 0) {
882        node_subtype(rule) = empty_rule_code;
883    }
884    if (gap) {
885        halfword kern = tex_new_kern_node(gap, vertical_math_kern_subtype);
886        tex_attach_attribute_list_attribute(kern, att);
887        tex_couple_nodes(kern, box);
888        tex_couple_nodes(rule, kern);
889    } else {
890        tex_couple_nodes(rule, box);
891    }
892    if (krn) {
893        halfword kern = tex_new_kern_node(krn, vertical_math_kern_subtype);
894        tex_attach_attribute_list_attribute(kern, att);
895        tex_couple_nodes(kern, rule);
896        rule = kern;
897    }
898    rule = tex_vpack(rule, 0, packing_additional, max_dimension, (singleword) math_direction_par, holding_none_option, NULL);
899    tex_attach_attribute_list_attribute(rule, att);
900    return rule;
901}
902
903static halfword tex_aux_underbar(halfword box, scaled gap, scaled height, scaled krn, halfword att, quarterword index, halfword size, halfword fam, halfword botdelimiter, halfword style)
904{
905    halfword rule = (botdelimiter && tex_aux_has_extensible(botdelimiter, size))
906        ? tex_aux_make_delimiter(null, botdelimiter, size, box_width(box), 1, style, 0, NULL, NULL, 0, 0, NULL, 0, att)
907        : tex_aux_fraction_rule(box_width(box), height, att, index, size, fam);
908    if (gap) {
909        halfword kern = tex_new_kern_node(gap, vertical_math_kern_subtype);
910        tex_attach_attribute_list_attribute(kern, att);
911        tex_couple_nodes(box, kern);
912        tex_couple_nodes(kern, rule);
913    } else {
914        tex_couple_nodes(box, rule);
915    }
916    if (krn) {
917        halfword kern = tex_new_kern_node(krn, vertical_math_kern_subtype);
918        tex_attach_attribute_list_attribute(kern, att);
919        tex_couple_nodes(rule, kern);
920    }
921    rule = tex_vpack(box, 0, packing_additional, max_dimension, (singleword) math_direction_par, holding_none_option, NULL);
922    tex_attach_attribute_list_attribute(rule, att);
923    /* */
924 // box_depth(rule) = box_total(rule) + krn - box_height(box); // MS needs to test this on the manual 
925    box_depth(rule) = box_total(rule) - box_height(box);
926    box_height(rule) = box_height(box);
927    /* */
928    return rule;
929}
930
931/*tex
932
933    Here is a subroutine that creates a new box, whose list contains a single character, and whose
934    width includes the italic correction for that character. The height or depth of the box will be
935    negative, if the height or depth of the character is negative. Thus, this routine may deliver a
936    slightly different result than |hpack| would produce.
937
938    The oldmath font flag can be used for cases where we pass a new school math constants (aka
939    parameters) table but have a (virtual) font assembled that uses old school type one fonts. In
940    that case we have a diffeent code path for:
941
942    \startitemize
943        \startitem rule thickness \stopitem
944        \startitem accent skew \stopitem
945        \startitem italic correction (normal width assumes it to be added) \stopitem
946        \startitem kerning \stopitem
947        \startitem delimiter construction \stopitem
948        \startitem accent placement \stopitem
949    \stopitemize
950
951    We keep this as reference but oldmath handling has been replaces by options that determine code
952    paths. We actually assuem that \OPENTRYPE fonts are used anyway. The flag is gone. 
953
954    In the traditional case an italic kern is always added and the |ic| variable is then passed
955    to the caller. For a while we had an option to add the correction to the width but now we
956    have the control options. So these are the options:
957
958    - traditional: insert a kern and pass that correction.
959    - opentype   : traditional_math_char_italic_width: add to width
960    -            : traditional_math_char_italic_pass : pass ic
961
962    Adding a kern in traditional mode is a mode driven option, not a font one.
963
964*/
965
966static halfword tex_aux_char_box(halfword fnt, int chr, halfword att, scaled *ic, quarterword subtype, scaled target, int style, int shrink, int stretch, int *isscaled)
967{
968    /*tex The new box and its character node. */
969    halfword glyph = tex_aux_new_math_glyph(fnt, chr, subtype);
970    halfword box = tex_new_null_box_node(hlist_node, math_char_list);
971    scaledwhd whd = tex_char_whd_from_glyph(glyph);
972    tex_attach_attribute_list_attribute(glyph, att);
973    tex_attach_attribute_list_attribute(box, att);
974    box_width(box) = whd.wd;
975    box_height(box) = whd.ht;
976    box_depth(box) = whd.dp;
977    box_list(box) = glyph;
978    if (isscaled) { 
979        *isscaled = 0;
980    }
981    if (tex_has_glyph_option(glyph, glyph_option_no_italic_correction)) {
982        whd.ic = 0;
983    }
984    if (! (stretch || shrink) && whd.ic) {
985        if (ic) {
986            *ic = whd.ic; /* also in open type? needs checking */
987        }
988        if (tex_aux_math_engine_control(fnt, math_control_apply_char_italic_kern)) {
989            tex_aux_math_insert_italic_kern(glyph, whd.ic, glyph, "box");
990            box_width(box) += whd.ic;
991        } else {
992            return box;
993        }
994    } else if (ic) {
995        *ic = 0;
996    }
997    if (target && whd.wd > 0) {
998        if (whd.wd < target && tex_aux_math_engine_control(fnt, math_control_extend_accents) && tex_char_has_tag_from_font(fnt, chr, extend_last_tag)) {
999            scaled margin = tex_get_math_x_parameter_default(style, math_parameter_accent_extend_margin, 0);
1000            scaled amount = target - 2 * margin;
1001            if (amount > 0) { 
1002                glyph_x_scale(glyph) = lround((double) glyph_x_scale(glyph) * amount/whd.wd);
1003                glyph_x_offset(glyph) = (whd.wd - amount)/2;
1004                if (isscaled) { 
1005                    *isscaled = 1;
1006                }
1007            }
1008            return box; 
1009        }
1010        if ((shrink && (whd.wd > target)) || (stretch && (whd.wd < target))) { // we need to keep an eye on it 
1011            glyph_x_scale(glyph) = lround((double) glyph_x_scale(glyph) * target/whd.wd);
1012         // glyph_x_offset(glyph) = (whd.wd - target)/2;
1013            whd = tex_char_whd_from_glyph(glyph);
1014            box_width(box) = whd.wd;
1015            if (isscaled) { 
1016                *isscaled = 1;
1017            }
1018        }
1019    }
1020    return box;
1021}
1022
1023/*tex 
1024    There is no need to deal with an italic correction here. If there is one in an extensible we 
1025    have a real weird font! So in this version we don't end up with a redicoulous amount of hlists
1026    in a horizontal extensible with is nicer when we trace. Actualy, the only extensibles that are
1027    italic are integrals and these are not in traditional fonts. 
1028
1029    We only got a warning with Lucida that has italic correction on the begin and end glyphs of 
1030    integrals and it looks real bad it we add that, so now we don't even warn any more and just 
1031    ignore it. 
1032*/
1033
1034static scaled tex_aux_stack_char_into_box(halfword box, halfword fnt, int chr, quarterword subtype, int horiziontal)
1035{
1036    halfword glyph = tex_aux_new_math_glyph(fnt, chr, subtype);
1037    scaledwhd whd = tex_char_whd_from_glyph(glyph);
1038    halfword list = box_list(box);
1039    tex_attach_attribute_list_attribute(glyph, get_attribute_list(box));
1040    if (horiziontal) {
1041        if (list) {
1042            tex_couple_nodes(tex_tail_of_node_list(list), glyph);
1043        } else {
1044            box_list(box) = glyph;
1045        }
1046        if (box_height(box) < whd.ht) {
1047            box_height(box) = whd.ht;
1048        }
1049        if (box_depth(box) < whd.dp) {
1050            box_depth(box) = whd.dp;
1051        }
1052     // if (whd.ic) { 
1053     //     tex_print_message("italic correction found in horizontal delimiter parts, needs checking"); 
1054     // }
1055        return whd.wd;
1056    } else { 
1057        halfword boxed = tex_new_null_box_node(hlist_node, math_char_list);
1058        tex_attach_attribute_list_attribute(boxed, get_attribute_list(box));
1059        box_width(boxed) = whd.wd;
1060        box_height(boxed) = whd.ht;
1061        box_depth(boxed) = whd.dp;
1062        box_list(boxed) = glyph;
1063        tex_try_couple_nodes(boxed, list);
1064        box_list(box) = boxed;
1065     // box_height(b) = box_height(boxed);
1066        if (box_width(box) < whd.wd) {
1067            box_width(box) = whd.wd;
1068        }
1069     // if (whd.ic) { 
1070     //     tex_print_message("italic correction found in vertical delimiter parts, needs checking");
1071     // }
1072        return whd.ht + whd.dp;
1073    }
1074}
1075
1076static void tex_aux_stack_glue_into_box(halfword box, scaled min, scaled max) {
1077    halfword glue = tex_new_glue_node(zero_glue, user_skip_glue); /* todo: subtype, correction_skip_glue? */
1078    glue_amount(glue) = min;
1079    glue_stretch(glue) = max - min;
1080    tex_add_glue_option(glue, glue_option_no_auto_break);
1081    tex_attach_attribute_list_copy(glue, box);
1082    if (node_type(box) == vlist_node) {
1083        tex_try_couple_nodes(glue, box_list(box));
1084        box_list(box) = glue;
1085    } else {
1086        halfword list = box_list(box);
1087        if (list) {
1088            tex_couple_nodes(tex_tail_of_node_list(list), glue);
1089        } else {
1090            box_list(box) = glue;
1091        }
1092    }
1093}
1094
1095/*tex
1096
1097    \TEX's most important routine for dealing with formulas is called |mlist_to_hlist|. After a
1098    formula has been scanned and represented as an mlist, this routine converts it to an hlist that
1099    can be placed into a box or incorporated into the text of a paragraph. The explicit parameter
1100    |cur_mlist| points to the first node or noad in the given mlist (and it might be |null|). The
1101    parameter |penalties| is |true| if penalty nodes for potential line breaks are to be inserted
1102    into the resulting hlist, the parameter |cur_style| is a style code. After |mlist_to_hlist| has
1103    acted, |vlink (temp_head)| points to the translated hlist.
1104
1105    Since mlists can be inside mlists, the procedure is recursive. And since this is not part of
1106    \TEX's inner loop, the program has been written in a manner that stresses compactness over
1107    efficiency. (This is no longer always true in \LUAMETATEX.)
1108
1109*/
1110
1111static halfword tex_aux_top_extensible_from_box(halfword e)
1112{
1113    if (node_type(e) == vlist_node && node_subtype(e) == math_v_extensible_list) {
1114        e = box_list(e);
1115        while (e) { 
1116            if (node_type(e) == hlist_node && box_list(e) && node_type(box_list(e)) == glyph_node) { 
1117                return box_list(e); /* hit is first */
1118            } else {
1119                e = node_next(e);
1120            }
1121        }
1122    }
1123    return null;
1124}
1125
1126static halfword tex_aux_bottom_extensible_from_box(halfword e)
1127{
1128    halfword g = null;
1129    if (node_type(e) == vlist_node && node_subtype(e) == math_v_extensible_list) {
1130        e = box_list(e);
1131        while (e) { 
1132            if (node_type(e) == hlist_node && box_list(e) && node_type(box_list(e)) == glyph_node) { 
1133                g = box_list(e); /* last so far */
1134            }
1135            e = node_next(e);
1136        }
1137    }
1138    return g; /* hit is last */
1139}
1140
1141static halfword tex_aux_get_delimiter_box(halfword fnt, halfword chr, scaled target, scaled minoverlap, int horizontal, halfword att)
1142{
1143    halfword size = lmt_math_state.size;
1144    int callback_id = lmt_callback_defined(make_extensible_callback);
1145    if (callback_id > 0) {
1146        /*tex
1147            This call is not optimized as it hardly makes sense to use it ... special
1148            and a bit of feature creep too.
1149        */
1150        halfword boxed = null;
1151        lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "ddddbNd->N", fnt, chr, target, minoverlap, horizontal, att, size, &boxed);
1152        if (boxed) {
1153            switch (node_type(boxed)) {
1154                case hlist_node:
1155                case vlist_node:
1156                    return boxed;
1157                default:
1158                    tex_formatted_error("fonts", "invalid extensible character %i created for font %i, [h|v]list expected", chr, fnt);
1159                    break;
1160            }
1161        }
1162    }
1163    return tex_make_extensible(fnt, chr, target, minoverlap, horizontal, att, size);
1164}
1165
1166halfword tex_make_extensible(halfword fnt, halfword chr, scaled target, scaled minoverlap, int horizontal, halfword att, halfword size)
1167{
1168    /*tex natural (maximum) size of the stack */
1169    scaled max_natural = 0;
1170    /*tex amount of possible shrink in the stack */
1171    scaled max_shrink = 0;
1172    scaled overlap;
1173    /*tex a temporary counter number of extensible pieces */
1174    int pieces = 0;
1175    /*tex new box */
1176    halfword box = tex_new_null_box_node(horizontal ? hlist_node : vlist_node, horizontal ? math_h_extensible_list : math_v_extensible_list);
1177    /*tex number of times to repeat each repeatable item in |ext| */
1178    int with_extenders = -1;
1179    int n_of_extenders = 0;
1180    int n_of_normal = 0;
1181    extinfo *extensible = tex_char_extensible_recipe_from_font(fnt, chr);
1182    if (minoverlap < 0) {
1183        minoverlap = 0;
1184    }
1185    tex_attach_attribute_list_attribute(box, att);
1186    for (extinfo *e = extensible; e; e = e->next) {
1187        if (! tex_char_exists(fnt, e->glyph)) {
1188            tex_handle_error(
1189                normal_error_type,
1190                "Extension part doesn't exist.",
1191                "Each glyph part in an extensible item should exist in the font. I will give up\n"
1192                "trying to find a suitable size for now. Fix your font!"
1193            );
1194            tex_aux_fake_delimiter(box);
1195            return box;
1196        } else {
1197            if (e->extender == math_extension_repeat) {
1198                n_of_extenders++;
1199            } else {
1200                n_of_normal++;
1201            }
1202            /*tex 
1203                No negative overlaps or advances are allowed. Watch out, we patch the glyph data at
1204                the \TEX\ end here. 
1205            */
1206            if (e->start_overlap < 0 || e->end_overlap < 0 || e->advance < 0) {
1207                tex_handle_error(
1208                    normal_error_type,
1209                    "Extensible recipe has negative fields.",
1210                    "All measurements in extensible items should be positive. To get around this\n"
1211                    "problem, I have changed the font metrics. Fix your font!"
1212                );
1213                if (e->start_overlap < 0) {
1214                    e->start_overlap = 0;
1215                }
1216                if (e->end_overlap < 0) {
1217                    e->end_overlap = 0;
1218                }
1219                if (e->advance < 0) {
1220                    e->advance = 0;
1221                }
1222            }
1223        }
1224    }
1225    if (n_of_normal == 0) {
1226        tex_handle_error(
1227            normal_error_type,
1228            "Extensible recipe has no fixed parts.",
1229            "Each extensible recipe should have at least one non-repeatable part. To get\n"
1230            "around this problem, I have changed the first part to be non-repeatable. Fix your\n"
1231            "font!"
1232        );
1233        if (extensible) { /* get rid of warning */
1234            extensible->extender = 0;
1235        }
1236        n_of_normal = 1;
1237        n_of_extenders--;
1238    }
1239    /*tex
1240
1241        In the meantime the Microsoft Typography website has a good description of the process: 
1242
1243        \startitemize
1244            \startitem
1245                Assemble all parts with all extenders removed and with connections overlapping by 
1246                the maximum amount. This gives the smallest possible result.
1247            \stopitem 
1248            \startitem
1249                Determine how much extra width/height can be obtained from all existing connections
1250                between neighboring parts by using minimal overlaps. If that is enough to achieve 
1251                the size goal, extend each connection equally by changing overlaps of connectors to
1252                finish the job.
1253            \stopitem 
1254            \startitem
1255                If all connections have been extended to the minimum overlap and further growth is 
1256                needed, add one of each extender, and repeat the process from the first step.
1257            \stopitem 
1258        \stopitemize 
1259
1260        Original comment: |ext| holds a linked list of numerous items that may or may not be 
1261        repeatable. For the total height, we have to figure out how many items are needed to create
1262        a stack of at least |v|. The next |while| loop does that. It has two goals: it finds out
1263        the natural height |b_max| of the all the parts needed to reach at least |v|, and it sets
1264        |with_extenders| to the number of times each of the repeatable items in |ext| has to be 
1265        repeated to reach that height.
1266
1267        It's an example figure it out once, write the solution, test it well and then never look 
1268        back code. 
1269    */
1270    while (max_natural < target && n_of_extenders > 0) {
1271        overlap = 0;
1272        max_natural = 0;
1273        with_extenders++;
1274        if (horizontal) {
1275            for (extinfo *e = extensible; e; e = e->next) {
1276                if (e->extender == 0) {
1277                    scaled initial = tex_aux_math_x_size_scaled(fnt, e->start_overlap, size);
1278                    scaled advance = tex_aux_math_x_size_scaled(fnt, e->advance, size);
1279                    if (minoverlap < initial) {
1280                        initial = minoverlap;
1281                    }
1282                    if (overlap < initial) {
1283                        initial = overlap;
1284                    }
1285                    if (advance == 0) {
1286                        advance = tex_aux_math_x_size_scaled(fnt, tex_char_width_from_font(fnt, e->glyph), size); /* todo: combine */
1287                    }
1288                    if (advance <= 0) {
1289                        tex_formatted_error("fonts", "bad horizontal extensible character %i in font %i", chr, fnt);
1290                    }
1291                    max_natural += advance - initial;
1292                    overlap = tex_aux_math_x_size_scaled(fnt, e->end_overlap, size);
1293                } else {
1294                    pieces = with_extenders;
1295                    while (pieces > 0) {
1296                        scaled initial = tex_aux_math_x_size_scaled(fnt, e->start_overlap, size);
1297                        scaled advance = tex_aux_math_x_size_scaled(fnt, e->advance, size);
1298                        if (minoverlap < initial) {
1299                            initial = minoverlap;
1300                        }
1301                        if (overlap < initial) {
1302                            initial = overlap;
1303                        }
1304                        if (advance == 0) {
1305                            advance = tex_aux_math_x_size_scaled(fnt, tex_char_width_from_font(fnt, e->glyph), size); /* todo: combine */
1306                        }
1307                        if (advance <= 0) {
1308                            tex_formatted_error("fonts", "bad horizontal extensible character %i in font %i", chr, fnt);
1309                        }
1310                        max_natural += advance - initial;
1311                        overlap = tex_aux_math_x_size_scaled(fnt, e->end_overlap, size);
1312                        pieces--;
1313                    }
1314                }
1315            }
1316        } else {
1317            for (extinfo *e = extensible; e; e = e->next) {
1318                if (e->extender == 0) {
1319                    scaled initial = tex_aux_math_y_size_scaled(fnt, e->start_overlap, size);
1320                    scaled advance = tex_aux_math_y_size_scaled(fnt, e->advance, size);
1321                    if (minoverlap < initial) {
1322                        initial = minoverlap;
1323                    }
1324                    if (overlap < initial) {
1325                        initial = overlap;
1326                    }
1327                    if (advance == 0) {
1328                        advance = tex_aux_math_y_size_scaled(fnt, tex_char_total_from_font(fnt, e->glyph), size); /* todo: combine */
1329                    }
1330                    if (advance <= 0) {
1331                        tex_formatted_error("fonts", "bad vertical extensible character %i in font %i", chr, fnt);
1332                    }
1333                    max_natural += advance - initial;
1334                    overlap = tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
1335                } else {
1336                    pieces = with_extenders;
1337                    while (pieces > 0) {
1338                        scaled initial = tex_aux_math_y_size_scaled(fnt, e->start_overlap, size);
1339                        scaled advance = tex_aux_math_y_size_scaled(fnt, e->advance, size);
1340                        if (minoverlap < initial) {
1341                            initial = minoverlap;
1342                        }
1343                        if (overlap < initial) {
1344                            initial = overlap;
1345                        }
1346                        if (advance == 0) {
1347                            advance = tex_aux_math_y_size_scaled(fnt, tex_char_total_from_font(fnt, e->glyph), size); /* todo: combine */
1348                        }
1349                        if (advance <= 0) {
1350                            tex_formatted_error("fonts", "bad vertical extensible character %i in font %i", chr, fnt);
1351                        }
1352                        max_natural += advance - initial;
1353                        overlap = tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
1354                        pieces--;
1355                    }
1356                }
1357            }
1358        }
1359    }
1360    /*tex
1361        Assemble box using |with_extenders| copies of each extender, with appropriate glue wherever
1362        an overlap occurs.
1363    */
1364    overlap = 0;
1365    max_natural = 0;
1366    max_shrink = 0;
1367    for (extinfo *e = extensible; e; e = e->next) {
1368        if (e->extender == 0) {
1369            scaled progress;
1370            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);
1371            if (overlap < initial) {
1372                initial = overlap;
1373            }
1374            progress = initial;
1375            if (minoverlap < initial) {
1376                initial = minoverlap;
1377            }
1378            if (progress > 0) {
1379                tex_aux_stack_glue_into_box(box, -progress, -initial);
1380                max_shrink += (-initial) - (-progress);
1381                max_natural -= progress;
1382            }
1383            max_natural += tex_aux_stack_char_into_box(box, fnt, e->glyph, glyph_math_extensible_subtype, horizontal);
1384            overlap = horizontal ? tex_aux_math_x_size_scaled(fnt, e->end_overlap, size) : tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
1385            pieces--;
1386        } else {
1387            pieces = with_extenders;
1388            while (pieces > 0) {
1389                scaled progress;
1390                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);
1391                if (overlap < initial) {
1392                    initial = overlap;
1393                }
1394                progress = initial;
1395                if (minoverlap < initial) {
1396                    initial = minoverlap;
1397                }
1398                if (progress > 0) {
1399                    tex_aux_stack_glue_into_box(box, -progress, -initial);
1400                    max_shrink += (-initial) - (-progress);
1401                    max_natural -= progress;
1402                }
1403                max_natural += tex_aux_stack_char_into_box(box, fnt, e->glyph, glyph_math_extensible_subtype, horizontal);
1404                overlap = horizontal ? tex_aux_math_x_size_scaled(fnt, e->end_overlap, size) : tex_aux_math_y_size_scaled(fnt, e->end_overlap, size);
1405                pieces--;
1406            }
1407        }
1408    }
1409    /*tex Set glue so as to stretch the connections if needed. */
1410    if (target > max_natural && max_shrink > 0) {
1411     // if (1) {
1412     //     halfword b;
1413     //     if (horizontal) {
1414     //         b = tex_hpack(box_list(box), target, packing_exactly, (singleword) math_direction_par, holding_none_option);
1415     //     } else {
1416     //         b = tex_vpack(box_list(box), target, packing_exactly, max_dimension, (singleword) math_direction_par, holding_none_option);
1417     //     }
1418     //     box_glue_order(box) = box_glue_order(b);
1419     //     box_glue_sign(box) = box_glue_sign(b);
1420     //     box_glue_set(box) = box_glue_set(b);
1421     //     box_list(b) = null;
1422     //     tex_flush_node(b);
1423     //     max_natural = target;
1424     // } else {
1425            scaled delta = target - max_natural;
1426            /*tex Don't stretch more than |s_max|. */
1427            if (delta > max_shrink) {
1428                if (tracing_math_par >= 1) {
1429                    tex_begin_diagnostic();
1430                    tex_print_format("[math: extensible clipped, target %p, natural %p, shrink %p, clip %p]",
1431                        target, max_natural, max_shrink, delta - max_shrink
1432                    );
1433                    tex_end_diagnostic();
1434                }
1435                delta = max_shrink;
1436            }
1437            box_glue_order(box) = normal_glue_order;
1438            box_glue_sign(box) = stretching_glue_sign;
1439            box_glue_set(box) = (glueratio) (delta / (glueratio) max_shrink);
1440            max_natural += delta;
1441     // }
1442    }
1443    if (horizontal) {
1444        box_width(box) = max_natural;
1445        node_subtype(box) = math_h_extensible_list;
1446    } else {
1447        box_height(box) = max_natural;
1448        node_subtype(box) = math_v_extensible_list;
1449    }
1450    return box;
1451}
1452
1453/*tex
1454
1455    The |var_delimiter| function, which finds or constructs a sufficiently large delimiter, is the
1456    most interesting of the auxiliary functions that currently concern us. Given a pointer |d| to a
1457    delimiter field in some noad, together with a size code |s| and a vertical distance |v|, this
1458    function returns a pointer to a box that contains the smallest variant of |d| whose height plus
1459    depth is |v| or more. (And if no variant is large enough, it returns the largest available
1460    variant.) In particular, this routine will construct arbitrarily large delimiters from
1461    extensible components, if |d| leads to such characters.
1462
1463    The value returned is a box whose |shift_amount| has been set so that the box is vertically
1464    centered with respect to the axis in the given size. If a built-up symbol is returned, the
1465    height of the box before shifting will be the height of its topmost component.
1466
1467*/
1468
1469static halfword register_extensible(halfword fnt, halfword chr, int size, halfword result, halfword att)
1470{
1471    int callback_id = lmt_callback_defined(register_extensible_callback);
1472    if (callback_id > 0) {
1473        halfword boxed = null;
1474        lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "ddddN->N", fnt, chr, size, att, result, &boxed);
1475        if (boxed) {
1476            switch (node_type(boxed)) {
1477                case hlist_node:
1478                case vlist_node:
1479                 // tex_attach_attribute_list_attribute(boxed, att); /* yes or no */
1480                    return boxed;
1481                default:
1482                    tex_formatted_error("fonts", "invalid extensible character %U registered for font %F, [h|v]list expected", chr, fnt);
1483                    break;
1484            }
1485        }
1486    }
1487    return result;
1488}
1489
1490/*tex
1491     A first version passed the first and last glyph around but then we need to maintain a copy because
1492     we can register a composed delimiter which can result in a flush of these nodes. 
1493*/
1494
1495static 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, halfword attr)
1496{
1497    /*tex the box that will be constructed */
1498    halfword result = null;
1499    /*tex best-so-far and tentative font codes */
1500    halfword fnt = null_font;
1501    /*tex best-so-far and tentative character codes */
1502    int chr = 0;
1503    int nxtchr = 0;
1504    /*tex are we trying the large variant? */
1505    int large_attempt = 0;
1506    int do_parts = 0;
1507    int isscaled = 0;
1508    int shrink = flat && has_noad_option_shrink(target);  
1509    int stretch = flat && has_noad_option_stretch(target);
1510    /*tex to save the current attribute list */
1511    halfword att = null;
1512    if (extremes) { 
1513        extremes->tfont = null_font;
1514        extremes->bfont = null_font; 
1515        extremes->tchar = 0;
1516        extremes->bchar = 0;
1517        extremes->height = 0;
1518        extremes->depth = 0;
1519    }
1520    if (! tex_aux_has_delimiter(delimiter, size)) {
1521        halfword result = tex_new_null_box_node(hlist_node, math_v_delimiter_list);
1522        tex_attach_attribute_list_copy(result, delimiter);
1523        if (! flat) {
1524            tex_aux_fake_delimiter(result);
1525        }
1526        tex_flush_node(delimiter); /* no, we can assign later on ... better a fatal error here */
1527        return result;
1528    }
1529    if (delimiter) {
1530        /*tex largest height-plus-depth so far */
1531        scaled besttarget = 0;
1532        /*tex |z| runs through font family members */
1533        int curfam = delimiter_small_family(delimiter);
1534        int curchr = 0;
1535        int count = 0;
1536        int prvfnt = null_font;
1537        int prvchr = 0;
1538        nxtchr = delimiter_small_character(delimiter);
1539        while (1) {
1540            /*tex
1541                The search process is complicated slightly by the facts that some of the characters
1542                might not be present in some of the fonts, and they might not be probed in increasing
1543                order of height. When we run out of sizes (variants) and end up at an extensible 
1544                pointer (parts) we quit the loop. 
1545            */
1546            if (curfam || nxtchr) {
1547                halfword curfnt = tex_fam_fnt(curfam, size);
1548                if (curfnt != null_font) {
1549                    curchr = nxtchr;
1550                  CONTINUE:
1551                    count++;
1552                    if (tex_char_exists(curfnt, curchr)) {
1553                        if (! tex_char_has_tag_from_font(curfnt, curchr, force_extensible_tag)) {
1554                            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);
1555                            if (nooverflow && total >= targetsize) {
1556                                if (total > targetsize && prvfnt != null_font) {
1557                                    fnt = prvfnt;
1558                                    chr = prvchr;
1559                                } else { 
1560                                    fnt = curfnt;
1561                                    chr = curchr;
1562                                }
1563                                besttarget = total; /* ? */
1564                                goto FOUND;
1565                            } else if (total >= besttarget) {
1566                                prvfnt = curfnt;
1567                                prvchr = curchr;
1568                                fnt = curfnt;
1569                                chr = curchr;
1570                                besttarget = total;
1571                                if (total >= (targetsize - tolerance)) {
1572                                    goto FOUND;
1573                                }
1574                           }
1575                        }
1576                        if (tex_char_has_tag_from_font(curfnt, curchr, extensible_tag)) {
1577                            if (tex_char_has_tag_from_font(curfnt, curchr, horizontal_tag) || tex_char_has_tag_from_font(curfnt, curchr, vertical_tag)) {
1578                                /*tex We only check when we are explicit. */
1579                                if (flat ? tex_char_has_tag_from_font(curfnt, curchr, horizontal_tag) : tex_char_has_tag_from_font(curfnt, curchr, vertical_tag)) {
1580                                    fnt = curfnt;
1581                                    chr = curchr;
1582                                    do_parts = 1;
1583                                }
1584                            }
1585                            goto FOUND;
1586                        } else if (count > scaling_factor) {
1587                            tex_formatted_warning("fonts", "endless loop in extensible character %U of font %F", curchr, curfnt);
1588                            goto FOUND;
1589                        } else if (tex_char_has_tag_from_font(curfnt, curchr, list_tag)) {
1590                            prvfnt = curfnt;
1591                            prvchr = curchr;
1592                            curchr = tex_char_next_from_font(curfnt, curchr);
1593                            goto CONTINUE;
1594                        }
1595                    }
1596                }
1597            }
1598            if (large_attempt) {
1599                /*tex There were none large enough. */
1600                goto FOUND;
1601            } else {
1602                large_attempt = 1;
1603                curfam = delimiter_large_family(delimiter);
1604                nxtchr = delimiter_large_character(delimiter);
1605            }
1606        }
1607    }
1608  FOUND:
1609    if (delimiter) {
1610        /*tex
1611            The builder below sets the list if needed and we dereference later because otherwise
1612            the list gets flushed before it can be reused.
1613        */
1614        att = get_attribute_list(delimiter);
1615        wipe_attribute_list_only(delimiter);
1616        tex_flush_node(delimiter);
1617    }
1618 // if (! att) {
1619 //     att = get_attribute_list(target); /* needs checking */
1620 // }
1621    if (fnt != null_font) {
1622        /*tex
1623            When the following code is executed, |do_parts| will be true if a built-up symbol is
1624            supposed to be returned.
1625        */
1626        extinfo *ext = do_parts ? tex_char_extensible_recipe_from_font(fnt, chr) : NULL;
1627        if (ext) {
1628            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);
1629            result = tex_aux_get_delimiter_box(fnt, chr, targetsize, minoverlap, flat, attr ? attr : att);
1630            if (stretch && flat && (box_width(result) > targetsize)) { // threshold nooverflow
1631                tex_flush_node_list(result);
1632                do_parts = 0;
1633                goto HERE;
1634            }
1635            if (delta) {
1636                /*tex Not yet done: horizontal italics. */
1637                if (tex_aux_math_engine_control(fnt, math_control_apply_vertical_italic_kern)) {
1638                    *delta = tex_aux_math_x_size_scaled(fnt, tex_char_extensible_italic_from_font(fnt, nxtchr), size);
1639                } else {
1640                    *delta = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, nxtchr), size);
1641                }
1642            }
1643            if (stack) {
1644                *stack = 1 ;
1645            }
1646            if (! flat && extremes) { 
1647                 halfword first = tex_aux_top_extensible_from_box(result);
1648                 halfword last = tex_aux_bottom_extensible_from_box(result);
1649                 extremes->tfont = glyph_font(first);
1650                 extremes->tchar = glyph_character(first);
1651                 extremes->bfont = glyph_font(last);
1652                 extremes->bchar = glyph_character(last);
1653                 extremes->height = box_height(result); 
1654                 extremes->depth = box_depth(result);
1655            }
1656        } else {
1657            /*tex
1658                Here italic is added to width in traditional fonts which makes the delimiter get
1659                the real width. An \OPENTYPE\ font already has the right width. There is one case
1660                where |delta| (ic) gets subtracted but only for a traditional font. In that case
1661                the traditional width (which is fake width + italic) becomes less and the delta is
1662                added. See (**).
1663            */
1664        HERE:
1665            result = tex_aux_char_box(fnt, chr, att, delta, glyph_math_delimiter_subtype, flat ? targetsize : 0, style, shrink, stretch, &isscaled);
1666            if (flat) { 
1667                /* This will be done when we have a reasonable example. */
1668            } else {
1669                if (box_total(result) < targetsize && tex_aux_math_engine_control(fnt, math_control_extend_delimiters) && tex_char_has_tag_from_font(fnt, chr, extend_last_tag)) {
1670                    halfword glyph = box_list(result);
1671                    if (glyph && node_type(glyph) == glyph_node) {
1672                        scaled margin = tex_get_math_y_parameter_default(style, math_parameter_delimiter_extend_margin, 0);
1673                        scaled amount = targetsize - 2 * margin;
1674                        if (amount > 0) { 
1675                            double ratio = (double) amount/box_total(result);
1676                            glyph_y_scale(glyph) = lround((double) glyph_y_scale(glyph) * ratio);
1677                            glyph_y_offset(glyph) = lround((double) box_total(glyph) * ratio);
1678                            box_height(result) = lround((double) box_height(result) * ratio);
1679                            box_depth(result) = lround((double) box_depth(result) * ratio);
1680                        }
1681                    }
1682                }
1683            }
1684            if (stack) {
1685                *stack = 0 ;
1686            }
1687            if (! flat && extremes) { 
1688                 extremes->tfont = fnt;
1689                 extremes->tchar = chr;
1690                 extremes->bfont = fnt;
1691                 extremes->bchar = chr;
1692                 extremes->height = box_height(result); 
1693                 extremes->depth = box_depth(result);
1694            }
1695        }
1696    } else {
1697        /*tex This can be an empty one as is often the case with fractions! */
1698        result = tex_new_null_box_node(hlist_node, flat ? math_h_delimiter_list : math_v_delimiter_list);
1699        tex_attach_attribute_list_attribute(result, att);
1700        /*tex Use this width if no delimiter was found. */
1701        if (! flat) {
1702            tex_aux_fake_delimiter(result);
1703        }
1704        if (delta) {
1705            *delta = 0;
1706        }
1707        if (stack) {
1708            *stack = 0 ;
1709        }
1710    }
1711    if (do_parts) {
1712        if (target && (has_noad_option_phantom(target) || has_noad_option_void(target))) {
1713            result = tex_aux_make_list_phantom(result, has_noad_option_void(target), att);
1714        } else {
1715            result = register_extensible(fnt, chr, size, result, att);
1716        }
1717    }
1718    if (! flat) {
1719        /*tex 
1720            We have a vertical variant. Case 1 deals with the fact that fonts can lie about their
1721            dimensions which happens in tfm files where there are a limited number of heights and 
1722            depths. However, that doesn't work out well when we want to anchor script later on in 
1723            a more sophisticated way. Most \OPENTYPE\ fonts have proper heights and depth but there 
1724            are some that don't. Problems can show up when we use kerns (as \CONTEXT\ does via 
1725            goodie files) and the relevant class option has been enabled. In order to deal with the 
1726            problematic fonts we can disable this via a font option. The natural height and depth 
1727            are communicated via extremes and kerns. 
1728            
1729            For fonts that have their shapes positioned properly around their axis case 1 doesn't 
1730            interfere but could as well be skipped. These shapes can also be used directly in 
1731            the input if needed (basically case 1 then becomes case 4).  
1732        */
1733        switch (shift) { 
1734            case 0:
1735                box_shift_amount(result) = tex_half_scaled(box_height(result) - box_depth(result));
1736                break;
1737            case 1:
1738                box_shift_amount(result) = tex_half_scaled(box_height(result) - box_depth(result));
1739                box_shift_amount(result) -= tex_aux_math_axis(size);
1740                break;
1741            case 2: 
1742                box_shift_amount(result) = move;
1743                break;
1744        }
1745        if (do_parts && extremes && extremes->height) {
1746            extremes->height -= box_shift_amount(result);
1747            extremes->depth += box_shift_amount(result);
1748        }
1749    }
1750    /* This needs checking in case the ref was changed. */
1751    delete_attribute_reference(att);
1752    if ((node_type(result) == hlist_node || node_type(result) == vlist_node) && node_subtype(result) == unknown_list) {
1753        node_subtype(result) = flat ? math_h_delimiter_list : math_v_delimiter_list;
1754    }
1755    return result;
1756}
1757
1758/*tex
1759
1760    The next subroutine is much simpler; it is used for numerators and denominators of fractions as
1761    well as for displayed operators and their limits above and below. It takes a given box~|b| and
1762    changes it so that the new box is centered in a box of width~|w|. The centering is done by
1763    putting |\hss| glue at the left and right of the list inside |b|, then packaging the new box;
1764    thus, the actual box might not really be centered, if it already contains infinite glue.
1765
1766    The given box might contain a single character whose italic correction has been added to the
1767    width of the box; in this case a compensating kern is inserted. Actually, we now check for
1768    the last glyph.
1769
1770*/
1771
1772static halfword tex_aux_rebox(halfword box, scaled width, halfword size)
1773{
1774    (void) size;
1775    if (box_width(box) != width && box_list(box)) {
1776        /*tex temporary registers for list manipulation */
1777        halfword head = box_list(box);
1778        quarterword subtype = node_subtype(box);
1779        halfword att = get_attribute_list(box);
1780        /*tex When the next two are not seen we can wipe att so we reserve by bump! */
1781        add_attribute_reference(att);
1782        if (node_type(box) == vlist_node) {
1783            box = tex_hpack(box, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
1784            node_subtype(box) = subtype;
1785            tex_attach_attribute_list_attribute(box, att);
1786            head = box_list(box);
1787        } else if (head && node_type(head) == glyph_node && ! node_next(head)) {
1788            /*tex
1789                This hack is for traditional fonts so with a proper opentype font we don't end up
1790                here (because then the width is unchanged). However controls can cheat so there is
1791                no explicit check for an opentype situation here.
1792            */
1793            if (tex_aux_math_engine_control(glyph_font(head), math_control_rebox_char_italic_kern)) {
1794                scaled boxwidth = box_width(box);
1795                scaled chrwidth = tex_char_width_from_glyph(head);
1796                if (boxwidth != chrwidth) {
1797                    /*tex
1798                        This is typical old font stuff. Maybe first check if we can just
1799                        remove a trailing kern. Also, why not just adapt the box width.
1800                    */
1801                    halfword kern = tex_new_kern_node(boxwidth - chrwidth, italic_kern_subtype); /* horizontal_math_kern */
1802                    tex_attach_attribute_list_attribute(kern, att);
1803                    tex_couple_nodes(head, kern);
1804                }
1805            }
1806        }
1807        box_list(box) = null;
1808        tex_flush_node(box);
1809        { 
1810            halfword right = tex_new_glue_node(fi_ss_glue, user_skip_glue); /* todo: subtype, correction_skip_glue? */
1811            halfword left = tex_new_glue_node(fi_ss_glue, user_skip_glue);  /* todo: subtype, correction_skip_glue? */
1812            tex_add_glue_option(left, glue_option_no_auto_break);
1813            tex_add_glue_option(right, glue_option_no_auto_break);
1814            tex_attach_attribute_list_attribute(left, att);
1815            tex_attach_attribute_list_attribute(right, att);
1816            tex_couple_nodes(left, head);
1817            tex_couple_nodes(tex_tail_of_node_list(head), right);
1818            box = tex_hpack(left, width, packing_exactly, direction_unknown, holding_none_option, box_limit_none);
1819            tex_attach_attribute_list_attribute(box, att);
1820            node_subtype(box) = subtype;
1821        }
1822        /*tex As we bumped we now need to unbump the ref counter! */
1823        delete_attribute_reference(att);
1824    } else {
1825        box_width(box) = width;
1826    }
1827    return box;
1828}
1829
1830/*tex
1831
1832    Here is a subroutine that creates a new glue specification from another one that is expressed
1833    in |mu|, given the value of the math unit.
1834
1835*/
1836
1837inline static scaled tex_aux_mu_mult(scaled a, scaled n, scaled f)
1838{
1839    return tex_multiply_and_add(n, a, tex_xn_over_d(a, f, unity), max_dimension);
1840}
1841
1842inline static void tex_aux_calculate_glue(scaled m, scaled *f, scaled *n)
1843{
1844    /*tex fraction part of |m| */
1845    *f = 0;
1846    /*tex integer part of |m| */
1847    *n = tex_x_over_n_r(m, unity, f);
1848    /*tex the new glue specification */
1849    if (*f < 0) {
1850        --n;
1851        f += unity;
1852    }
1853}
1854
1855static halfword tex_aux_math_muglue(halfword g, quarterword subtype, scaled m, halfword detail, int style)
1856{
1857    scaled f, n;
1858    halfword glue = tex_new_node(glue_node, subtype);
1859    tex_aux_calculate_glue(m, &f, &n);
1860    /* convert |mu| to |pt| */
1861    glue_amount(glue) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_amount(g), style), n, f);
1862    if (math_glue_stretch_enabled) {
1863        scaled stretch = tex_aux_math_x_scaled(glue_stretch(g), style);
1864        glue_stretch_order(glue) = glue_stretch_order(g);
1865        glue_stretch(glue) = (glue_stretch_order(glue) == normal_glue_order) ? tex_aux_mu_mult(stretch, n, f) : stretch;
1866    }
1867    if (math_glue_shrink_enabled) {
1868        scaled shrink = tex_aux_math_x_scaled(glue_shrink(g), style);
1869        glue_shrink_order(glue) = glue_shrink_order(g);
1870        glue_shrink(glue) = (glue_shrink_order(glue) == normal_glue_order) ? tex_aux_mu_mult(shrink, n, f) : shrink;
1871    }
1872    if (math_glue_limit_enabled) {
1873       tex_add_glue_option(glue, glue_option_limit);
1874    }
1875    glue_font(glue) = detail;
1876    tex_add_glue_option(glue, glue_option_no_auto_break);
1877    return glue;
1878}
1879
1880static halfword tex_aux_math_glue(halfword g, quarterword subtype, halfword detail)
1881{
1882    halfword glue = tex_new_glue_node(g, subtype);
1883    if (! math_glue_stretch_enabled) {
1884        glue_stretch_order(glue) = normal_glue_order;
1885        glue_stretch(glue) = 0;
1886    }
1887    if (! math_glue_shrink_enabled) {
1888        glue_shrink_order(glue) = normal_glue_order;
1889        glue_shrink(glue) = 0;
1890    }
1891    if (math_glue_limit_enabled) {
1892       tex_add_glue_option(glue, glue_option_limit);
1893    }
1894    glue_font(glue) = detail;
1895    tex_add_glue_option(glue, glue_option_no_auto_break);
1896    return glue;
1897}
1898
1899static halfword tex_aux_math_dimension(halfword g, quarterword subtype, halfword detail)
1900{
1901    halfword glue = tex_new_glue_node(null, subtype);
1902    glue_amount(glue) = g;
1903    glue_font(glue) = detail;
1904    tex_add_glue_option(glue, glue_option_no_auto_break);
1905    if (math_glue_limit_enabled) {
1906       tex_add_glue_option(glue, glue_option_limit);
1907    }
1908    return glue;
1909}
1910
1911static void tex_aux_math_glue_to_glue(halfword p, scaled m, int style)
1912{
1913    scaled f, n;
1914    tex_aux_calculate_glue(m, &f, &n);
1915    /*tex convert |mu| to |pt| */
1916    glue_amount(p) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_amount(p), style), n, f);
1917    if (! math_glue_stretch_enabled) {
1918        glue_stretch_order(p) = normal_glue_order;
1919        glue_stretch(p) = 0;
1920    } else if (glue_stretch_order(p) == normal_glue_order) {
1921        glue_stretch(p) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_stretch(p), style), n, f);
1922    }
1923    if (! math_glue_shrink_enabled) {
1924        glue_shrink_order(p) = normal_glue_order;
1925        glue_shrink(p) = 0;
1926    } else if (glue_shrink_order(p) == normal_glue_order) {
1927        glue_shrink(p) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_shrink(p), style), n, f);
1928    }
1929    if (math_glue_limit_enabled) {
1930       tex_add_glue_option(p, glue_option_limit);
1931    }
1932    /*tex Okay, we could have had a special subtype but we're stuck with this now. */
1933    node_subtype(p) = inter_math_skip_glue;
1934    tex_add_glue_option(p, glue_option_no_auto_break);
1935}
1936
1937/*tex
1938
1939    The |math_kern| subroutine removes |mu_glue| from a kern node, given the value of the math
1940    unit.
1941
1942*/
1943
1944static void tex_aux_make_kern(halfword current, scaled mu, int style)
1945{
1946    if (node_subtype(current) == explicit_math_kern_subtype) {
1947        scaled f, n;
1948        tex_aux_calculate_glue(mu, &f, &n);
1949        kern_amount(current) = tex_aux_mu_mult(tex_aux_math_x_scaled(glue_amount(current), style), n, f);
1950        node_subtype(current) = explicit_kern_subtype;
1951    }
1952}
1953
1954/*tex
1955
1956    Conditional math glue (|\nonscript|) results in a |glue_node| pointing to |zero_glue|, with
1957    |subtype(q)=cond_math_glue|; in such a case the node following will be eliminated if it is a
1958    glue or kern node and if the current size is different from |text_size|.
1959
1960    Unconditional math glue (|\muskip|) is converted to normal glue by multiplying the dimensions
1961    by |current_mu|.
1962
1963*/
1964
1965static void tex_aux_make_glue(halfword current, scaled mu, int style)
1966{
1967    switch (node_subtype(current)) {
1968        case mu_glue:
1969            tex_aux_math_glue_to_glue(current, mu, style);
1970            break;
1971        case conditional_math_glue:
1972            if (lmt_math_state.size != text_size) {
1973                halfword p = node_next(current);
1974                if (p) {
1975                    switch (node_type(p)) {
1976                        case glue_node:
1977                        case kern_node:
1978                            if (node_next(p)) {
1979                                tex_couple_nodes(current, node_next(p));
1980                                node_next(p) = null;
1981                            } else {
1982                                node_next(current) = null;
1983                            }
1984                            tex_flush_node_list(p);
1985                            break;
1986                    }
1987                }
1988            }
1989            break;
1990        case rulebased_math_glue:
1991            break;
1992    }
1993}
1994
1995/*tex
1996
1997    The |mlist_to_hlist| operation is actually called a lot when we have a math intense document,
1998    because it is also called nested. Here we have the main runner, called in the main loop;
1999    watch the callback.
2000
2001*/
2002
2003inline static int tex_aux_is_math_penalty(halfword n)
2004{
2005    return node_type(n) == penalty_node && (node_subtype(n) == math_pre_penalty_subtype || node_subtype(n) == math_post_penalty_subtype);
2006}
2007
2008void tex_run_mlist_to_hlist(halfword mlist, halfword penalties, halfword style, int beginclass, int endclass)
2009{
2010    if (mlist) {
2011        int saved_level = lmt_math_state.level;
2012        int callback_id = lmt_callback_defined(mlist_to_hlist_callback);
2013        lmt_math_state.level = 0;
2014        if (! valid_math_class_code(beginclass)) {
2015            beginclass = unset_noad_class;
2016        }
2017        if (! valid_math_class_code(endclass)) {
2018            endclass = unset_noad_class;
2019        }
2020        math_begin_class_par = unset_noad_class;
2021        math_end_class_par = unset_noad_class;
2022        /* not on the stack ... yet */
2023        if (tracing_math_par >= 1) {
2024            tex_begin_diagnostic();
2025            switch (style) {
2026                case display_style:
2027                    tex_print_str("> \\displaymath=");
2028                    break;
2029                case text_style:
2030                    tex_print_str("> \\inlinemath=");
2031                    break;
2032                default:
2033                    tex_print_str("> \\math=");
2034                    break;
2035            }
2036            tex_show_box(mlist);
2037            tex_end_diagnostic();
2038        }
2039        tex_finalize_math_parameters();
2040        if (callback_id > 0) {
2041            lua_State *L = lmt_lua_state.lua_instance;
2042            int top = 0;
2043            if (lmt_callback_okay(L, callback_id, &top)) {
2044                int i;
2045                node_prev(mlist) = null ;
2046                lmt_node_list_to_lua(L, mlist);
2047                lmt_push_math_style_name(L, style);
2048                lua_pushboolean(L, penalties);
2049                lua_pushinteger(L, beginclass);
2050                lua_pushinteger(L, endclass);
2051                lua_pushinteger(L, lmt_math_state.level);
2052                i = lmt_callback_call(L, 6, 1, top);
2053                if (i) {
2054                    lmt_callback_error(L, top, i);
2055                    node_next(temp_head) = null;
2056                } else {
2057                    halfword a = lmt_node_list_from_lua(L, -1);
2058                    /* node_prev(node_next(a)) = null; */
2059                    node_next(temp_head) = a;
2060                    lmt_callback_wrapup(L, top);
2061                }
2062            } else {
2063                 node_next(temp_head) = null;
2064            }
2065        } else if (callback_id == 0) {
2066             node_next(temp_head) = tex_mlist_to_hlist(mlist, penalties, style, beginclass, endclass, NULL);
2067        } else {
2068             node_next(temp_head) = null;
2069        }
2070        if (penalties) { // && tex_in_main_math_style(style)  
2071            /*tex This makes no sense in display math nor in script styles. */
2072            switch (style) {
2073                case text_style:        
2074                case cramped_text_style:        
2075                    if (math_forward_penalties_par) {
2076                        halfword n = tex_get_specification_count(math_forward_penalties_par);
2077                        if (n > 0) {
2078                            halfword h = node_next(temp_head);
2079                            halfword i = 1;
2080                            while (h && i <= n) {
2081                                if (tex_aux_is_math_penalty(h)) {
2082                                    penalty_amount(h) += tex_get_specification_penalty(math_forward_penalties_par, i++);
2083                                    tex_add_penalty_option(h, penalty_option_math_forward);
2084                                }
2085                                h = node_next(h);
2086                            }
2087                        }
2088                    }
2089                    if (math_backward_penalties_par) {
2090                        halfword n = tex_get_specification_count(math_backward_penalties_par);
2091                        if (n > 0) {
2092                            halfword t = tex_tail_of_node_list(node_next(temp_head));
2093                            halfword i = 1;
2094                            while (t && i <= n) {
2095                                if (tex_aux_is_math_penalty(t)) {
2096                                    penalty_amount(t) += tex_get_specification_penalty(math_backward_penalties_par, i++);
2097                                    tex_add_penalty_option(t, penalty_option_math_backward);
2098                                }
2099                                t = node_prev(t);
2100                            }
2101                        }
2102                    }
2103                    break;
2104            }
2105            if (node_next(temp_head) && ! tex_glue_is_zero(math_threshold_par)) {
2106                scaledwhd siz = tex_natural_hsizes(node_next(temp_head), null, 0.0, 0, 0);
2107                if  (siz.wd < glue_amount(math_threshold_par)) {
2108                    halfword box = tex_new_node(hlist_node, unknown_list);
2109                    tex_attach_attribute_list_copy(box, node_next(temp_head));
2110                    box_width(box) = siz.wd;
2111                    box_height(box) = siz.ht;
2112                    box_depth(box) = siz.dp;
2113                    box_list(box) = node_next(temp_head);
2114                    node_next(temp_head) = box;
2115                    if (glue_stretch(math_threshold_par) || glue_shrink(math_threshold_par)) {
2116                        halfword glue = tex_new_glue_node(math_threshold_par, u_leaders);
2117                        tex_add_glue_option(glue, glue_option_no_auto_break);
2118                        tex_attach_attribute_list_copy(glue, box);
2119                        glue_amount(glue) = siz.wd;
2120                        glue_leader_ptr(glue) = box;
2121                     // glue_callback(glue) = math_threshold_callback_par;
2122                        node_next(temp_head) = glue;
2123                    } else {
2124                        node_next(temp_head) = box;
2125                    }
2126                    if (tracing_math_par >= 2) {
2127                        tex_begin_diagnostic();
2128                        tex_print_format("[math: boxing inline, threshold %p, width %p, height %p, depth %p]",
2129                            glue_amount(math_threshold_par), // todo: stretch and shrink
2130                            siz.wd, siz.ht, siz.dp
2131                        );
2132                        tex_end_diagnostic();
2133                    }
2134                }
2135            }
2136            /* 
2137                At the outer level we check for discretionaries. Maybe only when we are in text or display? 
2138            */
2139            {
2140                halfword current = temp_head;
2141                while (current) { 
2142                    /*tex Maybe |math_discretionary_code| but I need to check the impact on \CONTEXT\ first. */
2143                    if (node_type(current) == glyph_node && tex_has_glyph_option(current, glyph_option_math_discretionary)) {
2144                        if (tracing_math_par >= 2) {
2145                            tex_begin_diagnostic();
2146                            tex_print_format("[math: promoting glyph with character %U to discretionary]", glyph_character(current));
2147                            tex_end_diagnostic();
2148                        }
2149                        current = tex_glyph_to_discretionary(current, mathematics_discretionary_code, tex_has_glyph_option(current, glyph_option_math_italics_too));
2150                    }
2151                    current = node_next(current);
2152                }
2153            }
2154        }
2155        lmt_math_state.level = saved_level;
2156    } else {
2157        node_next(temp_head) = null;
2158    }
2159}
2160
2161/*tex
2162
2163    The recursion in |mlist_to_hlist| is due primarily to a subroutine called |clean_box| that puts
2164    a given noad field into a box using a given math style; |mlist_to_hlist| can call |clean_box|,
2165    which can call |mlist_to_hlist|.
2166
2167    The box returned by |clean_box| is clean in the sense that its |shift_amount| is zero.
2168
2169*/
2170
2171inline static void tex_aux_remove_italic_after_first_glyph(halfword box)
2172{
2173    halfword list = box_list(box);
2174    if (list && node_type(list) == glyph_node) {
2175        halfword next = node_next(list);
2176        /*todo:  check for italic property */
2177        if (next && ! node_next(next) && node_type(next) == kern_node && node_subtype(next) == italic_kern_subtype) {
2178            /*tex Unneeded italic correction. */
2179            box_width(box) -= kern_amount(next);
2180            tex_flush_node(next);
2181            node_next(list) = null;
2182        }
2183    }
2184}
2185
2186static halfword tex_aux_clean_box(halfword n, int main_style, int style, quarterword subtype, int keepitalic, kernset *kerns)
2187{
2188    /*tex beginning of a list to be boxed */
2189    halfword list;
2190    /*tex box to be returned */
2191    halfword result;
2192    /*tex beginning of mlist to be translated */
2193    halfword mlist = null;
2194    switch (node_type(n)) {
2195        case math_char_node:
2196            mlist = tex_new_node(simple_noad, ordinary_noad_subtype);
2197            noad_nucleus(mlist) = tex_aux_math_clone(n);
2198            tex_attach_attribute_list_copy(mlist, n);
2199             break;
2200        case sub_box_node:
2201            list = kernel_math_list(n);
2202            goto FOUND;
2203        case sub_mlist_node:
2204            mlist = kernel_math_list(n);
2205            break;
2206        default:
2207            list = tex_new_null_box_node(hlist_node, math_list_list);
2208            tex_attach_attribute_list_copy(list, n);
2209            goto FOUND;
2210    }
2211    /*tex This might add some italic correction. */
2212    list = tex_mlist_to_hlist(mlist, 0, main_style, unset_noad_class, unset_noad_class, kerns);
2213    /*tex recursive call */
2214    tex_aux_set_current_math_size(style); /* persists after call */
2215  FOUND:
2216    if (kerns && list) {
2217        halfword tail = tex_tail_of_node_list(list);
2218        if (node_type(list) == glyph_node) {
2219            halfword fnt = glyph_font(list);
2220            halfword chr = glyph_character(list);
2221            kerns->topleft = tex_aux_math_x_size_scaled(fnt, tex_char_top_left_kern_from_font(fnt, chr), main_style);
2222            kerns->bottomleft = tex_aux_math_x_size_scaled(fnt, tex_char_bottom_left_kern_from_font(fnt, chr), main_style);
2223        }
2224        if (node_type(tail) == glyph_node) {
2225            halfword fnt = glyph_font(tail);
2226            halfword chr = glyph_character(tail);
2227            kerns->topright = tex_aux_math_x_size_scaled(fnt, tex_char_top_right_kern_from_font(fnt, chr), main_style);
2228            kerns->bottomright = tex_aux_math_x_size_scaled(fnt, tex_char_bottom_right_kern_from_font(fnt, chr), main_style);
2229        }
2230    }
2231    if (! list || node_type(list) == glyph_node) {
2232        result = tex_hpack(list, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
2233        tex_attach_attribute_list_copy(result, list);
2234    } else if (! node_next(list) && (node_type(list) == hlist_node || node_type(list) == vlist_node) && (box_shift_amount(list) == 0)) {
2235        /*tex It's already clean. */
2236        result = list;
2237    } else {
2238        result = tex_hpack(list, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
2239        tex_attach_attribute_list_copy(result, list);
2240    }
2241    node_subtype(result) = subtype;
2242    if (! keepitalic) {
2243        tex_aux_remove_italic_after_first_glyph(result);
2244    }
2245    return result;
2246}
2247
2248/*tex
2249
2250    It is convenient to have a procedure that converts a |math_char| field to an unpacked form. The
2251    |fetch| routine sets |cur_f| and |cur_c| to the font code and character code of a given noad
2252    field. It also takes care of issuing error messages for nonexistent characters; in such cases,
2253    |char_exists (cur_f, cur_c)| will be |false| after |fetch| has acted, and the field will also
2254    have been reset to |null|. The outputs of |fetch| are placed in global variables so that we can
2255    access them any time we want. We add a bit more detail about the location of the issue than
2256    standard \TEX\ does.
2257
2258    The |cur_f| and |cur_c| variables are now locals and we keep the (opentype) state otherwise.
2259
2260*/
2261
2262static int tex_aux_fetch(halfword n, const char *where, halfword *f, halfword *c) /* todo: also pass size */
2263{
2264    if (node_type(n) == glyph_node) {
2265        *f = glyph_font(n);
2266        *c = glyph_character(n);
2267        if (tex_char_exists(*f, *c)) {
2268            return 1;
2269        } else {
2270            tex_char_warning(*f, *c);
2271            return 0;
2272        }
2273    } else {
2274        *f = tex_fam_fnt(kernel_math_family(n), lmt_math_state.size);
2275        *c = kernel_math_character(n);
2276        if (math_kernel_node_has_option(n, math_kernel_ignored_character)) {
2277            return 1;
2278        } else if (*f == null_font) {
2279            tex_handle_error(
2280                normal_error_type,
2281                "\\%s%i is undefined in %s, font id %i, character %i)",
2282                tex_aux_math_size_string(lmt_math_state.size), kernel_math_family(n), where, *f, *c,
2283                "Somewhere in the math formula just ended, you used the stated character from an\n"
2284                "undefined font family. For example, plain TeX doesn't allow \\it or \\sl in\n"
2285                "subscripts. Proceed, and I'll try to forget that I needed that character."
2286            );
2287            return 0;
2288        } else if (tex_math_char_exists(*f, *c, lmt_math_state.size)) {
2289            return 1;
2290        } else {
2291            tex_char_warning(*f, *c);
2292            return 0;
2293        }
2294    }
2295}
2296
2297/*tex
2298
2299    We need to do a lot of different things, so |mlist_to_hlist| makes two passes over the given
2300    mlist.
2301
2302    The first pass does most of the processing: It removes |mu| spacing from glue, it recursively
2303    evaluates all subsidiary mlists so that only the top-level mlist remains to be handled, it puts
2304    fractions and square roots and such things into boxes, it attaches subscripts and superscripts,
2305    and it computes the overall height and depth of the top-level mlist so that the size of
2306    delimiters for a |fence_noad| will be known. The hlist resulting from each noad is recorded in
2307    that noad's |new_hlist| field, an integer field that replaces the |nucleus| or |thickness|.
2308
2309    The second pass eliminates all noads and inserts the correct glue and penalties between nodes.
2310
2311*/
2312
2313static void tex_aux_assign_new_hlist(halfword target, halfword hlist)
2314{
2315    switch (node_type(target)) {
2316        case fraction_noad:
2317            kernel_math_list(fraction_numerator(target)) = null;
2318            kernel_math_list(fraction_denominator(target)) = null;
2319            tex_flush_node(fraction_numerator(target));
2320            tex_flush_node(fraction_denominator(target));
2321            fraction_numerator(target) = null;
2322            fraction_denominator(target) = null;
2323            break;
2324        case radical_noad:
2325        case simple_noad:
2326        case accent_noad:
2327            if (noad_nucleus(target)) {
2328                kernel_math_list(noad_nucleus(target)) = null;
2329                tex_flush_node(noad_nucleus(target));
2330                noad_nucleus(target) = null;
2331            }
2332            break;
2333    }
2334    noad_new_hlist(target) = hlist;
2335}
2336
2337/*tex
2338
2339    Most of the actual construction work of |mlist_to_hlist| is done by procedures with names like
2340    |make_fraction|, |make_radical|, etc. To illustrate the general setup of such procedures, let's
2341    begin with a couple of simple ones.
2342
2343*/
2344
2345static void tex_aux_make_over(halfword target, halfword style, halfword size, halfword fam)
2346{
2347    /*tex
2348
2349        No rule adaption yet, maybe it will never be implemented because overbars should be proper
2350        extensibles. The order is: kern, rule, gap, content.
2351
2352    */
2353    scaled thickness = tex_get_math_y_parameter_checked(style, math_parameter_overbar_rule);
2354    scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_overbar_vgap);
2355    scaled kern = tex_get_math_y_parameter_checked(style, math_parameter_overbar_kern);
2356    {
2357        halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_over_rule, OverbarRuleThickness);
2358        if (t != undefined_math_parameter) {
2359            thickness = t;
2360        }
2361    }
2362    {
2363        halfword result = tex_aux_overbar(
2364            tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_over_line_variant), style, math_nucleus_list, 0, NULL),
2365            vgap, thickness, kern,
2366            get_attribute_list(noad_nucleus(target)), math_over_rule_subtype, size, fam,
2367            null, style
2368        );
2369        node_subtype(result) = math_over_list;
2370        kernel_math_list(noad_nucleus(target)) = result;
2371        node_type(noad_nucleus(target)) = sub_box_node;
2372    }
2373}
2374
2375static void tex_aux_make_under(halfword target, halfword style, halfword size, halfword fam)
2376{
2377    /*tex
2378
2379        No rule adaption yet, maybe never as underbars should be proper extensibles. Here |x| is
2380        the head, and |p| the tail but we keep the original names. The order is: content, gap,
2381        rule, kern.
2382
2383    */
2384    scaled thickness = tex_get_math_y_parameter_checked(style, math_parameter_underbar_rule);
2385    scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_underbar_vgap);
2386    scaled kern = tex_get_math_y_parameter_checked(style, math_parameter_underbar_kern);
2387    {
2388        halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_under_rule, UnderbarRuleThickness);
2389        if (t != undefined_math_parameter) {
2390            thickness = t;
2391        }
2392    }
2393    { 
2394        halfword result = tex_aux_underbar(
2395            tex_aux_clean_box(noad_nucleus(target), tex_math_style_variant(style, math_parameter_under_line_variant), style, math_nucleus_list, 0, NULL),
2396            vgap, thickness, kern,
2397            get_attribute_list(noad_nucleus(target)), math_under_rule_subtype, size, fam,
2398            null, style
2399        );
2400        node_subtype(result) = math_over_list;
2401        kernel_math_list(noad_nucleus(target)) = result;
2402        node_type(noad_nucleus(target)) = sub_box_node;
2403    }
2404}
2405
2406/*tex
2407
2408    In \LUAMETATEX\ we also permit |\vcenter| in text mode but there we use another function than
2409    the one below.
2410
2411 */
2412
2413static void tex_aux_make_vcenter(halfword target, halfword style, halfword size)
2414{
2415    halfword box = kernel_math_list(noad_nucleus(target));
2416    if (node_type(box) != vlist_node) {
2417        box = tex_aux_clean_box(noad_nucleus(target), style, style, math_list_list, 0, NULL); // todo: math_vcenter_list
2418        kernel_math_list(noad_nucleus(target)) = box;
2419        node_type(noad_nucleus(target)) = sub_box_node;
2420    }
2421    {
2422        scaled total = box_total(box);
2423        scaled axis = has_box_axis(box, no_math_axis) ? 0 : tex_aux_math_axis(size);
2424        box_height(box) = axis + tex_half_scaled(total);
2425        box_depth(box) = total - box_height(box);
2426    }
2427}
2428
2429/*tex
2430
2431    According to the rules in the |DVI| file specifications, we ensure alignment between a square
2432    root sign and the rule above its nucleus by assuming that the baseline of the square-root
2433    symbol is the same as the bottom of the rule. The height of the square-root symbol will be the
2434    thickness of the rule, and the depth of the square-root symbol should exceed or equal the
2435    height-plus-depth of the nucleus plus a certain minimum clearance~|psi|. The symbol will be
2436    placed so that the actual clearance is |psi| plus half the excess.
2437
2438*/
2439
2440static void tex_aux_make_hextension(halfword target, int style, int size)
2441{
2442    int stack = 0;
2443    scaled radicalwidth = tex_aux_math_given_x_scaled(noad_width(target));
2444    halfword extensible = radical_left_delimiter(target);
2445    halfword delimiter = tex_aux_make_delimiter(target, extensible, size, radicalwidth, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(target), NULL, 0, null);
2446    halfword delimiterwidth = box_width(delimiter);
2447    if (! stack && radicalwidth && (radicalwidth != delimiterwidth)) {
2448        if (has_noad_option_middle(target)) {
2449            scaled delta = tex_half_scaled(radicalwidth - delimiterwidth);
2450            if (delta) {
2451                halfword kern = tex_new_kern_node(delta, horizontal_math_kern_subtype);
2452                tex_attach_attribute_list_copy(kern, target);
2453                tex_couple_nodes(kern, delimiter);
2454                delimiter = kern;
2455            }
2456            delimiterwidth = radicalwidth;
2457        } else if (has_noad_option_exact(target)) {
2458            delimiterwidth = radicalwidth;
2459        }
2460    }
2461    delimiter = tex_hpack(delimiter, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
2462    box_width(delimiter) = delimiterwidth;
2463    tex_attach_attribute_list_copy(delimiter, target); /* yes or no */
2464    kernel_math_list(noad_nucleus(target)) = delimiter;
2465    radical_left_delimiter(target) = null;
2466    radical_right_delimiter(target) = null;
2467}
2468
2469static void tex_aux_preroll_root_radical(halfword target, int style, int size)
2470{
2471    (void) size;
2472    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);
2473}
2474
2475static halfword tex_aux_link_radical(halfword nucleus, halfword delimiter, halfword companion, halfword rightdelimiter)
2476{
2477    if (companion) {
2478        tex_couple_nodes(delimiter, nucleus);
2479        tex_couple_nodes(nucleus, companion);
2480        return delimiter;
2481    } else if (rightdelimiter) {  
2482        tex_couple_nodes(nucleus, delimiter);
2483        return nucleus; 
2484    } else {
2485        tex_couple_nodes(delimiter, nucleus);
2486        return delimiter;
2487    }
2488}
2489
2490static void tex_aux_assign_radical(halfword target, halfword radical)
2491{
2492    halfword result = tex_hpack(radical, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
2493    node_subtype(result) = math_radical_list;
2494    tex_attach_attribute_list_copy(result, target);
2495    kernel_math_list(noad_nucleus(target)) = result;
2496    node_type(noad_nucleus(target)) = sub_box_node;
2497    radical_left_delimiter(target) = null;
2498    radical_right_delimiter(target) = null;
2499}
2500
2501static void tex_aux_set_radical_kerns(delimiterextremes *extremes, kernset *kerns, halfword size)
2502{
2503    if (kerns && extremes->tfont) { 
2504        if (tex_math_has_class_option(radical_noad_subtype, carry_over_left_top_kern_class_option)) {  
2505            kerns->topleft = tex_aux_math_x_size_scaled(extremes->tfont, tex_char_top_left_kern_from_font(extremes->tfont, extremes->tchar), size);
2506        }
2507        if (tex_math_has_class_option(radical_noad_subtype, carry_over_left_bottom_kern_class_option)) {  
2508            kerns->bottomleft = tex_aux_math_x_size_scaled(extremes->bfont, tex_char_bottom_left_kern_from_font(extremes->bfont, extremes->bchar), size);
2509        }
2510        if (tex_math_has_class_option(radical_noad_subtype, carry_over_right_top_kern_class_option)) {  
2511            kerns->topright = tex_aux_math_x_size_scaled(extremes->tfont, tex_char_top_right_kern_from_font(extremes->tfont, extremes->tchar), size);
2512        }
2513        if (tex_math_has_class_option(radical_noad_subtype, carry_over_right_bottom_kern_class_option)) {  
2514            kerns->bottomright = tex_aux_math_x_size_scaled(extremes->bfont, tex_char_bottom_right_kern_from_font(extremes->bfont, extremes->bchar), size);
2515        }
2516        if (tex_math_has_class_option(radical_noad_subtype, prefer_delimiter_dimensions_class_option)) {  
2517            kerns->height = extremes->height;
2518            kerns->depth = extremes->depth;
2519            kerns->dimensions = 1;
2520            kerns->font = extremes->tfont;
2521        }
2522    }
2523}
2524
2525static void tex_aux_make_root_radical(halfword target, int style, int size, kernset *kerns)
2526{
2527    halfword nucleus = noad_new_hlist(target);
2528    scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_radical_vgap);
2529    scaled theta = tex_get_math_y_parameter(style, math_parameter_radical_rule);
2530    scaled kern = tex_get_math_y_parameter_checked(style, math_parameter_radical_kern);
2531    scaled fam = delimiter_small_family(radical_left_delimiter(target));
2532    halfword leftdelimiter = radical_left_delimiter(target);
2533    halfword rightdelimiter = radical_right_delimiter(target);
2534    halfword topdelimiter = radical_top_delimiter(target);
2535    halfword delimiter = leftdelimiter ? leftdelimiter : rightdelimiter;
2536    halfword companion = leftdelimiter ? rightdelimiter : null;
2537    halfword radical = null;
2538    delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
2539    scaled innerx = INT_MIN;
2540    scaled innery = INT_MIN;
2541    int norule = has_noad_option_norule(target);
2542    noad_new_hlist(target) = null;
2543    /*tex
2544        We can take the rule width from the fam/style of the delimiter or use the most recent math
2545        parameters value.
2546    */
2547    {
2548        halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_radical_rule, RadicalRuleThickness);
2549        if (t != undefined_math_parameter) {
2550            theta = t;
2551        }
2552    }
2553    { 
2554        halfword weird = theta == undefined_math_parameter;
2555        if (weird) { 
2556            /*tex What do we have here. Why not issue an error */
2557            theta = tex_get_math_y_parameter_checked(style, math_parameter_fraction_rule); /* a bit weird this one */
2558        }
2559        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, null);
2560        if (extremes.bfont) { 
2561            scaled margin = tex_char_left_margin_from_font(extremes.bfont, extremes.bchar);
2562            if (margin && margin != INT_MIN) {
2563                tex_aux_prepend_hkern_to_box_list(nucleus, tex_aux_math_y_size_scaled(extremes.bfont, margin, size), horizontal_math_kern_subtype, "bad radical body");
2564            }
2565        }
2566        if (radical_degree(target)) { 
2567            halfword innerf = 0;
2568            halfword innerc = 0;
2569            if (tex_char_has_tag_from_font(extremes.bfont, extremes.bchar, inner_left_tag)) { 
2570                innerf = extremes.bfont;
2571                innerc = extremes.bchar;
2572            } else if (tex_char_has_tag_from_font(extremes.tfont, extremes.tchar, inner_left_tag)) { 
2573                innerf = extremes.tfont;
2574                innerc = extremes.tchar;
2575            }
2576            if (innerc) {
2577                innerx = tex_char_inner_x_offset_from_font(innerf, innerc);
2578                innery = tex_char_inner_y_offset_from_font(innerf, innerc);
2579                innerx = innerx == INT_MIN ? 0 : tex_aux_math_y_size_scaled(innerf, innerx, size);
2580                innery = innery == INT_MIN ? 0 : tex_aux_math_y_size_scaled(innerf, innery, size);
2581            }
2582        }
2583        if (companion) {
2584            /*tex 
2585                For now we assume symmetry and same height and depth! Should we have a dedicated 
2586                |extremes| here? 
2587            */
2588            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, null);
2589            if (extremes.bfont) { 
2590                scaled margin = tex_char_right_margin_from_font(extremes.bfont, extremes.bchar);
2591                if (margin && margin != INT_MIN) {
2592                    tex_aux_append_hkern_to_box_list(nucleus, tex_aux_math_y_size_scaled(extremes.bfont, margin, size), horizontal_math_kern_subtype, "bad radical body");
2593                }
2594            }
2595        }
2596        if (weird) {
2597            /*tex
2598                If |y| is a composite then set |theta| to the height of its top character, else set it
2599                to the height of |y|. Really? 
2600            */
2601            halfword list = box_list(delimiter);
2602            if (list && (node_type(list) == hlist_node)) {
2603                /*tex possible composite */
2604                halfword glyph = box_list(list);
2605                if (glyph && node_type(glyph) == glyph_node) {
2606                    /*tex top character */
2607                    theta = tex_char_height_from_glyph(glyph);
2608                } else {
2609                    theta = box_height(delimiter);
2610                }
2611            } else {
2612                theta = box_height(delimiter);
2613            }
2614        }
2615    }
2616    /*tex 
2617        This is tricky: we might need to handle two different extremes here but I first need an 
2618        example. 
2619    */
2620    tex_aux_set_radical_kerns(&extremes, kerns, size);
2621    /*tex
2622        Radicals in traditional fonts have their shape below the baseline which makes them unuseable
2623        as stand alone characters but here we compensate for that fact. Opentype fonts derived from
2624        traditional \TEX\ fonts can also be like that and it goed unnoticed until one accesses the
2625        shape as character directly. Normally that gets corrected in the font when this has become
2626        clear.
2627    */
2628    {
2629        halfword delta = (box_total(delimiter) - theta) - (box_total(nucleus) + clearance);
2630        if (delta > 0) {
2631            /*tex increase the actual clearance */
2632            clearance += tex_half_scaled(delta);
2633        }
2634        if (has_noad_option_reflected(target)) {
2635            box_shift_amount(delimiter) = - ((box_depth(delimiter) - theta) - (box_depth(nucleus) + clearance));
2636            if (companion) { 
2637                box_shift_amount(companion) = - ((box_depth(companion) - theta) - (box_depth(nucleus) + clearance));
2638            }
2639        } else {
2640            box_shift_amount(delimiter) = (box_height(delimiter) - theta) - (box_height(nucleus) + clearance);
2641            if (companion) { 
2642                box_shift_amount(companion) = (box_height(companion) - theta) - (box_height(nucleus) + clearance);
2643            }
2644        }
2645    }
2646    if (node_type(delimiter) == vlist_node && node_subtype(delimiter) == math_v_delimiter_list) {
2647        halfword before = tex_get_math_x_parameter_default(style, math_parameter_radical_extensible_before, 0); 
2648        tex_aux_prepend_hkern_to_box_list(nucleus, before, horizontal_math_kern_subtype, "bad delimiter");
2649    }
2650    if (node_type(companion) == vlist_node && node_subtype(companion) == math_v_delimiter_list) {
2651        halfword after = tex_get_math_x_parameter_default(style, math_parameter_radical_extensible_after, 0); 
2652        tex_aux_append_hkern_to_box_list(nucleus, after, horizontal_math_kern_subtype, "bad delimiter");
2653    }
2654    {
2655        /* todo bottomdelimiter */
2656        halfword total = box_total(delimiter);
2657        halfword list = (has_noad_option_reflected(target) ? tex_aux_underbar : tex_aux_overbar)
2658            (nucleus,clearance,theta,kern,get_attribute_list(delimiter),math_radical_rule_subtype,size,fam,norule ? -1 : topdelimiter,style);
2659        radical = tex_aux_link_radical(list, delimiter, companion, rightdelimiter);
2660        if (radical_degree(target)) {
2661            /*tex
2662                This is a bit messy because we handle style at the texmath end already. Anyway, a
2663                a degree is always scriptstyle. 
2664            */
2665            halfword degreestyle = tex_math_style_variant(noad_style(target) == yet_unset_math_style ? noad_style(target) : style, math_parameter_degree_variant);
2666         // halfword degreestyle = script_script_style;
2667            halfword degree = tex_aux_clean_box(radical_degree(target), degreestyle, style, math_degree_list, 0, NULL);
2668            scaled width = box_width(degree);
2669            tex_attach_attribute_list_copy(degree, radical_degree(target));
2670            if (width) {
2671                scaled before = tex_get_math_x_parameter_checked(style, math_parameter_radical_degree_before);
2672                scaled after = tex_get_math_x_parameter_checked(style, math_parameter_radical_degree_after);
2673                scaled raise = tex_get_math_parameter_checked(style, math_parameter_radical_degree_raise);
2674                if (innerx != INT_MIN) { 
2675                    tex_aux_append_hkern_to_box_list(degree, innerx, horizontal_math_kern_subtype, "bad degree");
2676                    width += innerx;
2677                }
2678                if (-after > width) {
2679                    before += -after - width;
2680                }
2681                if (after) {
2682                    halfword kern = tex_new_kern_node(after, horizontal_math_kern_subtype);
2683                    tex_attach_attribute_list_copy(kern, radical_degree(target));
2684                    tex_couple_nodes(kern, radical);
2685                    nucleus = kern;
2686                } else {
2687                    nucleus = radical;
2688                }
2689                if (innery != INT_MIN) { 
2690                    box_shift_amount(degree) = - innery + box_depth(radical) + box_shift_amount(radical);
2691                } else {
2692                    box_shift_amount(degree) = - (tex_xn_over_d(total, raise, 100) - box_depth(radical) - box_shift_amount(radical));
2693                }
2694                tex_couple_nodes(degree, nucleus);
2695                if (before) {
2696                    halfword kern = tex_new_kern_node(before, horizontal_math_kern_subtype);
2697                    tex_attach_attribute_list_copy(kern, radical_degree(target));
2698                    tex_couple_nodes(kern, degree);
2699                    radical = kern;
2700                } else {
2701                    radical = degree;
2702                }
2703            } else {
2704                tex_flush_node(degree);
2705            }
2706            /*tex for |\Uroot.. {<list>} {}|: */
2707            kernel_math_list(radical_degree(target)) = null;
2708            tex_flush_node(radical_degree(target));
2709            radical_degree(target) = null;
2710        }
2711    }
2712    tex_aux_assign_radical(target, radical);
2713}
2714
2715/*tex 
2716    This is pretty much the same as the above when the |norule| option is set. But by splitting this 
2717    variant off we can enhance it more cleanly. 
2718*/
2719
2720static void tex_aux_make_delimited_radical(halfword target, int style, int size, kernset *kerns)
2721{
2722    halfword nucleus = noad_new_hlist(target);
2723 /* scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_radical_vgap); */
2724    halfword leftdelimiter = radical_left_delimiter(target);
2725    halfword rightdelimiter = radical_right_delimiter(target);
2726    halfword delimiter = leftdelimiter ? leftdelimiter : rightdelimiter;
2727    halfword companion = leftdelimiter ? rightdelimiter : null;
2728    halfword radical = null;
2729    halfword depth = has_noad_option_exact(target) ? radical_depth(target) : (box_depth(nucleus) + radical_depth(target));
2730    halfword height = has_noad_option_exact(target) ? radical_height(target) : (box_height(nucleus) + radical_height(target));
2731    halfword total = height + depth;
2732    delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
2733    noad_new_hlist(target) = null;
2734    size += radical_size(target);
2735    if (size < text_size) { 
2736        size = text_size;
2737    } else if (size > script_script_size) {
2738        size = script_script_size;
2739    }
2740    delimiter = tex_aux_make_delimiter(target, delimiter, size, total, 0, style, 2, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, depth, null);
2741    if (companion) {
2742        /*tex For now we assume symmetry and same height and depth! */
2743        companion = tex_aux_make_delimiter(target, companion, size, total, 0, style, 2, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, depth, null);
2744    }
2745    tex_aux_set_radical_kerns(&extremes, kerns, size);
2746    radical = tex_aux_link_radical(nucleus, delimiter, companion, rightdelimiter);
2747    tex_aux_assign_radical(target, radical);
2748}
2749
2750/*tex Construct a vlist box: */
2751
2752static halfword tex_aux_wrapup_over_under_delimiter(halfword target, halfword x, halfword y, scaled shift_up, scaled shift_down, quarterword st)
2753{
2754    halfword box = tex_new_null_box_node(vlist_node, st);
2755    scaled delta = (shift_up - box_depth(x)) - (box_height(y) - shift_down);
2756    box_height(box) = shift_up + box_height(x);
2757    box_depth(box) = box_depth(y) + shift_down;
2758    tex_attach_attribute_list_copy(box, target);
2759    if (delta) {
2760        halfword kern = tex_new_kern_node(delta, vertical_math_kern_subtype);
2761        tex_attach_attribute_list_copy(kern, target);
2762        tex_couple_nodes(x, kern);
2763        tex_couple_nodes(kern, y);
2764    } else {
2765        tex_couple_nodes(x, y);
2766    }
2767    box_list(box) = x;
2768    return box;
2769}
2770
2771/*tex When |exact| use radicalwidth (|y| is delimiter). */
2772
2773inline static halfword tex_aux_check_radical(halfword target, int stack, halfword r, halfword t)
2774{
2775    if (! stack && (box_width(r) >= box_width(t))) {
2776        scaled width = tex_aux_math_given_x_scaled(noad_width(target));
2777        if (width) {
2778            scaled delta = width - box_width(r);
2779            if (delta) {
2780                if (has_noad_option_left(target)) {
2781                    halfword kern = tex_new_kern_node(delta, horizontal_math_kern_subtype);
2782                    tex_attach_attribute_list_copy(kern, target);
2783                    tex_couple_nodes(kern, r);
2784                } else if (has_noad_option_middle(target)) {
2785                    halfword kern = tex_new_kern_node(tex_half_scaled(delta), horizontal_math_kern_subtype);
2786                    tex_attach_attribute_list_copy(kern, target);
2787                    tex_couple_nodes(kern, r);
2788                } else if (has_noad_option_right(target)) {
2789                    /*tex also kind of exact compared to vertical */
2790                } else {
2791                    return r;
2792                }
2793                r = tex_hpack(r, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
2794                box_width(r) = noad_width(target);
2795                tex_attach_attribute_list_copy(r, target);
2796            }
2797        }
2798    }
2799    return r;
2800}
2801
2802inline static void tex_aux_fixup_radical_width(halfword target, halfword x, halfword y)
2803{
2804    if (box_width(y) >= box_width(x)) {
2805        if (noad_width(target)) {
2806            box_shift_amount(x) += tex_half_scaled(box_width(y) - box_width(x)) ;
2807        }
2808        box_width(x) = box_width(y);
2809    } else {
2810        if (noad_width(target)) {
2811            box_shift_amount(y) += tex_half_scaled(box_width(x) - box_width(y)) ;
2812        }
2813        box_width(y) = box_width(x);
2814    }
2815}
2816
2817inline static halfword tex_aux_get_radical_width(halfword target, halfword p)
2818{
2819    return noad_width(target) ? noad_width(target) : box_width(p);
2820}
2821
2822/*tex
2823
2824    This has the |nucleus| box |x| as a limit above an extensible delimiter |y|.
2825
2826*/
2827
2828static void tex_aux_make_over_delimiter(halfword target, int style, int size)
2829{
2830    halfword result;
2831    scaled delta;
2832    int stack;
2833    scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_bgap);
2834    scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_vgap);
2835    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);
2836    scaled width = tex_aux_get_radical_width(target, content);
2837    halfword over_delimiter = fraction_left_delimiter(target);
2838    halfword delimiter = tex_aux_make_delimiter(target, over_delimiter, size, width, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(target), NULL, 0, null);
2839    fraction_left_delimiter(target) = null;
2840    delimiter = tex_aux_check_radical(target, stack, delimiter, content);
2841    tex_aux_fixup_radical_width(target, content, delimiter);
2842    delta = clearance - (shift - box_depth(content) - box_height(delimiter));
2843    if (delta > 0) {
2844        shift += delta;
2845    }
2846    result = tex_aux_wrapup_over_under_delimiter(target, content, delimiter, shift, 0, math_over_delimiter_list);
2847    box_width(result) = box_width(content);
2848    kernel_math_list(noad_nucleus(target)) = result;
2849    node_type(noad_nucleus(target)) = sub_box_node;
2850}
2851
2852/*tex
2853    This has the extensible delimiter |x| as a limit below |nucleus| box |y|.
2854*/
2855
2856static void tex_aux_make_under_delimiter(halfword target, int style, int size)
2857{
2858    halfword result;
2859    scaled delta;
2860    int stack;
2861    scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_bgap);
2862    scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_vgap);
2863    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);
2864    scaled width = tex_aux_get_radical_width(target, content);
2865    halfword under_delimiter = fraction_left_delimiter(target);
2866    halfword delimiter = tex_aux_make_delimiter(target, under_delimiter, size, width, 1, style, 1, &stack, NULL, 0, has_noad_option_nooverflow(target), NULL, 0, null);
2867    fraction_left_delimiter(target) = null;
2868    delimiter = tex_aux_check_radical(target, stack, delimiter, content);
2869    tex_aux_fixup_radical_width(target, delimiter, content);
2870    delta = clearance - (- box_depth(delimiter) - (box_height(content) - shift));
2871    if (delta > 0) {
2872        shift += delta;
2873    }
2874    result = tex_aux_wrapup_over_under_delimiter(target, delimiter, content, 0, shift, math_under_delimiter_list);
2875    box_width(result) = box_width(content);
2876    kernel_math_list(noad_nucleus(target)) = result;
2877    node_type(noad_nucleus(target)) = sub_box_node;
2878}
2879
2880/*tex
2881    This has the extensible delimiter |x| as a limit above |nucleus| box |y|.
2882*/
2883
2884static void tex_aux_make_delimiter_over(halfword target, int style, int size)
2885{
2886    halfword result;
2887    scaled actual;
2888    int stack;
2889    scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_bgap);
2890    scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_over_delimiter_vgap);
2891    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);
2892    scaled width = tex_aux_get_radical_width(target, content);
2893    halfword over_delimiter = fraction_left_delimiter(target);
2894    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, null);
2895    fraction_left_delimiter(target) = null;
2896    delimiter = tex_aux_check_radical(target, stack, delimiter, content);
2897    tex_aux_fixup_radical_width(target, delimiter, content);
2898    shift -= box_total(delimiter);
2899    actual = shift - box_height(content);
2900    if (actual < clearance) {
2901        shift += (clearance - actual);
2902    }
2903    result = tex_aux_wrapup_over_under_delimiter(target, delimiter, content, shift, 0, math_over_delimiter_list);
2904    box_width(result) = box_width(delimiter);
2905    kernel_math_list(noad_nucleus(target)) = result;
2906    node_type(noad_nucleus(target)) = sub_box_node;
2907}
2908
2909/*tex
2910    This has the extensible delimiter |y| as a limit below a |nucleus| box |x|.
2911*/
2912
2913static void tex_aux_make_delimiter_under(halfword target, int style, int size)
2914{
2915    halfword result;
2916    scaled actual;
2917    int stack;
2918    scaled shift = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_bgap);
2919    scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_under_delimiter_vgap);
2920    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);
2921    scaled width = tex_aux_get_radical_width(target, content);
2922    halfword under_delimiter = fraction_left_delimiter(target);
2923    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, null);
2924    fraction_left_delimiter(target) = null;
2925    delimiter = tex_aux_check_radical(target, stack, delimiter, content);
2926    tex_aux_fixup_radical_width(target, content, delimiter);
2927    shift -= box_total(delimiter);
2928    actual = shift - box_depth(content);
2929    if (actual < clearance) {
2930       shift += (clearance - actual);
2931    }
2932    result = tex_aux_wrapup_over_under_delimiter(target, content, delimiter, 0, shift, math_under_delimiter_list);
2933    /*tex This also equals |width(y)|: */
2934    box_width(result) = box_width(delimiter);
2935    kernel_math_list(noad_nucleus(target)) = result;
2936    node_type(noad_nucleus(target)) = sub_box_node;
2937}
2938
2939static void tex_aux_make_radical(halfword target, int style, int size, kernset *kerns)
2940{
2941    switch (node_subtype(target)) {
2942        case under_delimiter_radical_subtype:
2943            tex_aux_make_under_delimiter(target, style, size);
2944            break;
2945        case over_delimiter_radical_subtype:
2946            tex_aux_make_over_delimiter(target, style, size);
2947            break;
2948        case delimiter_under_radical_subtype:
2949            tex_aux_make_delimiter_under(target, style, size);
2950            break;
2951        case delimiter_over_radical_subtype:
2952            tex_aux_make_delimiter_over(target, style, size);
2953            break;
2954        case delimited_radical_subtype:
2955            tex_aux_make_delimited_radical(target, style, size, kerns);
2956            break;
2957        case h_extensible_radical_subtype:
2958            tex_aux_make_hextension(target, style, size);
2959            break;
2960        default:
2961            tex_aux_make_root_radical(target, style, size, kerns);
2962            break;
2963    }
2964    if (noad_source(target)) {
2965        halfword result = kernel_math_list(noad_nucleus(target));
2966        if (result) {
2967            box_source_anchor(result) = noad_source(target);
2968            tex_set_box_geometry(result, anchor_geometry);
2969        }
2970    }
2971}
2972
2973static void tex_aux_preroll_radical(halfword target, int style, int size)
2974{
2975    switch (node_subtype(target)) {
2976        case under_delimiter_radical_subtype:
2977        case over_delimiter_radical_subtype:
2978        case delimiter_under_radical_subtype:
2979        case delimiter_over_radical_subtype:
2980        case h_extensible_radical_subtype:
2981            break;
2982        default:
2983            tex_aux_preroll_root_radical(target, style, size);
2984            break;
2985    }
2986}
2987
2988/*tex
2989
2990    Slants are not considered when placing accents in math mode. The accenter is centered over the
2991    accentee, and the accent width is treated as zero with respect to the size of the final box.
2992
2993*/
2994
2995typedef enum math_accent_location_codes {
2996    top_accent_code     = 1,
2997    bot_accent_code     = 2, // todo : bottom_accent_code 
2998    overlay_accent_code = 4,
2999    stretch_accent_code = 8, /* reserved, not yet set */
3000} math_accent_location_codes;
3001
3002static int tex_aux_compute_accent_skew(halfword target, int flags, scaled *skew, halfword size)
3003{
3004    /*tex will be true if a top-accent is placed in |s| */
3005    int absolute = 0;
3006    switch (node_type(noad_nucleus(target))) {
3007        case math_char_node:
3008            {
3009                halfword chr = null;
3010                halfword fnt = null;
3011                tex_aux_fetch(noad_nucleus(target), "accent", &fnt, &chr);
3012                /* We have an unprocessed character, no glyph yet (in compact mode). */
3013                chr = tex_get_math_char(fnt, chr, size, NULL, 0);
3014                if (tex_aux_math_engine_control(fnt, math_control_accent_skew_apply)) {
3015                    /*tex
3016                        There is no bot_accent so let's assume that the shift also applies
3017                        to bottom and overlay accents.
3018                    */
3019                    if (flags & bot_accent_code) {
3020                        *skew = tex_char_unchecked_bottom_anchor_from_font(fnt, chr);
3021                    } else {
3022                        *skew = tex_char_unchecked_top_anchor_from_font(fnt, chr);
3023                    }
3024                    if (*skew != INT_MIN) {
3025                        *skew = tex_aux_math_x_size_scaled(fnt, *skew, size);
3026                        absolute = 1;
3027                    } else {
3028                        *skew = 0;
3029                    }
3030                } else if (flags & top_accent_code) {
3031                    *skew = tex_aux_math_x_size_scaled(fnt, tex_get_kern(fnt, chr, font_skew_char(fnt)), size);
3032                } else {
3033                    *skew = 0;
3034                }
3035                if (tracing_math_par >= 2) {
3036                    tex_begin_diagnostic();
3037                    tex_print_format("[math: accent skew, font %i, chr %x, skew %p, absolute %i]", fnt, chr, *skew, absolute);
3038                    tex_end_diagnostic();
3039                }
3040                break;
3041            }
3042        case sub_mlist_node:
3043            {
3044                /*tex
3045                    If |nucleus(q)| is a |sub_mlist_node| composed of an |accent_noad| we:
3046
3047                    \startitemize
3048                    \startitem
3049                        use the positioning of the nucleus of that noad, recursing until
3050                    \stopitem
3051                    \startitem
3052                        the inner most |accent_noad|. This way multiple stacked accents are
3053                    \stopitem
3054                    \startitem
3055                        aligned to the inner most one.
3056                    \stopitem
3057                    \stoptitemize
3058
3059                    The vlink test was added in version 1.06, so that we only consider a lone noad:
3060
3061                    $
3062                        \Umathaccent bottom 0 0 "023DF {   \Umathaccent fixed 0 0 "00302 { m } r } \quad
3063                        \Umathaccent bottom 0 0 "023DF { l \Umathaccent fixed 0 0 "00302 { m } r } \quad
3064                        \Umathaccent bottom 0 0 "023DF { l \Umathaccent fixed 0 0 "00302 { m }   } \quad
3065                        \Umathaccent bottom 0 0 "023DF {   \Umathaccent fixed 0 0 "00302 { m }   } \quad
3066                        \Umathaccent bottom 0 0 "023DF { l                                      r }
3067                    $
3068
3069                */
3070                halfword p = kernel_math_list(noad_nucleus(target));
3071                if (p && (! node_next(p))) { // || tex_aux_has_fake_nucleus(node_next(p))
3072                    switch (node_type(p)) {
3073                        case accent_noad:
3074                            absolute = tex_aux_compute_accent_skew(p, flags, skew, size);
3075                            break;
3076                        case simple_noad:
3077                            if (! noad_has_following_scripts(p)) {
3078                                absolute = tex_aux_compute_accent_skew(p, flags, skew, size);
3079                            }
3080                            break;
3081                    }
3082                }
3083                if (tracing_math_par >= 2) {
3084                    tex_begin_diagnostic();
3085                    tex_print_format("[math: accent skew, absolute %i]", absolute);
3086                    tex_end_diagnostic();
3087                }
3088                break;
3089            }
3090    }
3091    return absolute;
3092}
3093
3094static void tex_aux_do_make_math_accent(halfword target, halfword accentfnt, halfword accentchr, int flags, int style, int size, scaled *accenttotal, scaled *leftkern, scaled *rightkern)
3095{
3096    /*tex The width and height (without scripts) of base character: */
3097    scaled baseheight = 0;
3098 // scaled basedepth = 0;
3099    scaled basewidth = 0;
3100    scaled usedwidth = 0;
3101    /*tex The space to remove between accent and base: */
3102    scaled delta = 0;
3103    scaled overshoot = 0;
3104    extinfo *extended = NULL;
3105    halfword attrlist = node_attr(target);
3106    scaled fraction = accent_fraction(target) > 0 ? accent_fraction(target) : scaling_factor;
3107    scaled skew = 0;
3108    scaled offset = 0;
3109    scaled innery = 0;
3110    halfword accent = null;
3111    halfword base = null;
3112    halfword result = null;
3113    halfword nucleus = noad_nucleus(target);
3114    halfword stretch = (flags & stretch_accent_code) == stretch_accent_code;
3115    halfword basefnt = null_font;
3116    halfword basechr = 0;
3117    halfword accentbasefnt = accentfnt;
3118    halfword accentbasechr = accentchr;
3119    int found = 0;
3120    int isscaled = 0;
3121    int keep = 0;
3122    /*tex
3123        Compute the amount of skew, or set |skew| to an alignment point. This will be true if a
3124        top-accent has been determined. This concerns the base! Beware, we have not yet processed 
3125        the base so the (optional) smaller size is nto yet set. 
3126    */
3127    int absolute = tex_aux_compute_accent_skew(target, flags, &skew, size);
3128    {
3129        /*tex Here we can also process the possible compact one. */
3130        halfword usedstyle;
3131        kernset localkerns;
3132        if (flags & top_accent_code) {
3133            usedstyle = tex_math_style_variant(style, math_parameter_top_accent_variant);
3134        } else if (flags & bot_accent_code) {
3135            usedstyle = tex_math_style_variant(style, math_parameter_bottom_accent_variant);
3136        } else {
3137            usedstyle = tex_math_style_variant(style, math_parameter_overlay_accent_variant);
3138        }
3139        tex_math_wipe_kerns(&localkerns);
3140        /*tex Beware: this adds italic correction because it feeds into mlist_to_hlist */
3141        base = tex_aux_clean_box(noad_nucleus(target), usedstyle, style, math_nucleus_list, 1, &localkerns); /* keep italic */
3142        if (flags & top_accent_code) {
3143            if (leftkern) { 
3144                *leftkern = localkerns.bottomleft;
3145            }
3146            if (rightkern) { 
3147                *rightkern = localkerns.bottomright;
3148            }
3149        } else if (flags & bot_accent_code) {
3150            if (leftkern) { 
3151                *leftkern = localkerns.topleft;
3152            }
3153            if (rightkern) { 
3154                *rightkern = localkerns.topright;
3155            }
3156        }
3157        basewidth = box_width(base);
3158        baseheight = box_height(base);
3159     // basedepth = box_depth(base);
3160    }
3161    if (base) {
3162        /*tex We always have a base anyway. */
3163        halfword list = box_list(base);
3164        if (list && node_type(list) == glyph_node) {
3165            /*tex Here we have the possible compact one. */
3166            basefnt = glyph_font(list);
3167            basechr = glyph_character(list);
3168        }
3169    }
3170    if (stretch && absolute && (flags & top_accent_code) && tex_aux_math_engine_control(accentfnt, math_control_accent_top_skew_with_offset)) {
3171        /*tex 
3172            This assumes a font that has been tuned for it. We used a privately made font (will be
3173            in the \CONTEXT\ distribution) RalphSmithsFormalScript.otf (derived from the type one 
3174            font) for experimenting with top accents and these parameters. The idea is to have a 
3175            decent accent on the very slanted top (of e.g. A) that sticks out a little at the right 
3176            edge but still use glyphs with a proper boundingbox, so no messing around with italic 
3177            correction. Eventually this might become a more advanced (general) mechanism. Watch the 
3178            formula for calculating the used width. 
3179        */
3180        if (base && basefnt && basechr) { 
3181            offset = tex_char_top_overshoot_from_font(basefnt, basechr);
3182            offset = offset == INT_MIN ? 0 : tex_aux_math_x_size_scaled(basefnt, offset, size);
3183        }
3184        usedwidth = 2 * ((skew < (basewidth - skew) ? skew : (basewidth - skew)) + offset);
3185    } else if (! absolute && tex_aux_math_engine_control(accentfnt, math_control_accent_skew_half)) {
3186        skew = tex_half_scaled(basewidth);
3187        absolute = 1;
3188        usedwidth = basewidth;
3189    } else { 
3190        usedwidth = basewidth;
3191    }
3192    /*tex
3193        Todo: |w = w - loffset - roffset| but then we also need to add a few
3194        kerns so no hurry with that one.
3195    */
3196    if (stretch && (tex_char_width_from_font(accentfnt, accentchr) < usedwidth)) {
3197        /*tex Switch to a larger accent if available and appropriate */
3198        scaled target = 0; 
3199        if (flags & overlay_accent_code) { 
3200            target = baseheight;
3201        } else {
3202            target += usedwidth;
3203            if (base && basefnt && basechr) { 
3204                target += tex_aux_math_x_size_scaled(basefnt, tex_char_right_margin_from_font(basefnt, basechr), size);
3205                target += tex_aux_math_x_size_scaled(basefnt, tex_char_left_margin_from_font(basefnt, basechr), size);
3206            }
3207        }
3208        if (fraction > 0) {
3209            target = tex_xn_over_d(target, fraction, scaling_factor);
3210        }
3211        while (1) {
3212            if (tex_char_has_tag_from_font(accentfnt, accentchr, extensible_tag)) {
3213                extended = tex_char_extensible_recipe_from_font(accentfnt, accentchr);
3214            }
3215            if (extended) {
3216                /*tex
3217                    This is a bit weird for an overlay but anyway, here we don't need a factor as
3218                    we don't step.
3219                */
3220                halfword overlap = tex_get_math_x_parameter_checked(style, math_parameter_connector_overlap_min);
3221                accent = tex_aux_get_delimiter_box(accentfnt, accentchr, usedwidth, overlap, 1, attrlist);
3222                accent = register_extensible(accentfnt, accentchr, size, accent, attrlist);
3223                break;
3224            } else if (! tex_char_has_tag_from_font(accentfnt, accentchr, list_tag)) {
3225                break;
3226            } else {
3227                halfword next = tex_char_next_from_font(accentfnt, accentchr);
3228                if (! tex_char_exists(accentfnt, next)) {
3229                    break;
3230                } else if (flags & overlay_accent_code) {
3231                    if (tex_aux_math_y_size_scaled(accentfnt, tex_char_height_from_font(accentfnt, next), size) > target) {
3232                        break;
3233                    }
3234                } else {
3235                    if (tex_aux_math_x_size_scaled(accentfnt, tex_char_width_from_font(accentfnt, next), size) > target) {
3236                        break;
3237                    }
3238                }
3239                accentchr = next;
3240            }
3241        }
3242
3243    }
3244    keep = (accentfnt == accentbasefnt) && (accentchr == accentbasechr) && (has_noad_option_keep_base(target) || tex_char_has_tag_from_font(accentfnt, accentchr, keep_base_tag));
3245    if (accent) {
3246        /*tex
3247            We have an extensible that already has been boxed
3248        */
3249    } else { 
3250        /*tex 
3251            We have a base char or a variant. For traditional fonts the italic correction gets 
3252            added to width (not that we have these in \CONTEXT).  
3253        */
3254        accent = tex_aux_char_box(accentfnt, accentchr, attrlist, NULL, glyph_math_accent_subtype, usedwidth, style, keep ? 0 : has_noad_option_shrink(target), keep ? 0 : has_noad_option_stretch(target), &isscaled); // basewidth 
3255        found = 1; 
3256    }
3257    if (flags & top_accent_code) {
3258        scaled b = tex_get_math_y_parameter(style, math_parameter_accent_base_height);
3259        scaled u = tex_get_math_y_parameter(style, math_parameter_accent_top_shift_up);
3260        if (found && ! tex_aux_math_engine_control(accentfnt, math_control_ignore_flat_accents)) {
3261            scaled f = tex_get_math_y_parameter(style, math_parameter_flattened_accent_base_height);
3262            if (f != undefined_math_parameter && baseheight > f) {
3263                int keep = (accentfnt == accentbasefnt) && (accentchr == accentbasechr) && (has_noad_option_keep_base(target) || tex_char_has_tag_from_font(accentfnt, accentchr, keep_base_tag));
3264                halfword flatchr = tex_char_flat_accent_from_font(accentfnt, accentchr);
3265                if (flatchr && flatchr != INT_MIN && flatchr != accentchr) {
3266                    scaled uf = tex_get_math_y_parameter(style, math_parameter_flattened_accent_top_shift_up);
3267                    if (uf != undefined_math_parameter) {
3268                        u = uf; 
3269                    }
3270                    tex_flush_node(accent);
3271                    accent = tex_aux_char_box(accentfnt, flatchr, attrlist, NULL, glyph_math_accent_subtype, usedwidth, style, keep ? 0 : has_noad_option_shrink(target), keep ? 0 : has_noad_option_stretch(target), &isscaled);
3272                    if (tracing_math_par >= 2) {
3273                        tex_begin_diagnostic();
3274                        tex_print_format("[math: flattening accent, old %x, new %x]", accentchr, flatchr);
3275                        tex_end_diagnostic();
3276                    }
3277                    accentchr = flatchr;
3278                }
3279            }
3280        }
3281        if (has_noad_option_auto_base(target)) {
3282            b = - box_depth(accent);
3283        }
3284        if (b != undefined_math_parameter) {
3285            /* not okay but interesting with negative values */
3286            delta = baseheight < b ? baseheight : b;
3287        }
3288        if (u != undefined_math_parameter) {
3289            delta -= u;
3290        }
3291        if (tex_char_has_tag_from_font(accentfnt, accentchr, inner_top_tag)) { 
3292            innery = tex_char_inner_y_offset_from_font(accentfnt, accentchr);
3293            innery = innery == INT_MIN ? 0 : tex_aux_math_y_size_scaled(accentfnt, innery, size);
3294        }
3295    } else if (flags & bot_accent_code) {
3296     // scaled b = tex_get_math_y_parameter(style, math_parameter_accent_base_depth, 0);
3297     // scaled f = tex_get_math_y_parameter(style, math_parameter_flattened_accent_base_depth, 0);
3298        scaled l = tex_get_math_y_parameter(style, stretch ? math_parameter_flattened_accent_bottom_shift_down : math_parameter_accent_bottom_shift_down);
3299     // if (b != undefined_math_parameter) {
3300         // /* not okay */
3301         // delta = basedepth < b ? basedepth : b;
3302     // }
3303        if (l != undefined_math_parameter) {
3304            delta += l;
3305        }
3306        if (tex_char_has_tag_from_font(accentfnt, accentchr, inner_bottom_tag)) { 
3307            innery = tex_char_inner_y_offset_from_font(accentfnt, accentchr);
3308            innery = innery == INT_MIN ? 0 : tex_aux_math_y_size_scaled(accentfnt, innery, size);
3309        }
3310    } else { /* if (flags & overlay_accent_code) { */
3311        /*tex Center the accent vertically around base: */
3312        if (has_noad_option_exact(target)) {
3313            delta = box_height(base) + box_depth(accent);
3314        } else { 
3315            delta = tex_half_scaled(box_total(accent) + box_total(base));
3316        }
3317     // if (has_noad_option_axis(target)) {
3318     //     delta -= tex_aux_math_axis(size);
3319     // }
3320    }
3321    if (accenttotal) {
3322        *accenttotal = box_total(accent);
3323    }
3324    if (node_type(nucleus) != math_char_node) {
3325        /*tex We have a molecule, not a simple atom. */
3326    } else if (noad_has_following_scripts(target)) {
3327        /*tex Swap the scripts: */
3328        tex_flush_node_list(base);
3329        base = tex_new_node(simple_noad, ordinary_noad_subtype);
3330        tex_attach_attribute_list_copy(base, nucleus);
3331        noad_nucleus(base) = tex_aux_math_clone(nucleus);
3332        /* we no longer move the outer scripts to the inner noad */
3333        node_type(nucleus) = sub_mlist_node;
3334        kernel_math_list(nucleus) = base;
3335        base = tex_aux_clean_box(nucleus, style, style, math_nucleus_list, 1, NULL); /* keep italic */
3336        delta = delta + box_height(base) - baseheight;
3337        baseheight = box_height(base);
3338    }
3339    /*tex The top accents of both characters are aligned. */
3340    if (flags & overlay_accent_code) {
3341        /* We ignore overshoot here, at least for now. */
3342        box_shift_amount(accent) = tex_half_scaled(basewidth - box_width(accent));
3343        box_width(accent) = 0; /* in gyre zero anyway */
3344    } else {
3345        halfword accentwidth = box_width(accent);
3346        if (accentwidth > basewidth && has_noad_option_nooverflow(target)) { 
3347            /*tex 
3348                This likely only happens with (too wide base) rules so centering is quite okay then and a
3349                bit like scaling. But it could be an accent option (weren't it that we ran out of bits). 
3350                In that case a topaccent is also unlikely. 
3351            */
3352            scaled leftkern = tex_half_scaled(accentwidth - basewidth);
3353            if (leftkern > 0) {
3354                halfword kern = tex_new_kern_node(leftkern, horizontal_math_kern_subtype);
3355                tex_attach_attribute_list_copy(kern, target);
3356                tex_try_couple_nodes(kern, base);
3357                base = tex_hpack(kern, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
3358                tex_attach_attribute_list_copy(base, target);
3359                basewidth = accentwidth;
3360                box_width(base) = accentwidth;
3361            }
3362        } else {
3363            if (absolute) {
3364                scaled anchor = 0; /* maybe: INT_MIN */
3365                if (extended || isscaled) {
3366                    /*tex If the accent is extensible just take the center. */
3367                    anchor = tex_half_scaled(accentwidth);
3368                } else {
3369                    /*tex When we scale we center. */
3370                    if (flags & top_accent_code) {
3371                        anchor = tex_char_unchecked_top_anchor_from_font(accentfnt, accentchr); 
3372                    } else if (flags & bot_accent_code) {
3373                        anchor = tex_char_unchecked_bottom_anchor_from_font(accentfnt, accentchr); 
3374                    } else { 
3375                        anchor = INT_MIN;
3376                    }
3377                    if (anchor == INT_MIN || has_noad_option_center(target)) {
3378                        /*tex just take the center */
3379                        anchor = tex_half_scaled(accentwidth);
3380                    } else {
3381                        anchor = tex_aux_math_x_size_scaled(accentfnt, anchor, size);
3382                    } 
3383                }
3384                if (math_direction_par == dir_righttoleft) {
3385                   skew += anchor - accentwidth;
3386                } else {
3387                  skew -= anchor;
3388                }
3389            } else if (accentwidth == 0) {
3390                skew += basewidth;
3391            } else if (math_direction_par == dir_righttoleft) {
3392                skew += accentwidth; /* ok? */
3393            } else {
3394                skew += tex_half_scaled(basewidth - accentwidth);
3395            }
3396            box_shift_amount(accent) = skew;
3397            box_width(accent) = 0; /* in gyre zero anyway */
3398            if (accentwidth) { 
3399                overshoot = accentwidth + skew - basewidth;
3400            }
3401            if (overshoot < 0) {
3402                overshoot = 0;
3403            }
3404        }
3405    }
3406    if (flags & (top_accent_code)) {
3407        accent_top_overshoot(target) = overshoot;
3408    }
3409    if (flags & (bot_accent_code)) {
3410        accent_bot_overshoot(target) = overshoot;
3411    }
3412    if (flags & (top_accent_code | overlay_accent_code)) {
3413        delta += innery;
3414        if (delta) {
3415            halfword kern = tex_new_kern_node(-delta, vertical_math_kern_subtype);
3416            tex_attach_attribute_list_copy(kern, target);
3417            tex_couple_nodes(accent, kern);
3418            tex_couple_nodes(kern, base);
3419        } else {
3420            tex_couple_nodes(accent, base);
3421        }
3422        result = accent;
3423    } else if ((flags & bot_accent_code) && innery) {
3424        halfword kern = tex_new_kern_node(innery, vertical_math_kern_subtype);
3425        tex_attach_attribute_list_copy(kern, target);
3426        tex_couple_nodes(base, kern);
3427        tex_couple_nodes(kern, accent);
3428        result = base;
3429    } else {
3430        tex_couple_nodes(base, accent);
3431        result = base;
3432    }
3433    result = tex_vpack(result, 0, packing_additional, max_dimension, (singleword) math_direction_par, holding_none_option, NULL);
3434    tex_attach_attribute_list_copy(result, target);
3435    node_subtype(result) = math_accent_list;
3436    box_width(result) = box_width(base); // basewidth
3437    delta = baseheight - box_height(result);
3438    if (flags & (top_accent_code | overlay_accent_code)) {
3439        if (delta > 0) {
3440            /*tex make the height of box |y| equal to |h| */
3441            halfword kern = tex_new_kern_node(delta, vertical_math_kern_subtype);
3442            tex_attach_attribute_list_copy(kern, target);
3443            tex_try_couple_nodes(kern, box_list(result));
3444            box_list(result) = kern;
3445            box_height(result) = baseheight;
3446        }
3447    } else {
3448        box_shift_amount(result) = - delta;
3449    }
3450    box_width(result) += overshoot;
3451
3452if (node_type(result) == vlist_node) {
3453    result = tex_hpack(result, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
3454    tex_attach_attribute_list_copy(result, target);
3455    node_subtype(result) = math_accent_list;
3456}
3457
3458    kernel_math_list(nucleus) = result;
3459    node_type(nucleus) = sub_box_node;
3460}
3461
3462static void tex_aux_make_accent(halfword target, int style, int size, kernset *kerns)
3463{
3464    int topstretch = 0; /* ! (node_subtype(q) % 2); */
3465    int botstretch = 0; /* ! (node_subtype(q) / 2); */
3466    halfword fnt = null;
3467    halfword chr = null;
3468    /*tex
3469        We don't do some div and mod magic on the subtype here: we just check it:
3470    */
3471    switch (node_subtype(target)) {
3472        case bothflexible_accent_subtype: topstretch = 1; botstretch = 1; break;
3473        case fixedtop_accent_subtype    : botstretch = 1; break;
3474        case fixedbottom_accent_subtype : topstretch = 1; break;
3475        case fixedboth_accent_subtype   : break;
3476    }
3477    /*tex
3478        There is some inefficiency here as we calculate the width of the nuclues upto three times.
3479        Maybe I need to have a look at that some day.
3480    */
3481    if (accent_top_character(target)) {
3482        if (tex_aux_fetch(accent_top_character(target), "top accent", &fnt, &chr)) {
3483            tex_aux_do_make_math_accent(target, fnt, chr, top_accent_code | (topstretch ? stretch_accent_code : 0), style, size, &(kerns->toptotal), 
3484                tex_math_has_class_option(accent_noad_subtype, left_bottom_kern_class_option)  ? &(kerns->bottomleft)  : NULL,
3485                tex_math_has_class_option(accent_noad_subtype, right_bottom_kern_class_option) ? &(kerns->bottomright) : NULL
3486            );
3487        }
3488        tex_flush_node(accent_top_character(target));
3489        accent_top_character(target) = null;
3490    }
3491    if (accent_bottom_character(target)) {
3492        if (tex_aux_fetch(accent_bottom_character(target), "bottom accent", &fnt, &chr)) {
3493            tex_aux_do_make_math_accent(target, fnt, chr, bot_accent_code | (botstretch ? stretch_accent_code : 0), style, size, &(kerns->bottomtotal), 
3494                tex_math_has_class_option(accent_noad_subtype, left_bottom_kern_class_option)  ? &(kerns->topleft)  : NULL,
3495                tex_math_has_class_option(accent_noad_subtype, right_bottom_kern_class_option) ? &(kerns->topright) : NULL
3496            );
3497        }
3498        tex_flush_node(accent_bottom_character(target));
3499        accent_bottom_character(target) = null;
3500    }
3501    if (accent_middle_character(target)) {
3502        if (tex_aux_fetch(accent_middle_character(target), "overlay accent", &fnt, &chr)) {
3503            tex_aux_do_make_math_accent(target, fnt, chr, overlay_accent_code | stretch_accent_code, style, size, NULL, NULL, NULL);
3504        }
3505        tex_flush_node(accent_middle_character(target));
3506        accent_middle_character(target) = null;
3507    }
3508    if (noad_source(target)) {
3509        halfword result = kernel_math_list(noad_nucleus(target));
3510        if (result) {
3511            box_source_anchor(result) = noad_source(target);
3512            tex_set_box_geometry(result, anchor_geometry);
3513        }
3514    }
3515}
3516
3517/*tex
3518
3519    The |make_fraction| procedure is a bit different because it sets |new_hlist (q)| directly rather
3520    than making a sub-box.
3521
3522    Kerns are probably never zero so no need to be lean here. Actually they are likely to
3523    be the same. By the time we make the rule we already dealt with all these clearance
3524    issues, so we're sort of ahead of what happens in a callback wrt thickness.
3525
3526    This rather large function has been split up in pieces which is a bit more readable but also gives
3527    a much bigger binary (probably due to inlining the helpers).  
3528
3529*/ 
3530
3531/*tex
3532    Create equal-width boxes |x| and |z| for the numerator and denominator. After this one is 
3533    called we compute the default amounts |shift_up| and |shift_down| by which they are displaced 
3534    from the baseline.
3535*/
3536
3537static void tex_aux_wrap_fraction_parts(halfword target, int style, int size, halfword *numerator, halfword *denominator, int check)
3538{
3539    if (noad_style(target) == unused_math_style) {
3540        *numerator = tex_aux_clean_box(fraction_numerator(target), tex_math_style_variant(style, math_parameter_numerator_variant), style, math_numerator_list, 0, NULL);
3541        *denominator = tex_aux_clean_box(fraction_denominator(target), tex_math_style_variant(style, math_parameter_denominator_variant), style, math_denominator_list, 0, NULL);
3542    } else {
3543        *numerator = tex_aux_clean_box(fraction_numerator(target), noad_style(target), style, math_numerator_list, 0, NULL);
3544        *denominator = tex_aux_clean_box(fraction_denominator(target), noad_style(target), style, math_denominator_list, 0, NULL);
3545    }
3546    if (check) {
3547        if (box_width(*numerator) < box_width(*denominator)) {
3548            *numerator = tex_aux_rebox(*numerator, box_width(*denominator), size);
3549        } else {
3550            *denominator = tex_aux_rebox(*denominator, box_width(*numerator), size);
3551        }
3552    }
3553}
3554
3555/*tex
3556    Put the fraction into a box with its delimiters, and make |new_hlist(q)| point to it.
3557*/
3558
3559static void tex_aux_wrap_fraction_result(halfword target, int style, int size, halfword fraction, kernset *kerns)
3560{
3561    halfword result = null;
3562    halfword left_delimiter = fraction_left_delimiter(target);
3563    halfword right_delimiter = fraction_right_delimiter(target);
3564    if (left_delimiter || right_delimiter) {
3565        halfword left = null;
3566        halfword right = null;
3567        halfword delta = tex_get_math_y_parameter(style, math_parameter_fraction_del_size);
3568        delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
3569        if (delta == undefined_math_parameter) {
3570            delta = tex_aux_get_delimiter_height(box_height(fraction), box_depth(fraction), 1, size, style);
3571        }
3572        /*tex Watch out: there can be empty delimiter boxes but with width. */
3573        left = tex_aux_make_delimiter(target, left_delimiter, size, delta, 0, style, 1, NULL, NULL, 0, has_noad_option_nooverflow(target), NULL, 0, null);
3574        right = tex_aux_make_delimiter(target, right_delimiter, size, delta, 0, style, 1, NULL, NULL, 0, has_noad_option_nooverflow(target), &extremes, 0, null);
3575        if (kerns && extremes.tfont) { 
3576            if (tex_math_has_class_option(fraction_noad_subtype, carry_over_left_top_kern_class_option)) {  
3577                kerns->topleft = tex_aux_math_x_size_scaled(extremes.tfont, tex_char_top_left_kern_from_font(extremes.tfont, extremes.tchar), size);
3578            }
3579            if (tex_math_has_class_option(fraction_noad_subtype, carry_over_left_bottom_kern_class_option)) {  
3580                kerns->bottomleft = tex_aux_math_x_size_scaled(extremes.bfont, tex_char_bottom_left_kern_from_font(extremes.bfont, extremes.bchar), size);
3581            }
3582            if (tex_math_has_class_option(fraction_noad_subtype, carry_over_right_top_kern_class_option)) {  
3583                kerns->topright = tex_aux_math_x_size_scaled(extremes.tfont, tex_char_top_right_kern_from_font(extremes.tfont, extremes.tchar), size);
3584            }
3585            if (tex_math_has_class_option(fraction_noad_subtype, carry_over_right_bottom_kern_class_option)) {  
3586                kerns->bottomright = tex_aux_math_x_size_scaled(extremes.bfont, tex_char_bottom_right_kern_from_font(extremes.bfont, extremes.bchar), size);
3587            }
3588            if (tex_math_has_class_option(fraction_noad_subtype, prefer_delimiter_dimensions_class_option)) {  
3589                kerns->height = extremes.height;
3590                kerns->depth = extremes.depth;
3591                kerns->dimensions = 1;
3592                kerns->font = extremes.tfont;
3593            }
3594        }
3595     /* tex_aux_normalize_delimiters(left, right); */
3596        tex_couple_nodes(left, fraction);
3597        tex_couple_nodes(fraction, right);
3598        fraction = left;
3599    }
3600    result = tex_hpack(fraction, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
3601    tex_attach_attribute_list_copy(result, target);
3602    /*tex There can also be a nested one: */
3603    node_subtype(result) = math_fraction_list;
3604    tex_aux_assign_new_hlist(target, result);
3605    if (noad_source(target)) {
3606        box_source_anchor(result) = noad_source(target);
3607     // box_anchor(result) = left_origin_anchor;
3608        tex_set_box_geometry(result, anchor_geometry);
3609    }
3610}
3611
3612/*tex
3613    The numerator and denominator must be separated by a certain minimum clearance, called |clr| in 
3614    the following program. The difference between |clr| and the actual clearance is |2 * delta|.
3615
3616    In the case of a fraction line, the minimum clearance depends on the actual thickness of the 
3617    line but we've moved that elsewhere. This gap vs up/down is kindo f weird anyway. 
3618*/
3619
3620static void tex_aux_calculate_fraction_shifts(halfword target, int style, int size, scaled *shift_up, scaled *shift_down, int up, int down)
3621{
3622    (void) size;
3623    *shift_up = tex_get_math_y_parameter_checked(style, up);
3624    *shift_down = tex_get_math_y_parameter_checked(style, down);
3625    *shift_up = tex_round_xn_over_d(*shift_up, fraction_v_factor(target), scaling_factor);
3626    *shift_down = tex_round_xn_over_d(*shift_down, fraction_v_factor(target), scaling_factor);
3627}
3628
3629static 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)
3630{
3631    scaled clearance = tex_get_math_y_parameter_checked(style, math_parameter_stack_vgap);
3632    tex_aux_calculate_fraction_shifts(target, style, size, shift_up, shift_down, math_parameter_stack_num_up, math_parameter_stack_denom_down);
3633    *delta = tex_half_scaled(clearance - ((*shift_up - box_depth(numerator)) - (box_height(denominator) - *shift_down)));
3634    if (*delta > 0) {
3635        *shift_up += *delta;
3636        *shift_down += *delta;
3637    }
3638}
3639
3640static 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)
3641{
3642    scaled axis = tex_aux_math_axis(size);
3643    scaled numerator_clearance = tex_get_math_y_parameter_checked(style, math_parameter_fraction_num_vgap);
3644    scaled denominator_clearance = tex_get_math_y_parameter_checked(style, math_parameter_fraction_denom_vgap);
3645    scaled delta_up = 0;
3646    scaled delta_down = 0;
3647    tex_aux_calculate_fraction_shifts(target, style, size, shift_up, shift_down, math_parameter_fraction_num_up, math_parameter_fraction_denom_down);
3648    /* hm, delta is only set when we have a middle delimiter ... needs checking .. i should write this from scratch */
3649    *delta = tex_half_scaled(tex_aux_math_given_y_scaled(fraction_rule_thickness(target)));
3650    delta_up = numerator_clearance - ((*shift_up   - box_depth(numerator) ) - (axis + *delta));
3651    delta_down = denominator_clearance - ((*shift_down - box_height(denominator)) + (axis - *delta));
3652    if (delta_up > 0) {
3653        *shift_up += delta_up;
3654    }
3655    if (delta_down > 0) {
3656        *shift_down += delta_down;
3657    }
3658}
3659
3660static scaled tex_aux_check_fraction_rule(halfword target, int style, int size, int fractiontype, halfword *usedfam)
3661{
3662    scaled preferfont = has_noad_option_preferfontthickness(target);
3663    halfword fam = math_rules_fam_par;
3664    (void) style;
3665    /*tex
3666        We can take the rule width from an explicitly set fam, even if a fraction itself has no
3667        character, otherwise we just use the math parameter.
3668    */
3669    if (preferfont) {
3670        /*tex Forced by option or command. */
3671    } else if (fractiontype == above_fraction_subtype) {
3672        /*tex Bypassed by command. */
3673        preferfont = 0;
3674        if (has_noad_option_proportional(target)) {            
3675            /* We replaced the non |exact| code path by this one: */
3676            scaled text = tex_get_math_y_parameter_checked(text_style, math_parameter_fraction_rule);
3677            scaled here = tex_get_math_y_parameter_checked(style, math_parameter_fraction_rule);
3678            fraction_rule_thickness(target) = tex_ext_xn_over_d(fraction_rule_thickness(target), here, text);
3679        }
3680    } else if (fraction_rule_thickness(target)) {
3681        /*tex Controlled by optional parameter. */
3682        preferfont = 1;
3683    }
3684    if (preferfont) {
3685        halfword t = tex_aux_check_rule_thickness(target, size, &fam, math_control_fraction_rule, FractionRuleThickness);
3686        if (t != undefined_math_parameter) {
3687            fraction_rule_thickness(target) = t;
3688        }
3689    }
3690    if (fraction_rule_thickness(target) == preset_rule_thickness) {
3691        fraction_rule_thickness(target) = tex_get_math_y_parameter_checked(style, math_parameter_fraction_rule); 
3692    }
3693    if (usedfam) {
3694        *usedfam = fam;
3695    }
3696    return tex_aux_math_given_y_scaled(fraction_rule_thickness(target));
3697}
3698
3699static void tex_aux_compensate_fraction_rule(halfword target, halfword fraction, halfword separator, scaled thickness)
3700{
3701    (void) target;
3702    if (box_total(separator) != thickness) {
3703        scaled half = tex_half_scaled(box_total(separator) - thickness);
3704        box_height(fraction) += half;
3705        box_depth(fraction) += half;
3706    }
3707}
3708
3709static void tex_aux_apply_fraction_shifts(halfword fraction, halfword numerator, halfword denominator, scaled shift_up, scaled shift_down)
3710{
3711    box_height(fraction) = shift_up + box_height(numerator);
3712    box_depth(fraction) = box_depth(denominator) + shift_down;
3713    box_width(fraction) = box_width(numerator);
3714}
3715
3716/*tex
3717    We construct a vlist box for the fraction, according to |shift_up| and |shift_down|. Maybe in 
3718    the meantime it is nicer to just calculate the fraction instead of messing with the height and
3719    depth explicitly (the old approach). 
3720*/
3721
3722static 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)
3723{
3724    (void) target;
3725    (void) style;
3726    if (separator) {
3727        scaled axis = tex_aux_math_axis(size);
3728        halfword after = tex_new_kern_node((axis - delta) - (box_height(denominator) - shift_down), vertical_math_kern_subtype);
3729        halfword before = tex_new_kern_node((shift_up - box_depth(numerator)) - (axis + delta), vertical_math_kern_subtype);
3730        tex_attach_attribute_list_copy(after, target);
3731        tex_attach_attribute_list_copy(before, target);
3732        tex_couple_nodes(separator, after);
3733        tex_couple_nodes(after, denominator);
3734        tex_couple_nodes(before, separator);
3735        tex_couple_nodes(numerator, before);
3736    } else { 
3737        halfword between = tex_new_kern_node((shift_up - box_depth(numerator)) - (box_height(denominator) - shift_down), vertical_math_kern_subtype);
3738        tex_attach_attribute_list_copy(between, target);
3739        tex_couple_nodes(between, denominator);
3740        tex_couple_nodes(numerator, between);
3741    }
3742    return numerator;
3743}
3744
3745static halfword tex_aux_make_skewed_fraction(halfword target, int style, int size, kernset *kerns)
3746{
3747    halfword middle = null;
3748    halfword fraction = null;
3749    halfword numerator = null;
3750    halfword denominator = null;
3751    scaled delta = 0;
3752    halfword middle_delimiter = fraction_middle_delimiter(target);
3753    scaled maxheight = 0;
3754    scaled maxdepth = 0;
3755    scaled ngap = 0;
3756    scaled dgap = 0;
3757    scaled hgap = 0;
3758    delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
3759    scaled tolerance = tex_get_math_y_parameter_default(style, math_parameter_skewed_delimiter_tolerance, 0);
3760    scaled shift_up = 0;
3761    scaled shift_down = 0;
3762    (void) kerns;
3763    if (! has_noad_option_center(target)) { 
3764        shift_up = tex_get_math_y_parameter_checked(style, math_parameter_skewed_fraction_vgap);
3765        shift_down = tex_round_xn_over_d(shift_up, fraction_v_factor(target), scaling_factor);
3766    }
3767    tex_aux_wrap_fraction_parts(target, style, size, &numerator, &denominator, 0);
3768    /*tex 
3769        Here we don't share code because we're going horizontal.
3770    */
3771    if (! has_noad_option_noaxis(target)) {
3772        shift_up += tex_half_scaled(tex_aux_math_axis(size));
3773        shift_down = shift_up;
3774    }
3775    /*tex
3776        Construct a hlist box for the fraction, according to |hgap| and |vgap|.
3777    */
3778    hgap = tex_get_math_x_parameter_checked(style, math_parameter_skewed_fraction_hgap);
3779    hgap = tex_round_xn_over_d(hgap, fraction_h_factor(target), scaling_factor);
3780    {
3781        scaled ht = box_height(numerator) + shift_up;
3782        scaled dp = box_depth(numerator) - shift_up;
3783        if (dp < 0) {
3784            dp = 0;
3785        }
3786        if (ht < 0) {
3787            ht = 0;
3788        }
3789        if (ht > maxheight) {
3790            maxheight = ht;
3791        }
3792        if (dp > maxdepth) {
3793            maxdepth = dp;
3794        }
3795    }
3796    {
3797        scaled ht = box_height(denominator) - shift_down;
3798        scaled dp = box_depth(denominator) + shift_down;
3799        if (dp < 0) {
3800            dp = 0;
3801        }
3802        if (ht < 0) {
3803            ht = 0;
3804        }
3805        if (ht > maxheight) {
3806            maxheight = ht;
3807        }
3808        if (dp > maxdepth) {
3809            maxdepth = dp;
3810        }
3811    }
3812    box_shift_amount(numerator) = -shift_up;
3813    box_shift_amount(denominator) = shift_down;
3814    delta = maxheight + maxdepth;
3815    middle = tex_aux_make_delimiter(target, middle_delimiter, size, delta, 0, style, 1, NULL, NULL, tolerance, has_noad_option_nooverflow(target), &extremes, 0, null);
3816    if (has_noad_option_center(target)) { 
3817       box_shift_amount(middle) -= box_total(middle) - delta;
3818    }
3819    fraction = tex_new_null_box_node(hlist_node, math_fraction_list);
3820    tex_attach_attribute_list_copy(fraction, target);
3821    box_width(fraction) = box_width(numerator) + box_width(denominator) + box_width(middle) - hgap;
3822    hgap = -tex_half_scaled(hgap);
3823    box_height(fraction) = box_height(middle) > maxheight ? box_height(middle) : maxheight;
3824    box_depth(fraction) = box_depth(middle) > maxdepth ? box_depth(middle) : maxdepth;
3825    ngap = hgap; 
3826    dgap = hgap; 
3827    /*tex Better not do this, as we now have factors to control it and can fix the parameters. */
3828    /*
3829        if (tex_math_has_class_option(fraction_noad_subtype, carry_over_left_top_kern_class_option)) {  
3830            ngap += tex_aux_math_x_size_scaled(extremes.tfont, tex_char_top_left_kern_from_font(extremes.tfont, extremes.tchar), size);
3831        }
3832        if (tex_math_has_class_option(fraction_noad_subtype, carry_over_right_bottom_kern_class_option)) {  
3833            dgap += tex_aux_math_x_size_scaled(extremes.bfont, tex_char_bottom_right_kern_from_font(extremes.bfont, extremes.bchar), size);
3834        }
3835    */
3836    if (ngap || dgap) {
3837        // todo: only add when non zero 
3838        halfword nkern = tex_new_kern_node(ngap, horizontal_math_kern_subtype);
3839        halfword dkern = tex_new_kern_node(dgap, horizontal_math_kern_subtype);
3840        tex_attach_attribute_list_copy(nkern, target);
3841        tex_attach_attribute_list_copy(dkern, target);
3842        tex_couple_nodes(numerator, nkern);
3843        tex_couple_nodes(nkern, middle);
3844        tex_couple_nodes(middle, dkern);
3845        tex_couple_nodes(dkern, denominator);
3846    } else {
3847        tex_couple_nodes(numerator, middle);
3848        tex_couple_nodes(middle, denominator);
3849    }
3850    box_list(fraction) = numerator;     
3851    return fraction;
3852}
3853
3854static halfword tex_aux_make_ruled_fraction(halfword target, int style, int size, kernset *kerns, int fractiontype)
3855{
3856    halfword numerator = null;
3857    halfword denominator = null;
3858    scaled shift_up = 0;
3859    scaled shift_down = 0;
3860    scaled delta = 0;
3861    halfword fam = 0;
3862    halfword thickness = tex_aux_check_fraction_rule(target, style, size, fractiontype, &fam);
3863    halfword fraction = tex_new_null_box_node(vlist_node, math_fraction_list);
3864    halfword rule = null;
3865    (void) kerns;
3866    tex_attach_attribute_list_copy(fraction, target);
3867    tex_aux_wrap_fraction_parts(target, style, size, &numerator, &denominator, 1);
3868    if (fraction_rule_thickness(target) == 0) {
3869        tex_aux_calculate_fraction_shifts_stack(target, style, size, numerator, denominator, &shift_up, &shift_down, &delta);
3870    } else {
3871        tex_aux_calculate_fraction_shifts_normal(target, style, size, numerator, denominator, &shift_up, &shift_down, &delta);
3872    }
3873    tex_aux_apply_fraction_shifts(fraction, numerator, denominator, shift_up, shift_down);
3874    if (fractiontype != atop_fraction_subtype) {
3875        rule = tex_aux_fraction_rule(box_width(fraction), thickness, get_attribute_list(target), math_fraction_rule_subtype, size, fam);
3876        tex_aux_compensate_fraction_rule(target, fraction, rule, thickness);
3877    }
3878    box_list(fraction) = tex_aux_assemble_fraction(target, style, size, numerator, denominator, rule, delta, shift_up, shift_down);
3879    return fraction;
3880}
3881
3882static halfword tex_aux_make_stretched_fraction(halfword target, int style, int size, kernset *kerns)
3883{
3884    halfword middle_delimiter = fraction_middle_delimiter(target);
3885    if (tex_aux_has_extensible(middle_delimiter, size)) {
3886        halfword middle = null;
3887        halfword numerator = null;
3888        halfword denominator = null;
3889        scaled shift_up = 0;
3890        scaled shift_down = 0;
3891        scaled delta = 0;
3892        halfword thickness = tex_aux_check_fraction_rule(target, style, size, stretched_fraction_subtype, NULL);
3893        halfword fraction = tex_new_null_box_node(vlist_node, math_fraction_list);
3894        (void) kerns;
3895        tex_attach_attribute_list_copy(fraction, target);
3896        tex_aux_wrap_fraction_parts(target, style, size, &numerator, &denominator, 1);
3897        tex_aux_calculate_fraction_shifts_normal(target, style, size, numerator, denominator, &shift_up, &shift_down, &delta);
3898        tex_aux_apply_fraction_shifts(fraction, numerator, denominator, shift_up, shift_down);
3899        middle = tex_aux_make_delimiter(target, middle_delimiter, size, box_width(fraction), 1, style, 0, NULL, NULL, 0, 0, NULL, 0, node_attr(target));
3900        if (box_width(middle) < box_width(fraction)) {
3901            /*tex It's always in the details: */
3902            scaled delta = (box_width(fraction) - box_width(middle)) / 2;
3903            tex_aux_prepend_hkern_to_box_list(middle, delta, horizontal_math_kern_subtype, "narrow delimiter");
3904            tex_aux_append_hkern_to_box_list(middle, delta, horizontal_math_kern_subtype, "narrow delimiter");
3905            box_width(middle) = box_width(fraction);
3906        } else if (box_width(middle) > box_width(fraction)) {
3907            scaled delta = (box_width(middle) - box_width(fraction)) / 2;
3908            tex_aux_prepend_hkern_to_box_list(numerator, delta, horizontal_math_kern_subtype, "wide delimiter");
3909            tex_aux_append_hkern_to_box_list(numerator, delta, horizontal_math_kern_subtype, "wide delimiter");
3910            tex_aux_prepend_hkern_to_box_list(denominator, delta, horizontal_math_kern_subtype, "wide delimiter");
3911            tex_aux_append_hkern_to_box_list(denominator, delta, horizontal_math_kern_subtype, "wide delimiter");
3912            box_width(fraction) = box_width(middle);
3913        }
3914        tex_aux_compensate_fraction_rule(target, fraction, middle, thickness);
3915        box_list(fraction) = tex_aux_assemble_fraction(target, style, size, numerator, denominator, middle, delta, shift_up, shift_down);
3916        return fraction;
3917    } else { 
3918        return tex_aux_make_ruled_fraction(target, style, size, kerns, over_fraction_subtype);
3919    }
3920}
3921
3922/*tex 
3923    We intercept bad nodes created at the \LUA\ end but only partially. The fraction handler is
3924    quite complex and uses a lot of parameters. You shouldn't mess with \TEX. 
3925*/
3926
3927static void tex_aux_make_fraction(halfword target, int style, int size, kernset *kerns)
3928{
3929    quarterword fractiontype = node_subtype(target);
3930    halfword fraction = null;
3931  TRYAGAIN:
3932    switch (fractiontype) { 
3933        case over_fraction_subtype: 
3934        case atop_fraction_subtype: 
3935        case above_fraction_subtype: 
3936            tex_flush_node_list(fraction_middle_delimiter(target));
3937            fraction_middle_delimiter(target) = null;
3938            fraction = tex_aux_make_ruled_fraction(target, style, size, kerns, fractiontype);
3939            break;
3940        case skewed_fraction_subtype: 
3941            fraction_rule_thickness(target) = 0;
3942            fraction = tex_aux_make_skewed_fraction(target, style, size, kerns);
3943            break;
3944        case stretched_fraction_subtype: 
3945            fraction = tex_aux_make_stretched_fraction(target, style, size, kerns);
3946            break;
3947        default: 
3948            fractiontype = atop_fraction_subtype;
3949            goto TRYAGAIN;
3950    }
3951    tex_aux_wrap_fraction_result(target, style, size, fraction, kerns);
3952    fraction_left_delimiter(target) = null;
3953    fraction_middle_delimiter(target) = null;
3954    fraction_right_delimiter(target) = null;
3955}
3956
3957/*tex
3958
3959    If the nucleus of an |op_noad| is a single character, it is to be centered vertically with
3960    respect to the axis, after first being enlarged (via a character list in the font) if we are in
3961    display style. The normal convention for placing displayed limits is to put them above and
3962    below the operator in display style.
3963
3964    The italic correction is removed from the character if there is a subscript and the limits are
3965    not being displayed. The |make_op| routine returns the value that should be used as an offset
3966    between subscript and superscript.
3967
3968    After |make_op| has acted, |subtype(q)| will be |limits| if and only if the limits have been
3969    set above and below the operator. In that case, |new_hlist(q)| will already contain the desired
3970    final box.
3971
3972    In display mode we also handle the nolimits scripts here because we have an option to tweak the
3973    placement with |\mathnolimitsmode| in displaymode. So, when we have neither |\limits| or
3974    |\nolimits| in text mode we fall through and scripts are dealt with later.
3975
3976*/
3977
3978static void tex_aux_make_scripts (
3979    halfword  target,
3980    halfword  kernel,
3981    scaled    italic,
3982    int       style,
3983    scaled    supshift,
3984    scaled    subshift,
3985    scaled    supdrop,
3986    kernset  *kerns,
3987    halfword  single    
3988);
3989
3990static halfword tex_aux_check_nucleus_complexity (
3991    halfword  target,
3992    scaled   *delta,
3993    halfword  style,
3994    halfword  size,
3995    kernset  *kerns
3996);
3997
3998/*
3999    For easy configuration ... fonts are somewhat inconsistent and the values for italic correction 
4000    run from 30 to 60\% of the width.
4001*/
4002
4003static void tex_aux_get_shifts(int mode, int style, scaled delta, scaled *top, scaled *bot)
4004{
4005    switch (mode) {
4006        case 0:
4007            /*tex full bottom correction */
4008            *top = 0;
4009            *bot = -delta;
4010            break;
4011        case 1:
4012            /*tex |MathConstants| driven */
4013            *top =  tex_round_xn_over_d(delta, tex_get_math_parameter_default(style, math_parameter_nolimit_sup_factor, 0), scaling_factor);
4014            *bot = -tex_round_xn_over_d(delta, tex_get_math_parameter_default(style, math_parameter_nolimit_sub_factor, 0), scaling_factor);
4015            break ;
4016        case 2:
4017            /*tex no correction */
4018            *top = 0;
4019            *bot = 0;
4020            break ;
4021        case 3:
4022            /*tex half bottom correction */
4023            *top =  0;
4024            *bot = -tex_half_scaled(delta);
4025            break;
4026        case 4:
4027            /*tex half bottom and top correction */
4028            *top =  tex_half_scaled(delta);
4029            *bot = -tex_half_scaled(delta);
4030            break;
4031        default :
4032            /*tex above 15: for quickly testing values */
4033            *top =  0;
4034            *bot = (mode > 15) ? -tex_round_xn_over_d(delta, mode, scaling_factor) : 0;
4035            break;
4036    }
4037}
4038
4039static scaled tex_aux_op_no_limits(halfword target, int style, int size, int italic, kernset *kerns, int forceitalics)
4040{
4041    kernset localkerns ;
4042    halfword kernel;
4043    (void) size; 
4044    (void) forceitalics; 
4045    if (kerns) { 
4046        tex_math_copy_kerns(&localkerns, kerns);
4047    } else { 
4048        tex_math_wipe_kerns(&localkerns);
4049    }
4050    kernel = tex_aux_check_nucleus_complexity(target, NULL, style, lmt_math_state.size, &localkerns);
4051    if (noad_has_scripts(target)) {
4052        scaled topshift = 0; /*tex Normally this would be: | delta|. */
4053        scaled botshift = 0; /*tex Normally this would be: |-delta|. */
4054        if (localkerns.topright || localkerns.bottomright) {
4055            italic = 0;
4056        }
4057        tex_aux_get_shifts(math_nolimits_mode_par, style, italic, &topshift, &botshift);
4058        tex_aux_make_scripts(target, kernel, 0, style, topshift, botshift, 0, &localkerns, 0);
4059    } else {
4060        tex_aux_assign_new_hlist(target, kernel);
4061    }
4062    // italic = 0;
4063    return 0; 
4064}
4065
4066static scaled tex_aux_op_do_limits(halfword target, int style, int size, int italic, kernset *kerns, int forceitalics)
4067{
4068    halfword nucleus = noad_nucleus(target);
4069    halfword superscript = tex_aux_clean_box(noad_supscr(target), tex_math_style_variant(style, math_parameter_superscript_variant), style, math_sup_list, 0, NULL);
4070    halfword kernel = tex_aux_clean_box(nucleus, style, style, math_nucleus_list, forceitalics, NULL);
4071    halfword subscript = tex_aux_clean_box(noad_subscr(target), tex_math_style_variant(style, math_parameter_subscript_variant), style, math_sub_list, 0, NULL);
4072    halfword result = tex_new_null_box_node(vlist_node, math_modifier_list);
4073    (void) kerns;
4074    tex_attach_attribute_list_copy(result, target);
4075    if (nucleus) {
4076        // todo: get rid of redundant italic calculation ... it is still a mess .. maybe use noad_italic .. then this whole branch can go 
4077        switch (node_type(nucleus)) {
4078            case sub_mlist_node:
4079            case sub_box_node:
4080                {
4081                    halfword n = kernel_math_list(nucleus);
4082                    if (! n) {
4083                        /* kind of special */
4084                    } else if (node_type(n) == hlist_node) {
4085                        /*tex just a not scaled char */
4086                        n = box_list(n);
4087                        while (n) {
4088                            if (node_type(n) == glyph_node && ! tex_has_glyph_option(n, glyph_option_no_italic_correction)) {
4089                                if (tex_aux_math_engine_control(glyph_font(n), math_control_apply_boxed_italic_kern)) {
4090                                    italic = tex_aux_math_x_size_scaled(glyph_font(n), tex_char_italic_from_font(glyph_font(n), glyph_character(n)), size);
4091                                }
4092                            }
4093                            n = node_next(n);
4094                        }
4095                    } else {
4096                        /*tex This might need checking. */
4097                        while (n) {
4098                            if (node_type(n) == fence_noad && noad_italic(n) > italic) {
4099                                /*tex we can have dummies, the period ones */
4100                                italic = tex_aux_math_given_x_scaled(noad_italic(n));
4101                            }
4102                            n = node_next(n);
4103                        }
4104                    }
4105                    break;
4106                }
4107            case math_char_node:
4108                {
4109                    halfword fnt = tex_fam_fnt(kernel_math_family(nucleus), size);
4110                    halfword chr = kernel_math_character(nucleus);
4111                    italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
4112                    break;
4113                }
4114        }
4115    }
4116    /*tex We're still doing limits. */
4117    if (noad_supscr(target) || noad_subscr(target)) {
4118        scaled supwidth = box_width(superscript);
4119        scaled boxwidth = box_width(kernel);
4120        scaled subwidth = box_width(subscript);
4121        scaled halfitalic = tex_half_scaled(italic);
4122        scaled topshift = halfitalic;
4123        scaled bottomshift = halfitalic; 
4124        /* move this into the limits mode */
4125        if (kerns && ! halfitalic) { 
4126            halfword fnt = kerns->font;
4127            halfword chr = kerns->character;
4128            if (fnt && chr) { 
4129                scaled topanchor = tex_aux_math_x_size_scaled(fnt, tex_char_top_anchor_from_font(fnt, chr), size);
4130                scaled bottomanchor = tex_aux_math_x_size_scaled(fnt, tex_char_bottom_anchor_from_font(fnt, chr), size);
4131                /* kind of weird ... see context .. might change in proper anchor */
4132                if (topanchor) { 
4133                    topshift = topanchor - boxwidth;
4134                }
4135                if (bottomanchor) {    
4136                    bottomshift = boxwidth - bottomanchor;
4137                }
4138            }
4139        }
4140        if (math_limits_mode_par >= 1) {
4141            /*tex
4142                This option enforces the real dimensions and avoids longer limits to stick out
4143                which is a traditional \TEX\ feature. It's handy to have this for testing. Nicer
4144                would be to also adapt the width of the wrapped scripts but these are reboxed
4145                with centering so we keep that as it is. We rebox anyway to get hboxes.  
4146            */
4147            halfword savedwidth = boxwidth;
4148            halfword overshoot = 0; 
4149            superscript = tex_aux_rebox(superscript, supwidth, size);
4150            kernel = tex_aux_rebox(kernel, boxwidth, size);
4151            subscript = tex_aux_rebox(subscript, subwidth, size);
4152            if (supwidth) {
4153                halfword shift = savedwidth/2 + topshift - supwidth/2;
4154                halfword delta = supwidth + shift - savedwidth;
4155                box_shift_amount(superscript) = shift;
4156                if (delta > 0) { 
4157                    boxwidth = boxwidth + delta;
4158                }
4159                overshoot = supwidth - boxwidth; 
4160                if (overshoot > 0) {
4161                    box_shift_amount(superscript) += overshoot;
4162                    box_shift_amount(kernel) += overshoot;
4163                    boxwidth = boxwidth + overshoot;
4164                } else { 
4165                    overshoot = 0;
4166                }
4167            }
4168            if (subwidth) {
4169                halfword shift = savedwidth/2 - bottomshift - subwidth/2 + overshoot;
4170                box_shift_amount(subscript) = shift;
4171                if (shift < 0) {
4172                    box_shift_amount(superscript) -= shift;
4173                    box_shift_amount(subscript) -= shift;
4174                    box_shift_amount(kernel) -= shift;
4175                    boxwidth = boxwidth - shift;
4176                }
4177                overshoot = subwidth - boxwidth; 
4178                if (overshoot > 0) {
4179                    boxwidth += overshoot;
4180                }
4181            }
4182            box_width(kernel) = boxwidth;
4183        } else { 
4184            /*tex We keep the possible left and/or right overshoot of limits. */
4185            if (supwidth > boxwidth) {
4186                boxwidth = supwidth;
4187            }
4188            if (subwidth > boxwidth) {
4189                boxwidth = subwidth;
4190            }
4191            superscript = tex_aux_rebox(superscript, boxwidth, size);
4192            kernel = tex_aux_rebox(kernel, boxwidth, size);
4193            subscript = tex_aux_rebox(subscript, boxwidth, size);
4194            /*tex This is only (visually) ok for integrals, but other operators have no italic anyway. */
4195            box_shift_amount(superscript) = topshift;
4196            box_shift_amount(subscript) = -bottomshift;
4197        }
4198        /*tex Here the target |v| is still empty but we do set the height and depth. */
4199        box_width(result) = boxwidth;
4200        box_height(result) = box_height(kernel);
4201        box_depth(result) = box_depth(kernel);
4202    } else { 
4203        box_width(result) = box_width(kernel);
4204        box_height(result) = box_height(kernel);
4205        box_depth(result) = box_depth(kernel);
4206    }
4207    /*tex
4208
4209        Attach the limits to |y| and adjust |height(v)|, |depth(v)| to account for
4210        their presence.
4211
4212        We use |shift_up| and |shift_down| in the following program for the amount of
4213        glue between the displayed operator |y| and its limits |x| and |z|.
4214
4215        The vlist inside box |v| will consist of |x| followed by |y| followed by |z|,
4216        with kern nodes for the spaces between and around them; |b| is baseline and |v|
4217        is the minumum gap.
4218
4219    */
4220    if (noad_supscr(target)) { 
4221        scaled bgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_bgap);
4222        scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_vgap);
4223        scaled vkern = tex_get_math_y_parameter_checked(style, math_parameter_limit_above_kern);
4224        scaled vshift = bgap - box_depth(superscript);
4225        if (vshift < vgap) {
4226            vshift = vgap;
4227        }
4228        if (vshift) {
4229            halfword kern = tex_new_kern_node(vshift, vertical_math_kern_subtype);
4230            tex_attach_attribute_list_copy(kern, target);
4231            tex_couple_nodes(kern, kernel);
4232            tex_couple_nodes(superscript, kern);
4233        } else {
4234            tex_couple_nodes(kernel, superscript);
4235        }
4236        if (vkern) {
4237            halfword kern = tex_new_kern_node(vkern, vertical_math_kern_subtype);
4238            tex_attach_attribute_list_copy(kern, target);
4239            tex_couple_nodes(kern, superscript);
4240            box_list(result) = kern;
4241        } else {
4242            box_list(result) = superscript;
4243        }
4244        box_height(result) += vkern + box_total(superscript) + vshift;
4245    } else {
4246        box_list(superscript) = null;
4247        tex_flush_node(superscript);
4248        box_list(result) = kernel;
4249    }
4250    if (noad_subscr(target)) {
4251        scaled bgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_bgap);
4252        scaled vgap = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_vgap);
4253        scaled vkern = tex_get_math_y_parameter_checked(style, math_parameter_limit_below_kern);
4254        scaled vshift = bgap - box_height(subscript);
4255        if (vshift < vgap) {
4256            vshift = vgap;
4257        }
4258        if (vshift) {
4259            halfword kern = tex_new_kern_node(vshift, vertical_math_kern_subtype);
4260            tex_attach_attribute_list_copy(kern, target);
4261            tex_couple_nodes(kernel, kern);
4262            tex_couple_nodes(kern, subscript);
4263        } else {
4264            tex_couple_nodes(kernel, subscript);
4265        }
4266        if (vkern) {
4267            halfword kern = tex_new_kern_node(vkern, vertical_math_kern_subtype);
4268            tex_attach_attribute_list_copy(kern, target);
4269            tex_couple_nodes(subscript, kern);
4270        }
4271        box_depth(result) += vkern + box_total(subscript) + vshift;
4272    } else {
4273        box_list(subscript) = null;
4274        tex_flush_node(subscript);
4275    }
4276    if (noad_subscr(target)) {
4277        kernel_math_list(noad_subscr(target)) = null;
4278        tex_flush_node(noad_subscr(target));
4279        noad_subscr(target) = null;
4280    }
4281    if (noad_supscr(target)) {
4282        kernel_math_list(noad_supscr(target)) = null;
4283        tex_flush_node(noad_supscr(target));
4284        noad_supscr(target) = null;
4285    }
4286    tex_aux_assign_new_hlist(target, result);
4287 // italic = 0;
4288    return 0;
4289}
4290
4291/*tex 
4292    The adapt to left or right is sort of fuzzy and might disappear in future versions. After all, 
4293    we have more fance fence support now. 
4294*/
4295
4296static scaled tex_aux_op_wrapup(halfword target, int style, int size, int italic, kernset *kerns, int forceitalics)
4297{
4298    halfword box;
4299    int shiftaxis = 0;
4300    halfword chr = null;
4301    halfword fnt = null;
4302    halfword autoleft = null;
4303    halfword autoright = null;
4304    halfword autosize = has_noad_option_auto(target);
4305    scaled openupheight = has_noad_option_openupheight(target) ? noad_height(target) : 0;
4306    scaled openupdepth = has_noad_option_openupdepth(target) ? noad_depth(target) : 0;
4307    (void) kerns;
4308    if (has_noad_option_adapttoleft(target) && node_prev(target)) {
4309        autoleft = node_prev(target);
4310        if (node_type(autoleft) != simple_noad) {
4311            autoleft = null;
4312        } else {
4313            autoleft = noad_new_hlist(autoleft);
4314        }
4315    }
4316    if (has_noad_option_adapttoright(target) && node_next(target)) {
4317        /* doesn't always work well */
4318        autoright = noad_nucleus(node_next(target));
4319    }
4320    tex_aux_fetch(noad_nucleus(target), "operator", &fnt, &chr);
4321    /*tex Nicer is actually to just test for |display_style|. */
4322    if ((style < text_style) || autoleft || autoright || autosize) {
4323        /*tex Try to make it larger in displaystyle. */
4324        scaled opsize = tex_get_math_parameter(style, math_parameter_operator_size, NULL);
4325        if ((autoleft || autoright || autosize) && (opsize == undefined_math_parameter)) {
4326            opsize = 0;
4327        }
4328        if (opsize != undefined_math_parameter) {
4329            /*tex Creating a temporary delimiter is the cleanest way. */
4330            halfword y = tex_new_node(delimiter_node, 0);
4331            tex_attach_attribute_list_copy(y, noad_nucleus(target));
4332            delimiter_small_family(y) = kernel_math_family(noad_nucleus(target));
4333            delimiter_small_character(y) = kernel_math_character(noad_nucleus(target));
4334            opsize = tex_aux_math_y_scaled(opsize, style);
4335            if (autoright) {
4336                /*tex We look ahead and preroll, |autoright| is a noad. */
4337                scaledwhd siz = tex_natural_hsizes(autoright, null, 0.0, 0, 0);
4338                scaled total = siz.ht + siz.dp;
4339                if (total > opsize) {
4340                    opsize = total;
4341                }
4342            }
4343            if (autoleft && box_total(autoleft) > opsize) {
4344                /*tex We look back and check, |autoleft| is a box. */
4345                opsize = box_total(autoleft);
4346            }
4347            /* we need to check for overflow here */
4348            opsize += limited_scaled(openupheight);
4349            opsize += openupdepth;
4350            box = 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, null);
4351        } else {
4352            /*tex
4353                Where was the weird + 1 coming from? It tweaks the comparison. Anyway, because we
4354                do a lookup we don't need to scale the |total| and |opsize|. We have a safeguard
4355                against endless loops.
4356            */
4357            opsize = tex_char_total_from_font(fnt, chr) + openupheight + openupdepth + 1;
4358            /*
4359            if (opsize) {
4360                opsize = tex_aux_math_y_style_scaled(fnt, opsize, size); // we compare unscaled
4361            }
4362            */
4363            while (tex_char_has_tag_from_font(fnt, chr, list_tag) && tex_char_total_from_font(fnt, chr) < opsize) {
4364                halfword next = tex_char_next_from_font(fnt, chr);
4365                if (chr != next && tex_char_exists(fnt, next)) {
4366                    chr = next;
4367                    kernel_math_character(noad_nucleus(target)) = chr;
4368                } else {
4369                    break;
4370                }
4371            }
4372            if (math_kernel_node_has_option(noad_nucleus(target), math_kernel_no_italic_correction) && ! forceitalics) {
4373                italic = 0;
4374            } else { 
4375                italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
4376            }
4377            box = tex_aux_clean_box(noad_nucleus(target), style, style, math_nucleus_list, 0, NULL);
4378            shiftaxis = 1;
4379        }
4380    } else {
4381        /*tex Non display style. */
4382        italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
4383        box = tex_aux_clean_box(noad_nucleus(target), style, style, math_nucleus_list, 0, NULL);
4384        box_height(box) += openupheight;
4385        box_depth(box) += openupdepth;
4386        shiftaxis = 1;
4387    }
4388    if (shiftaxis) {
4389        /*tex center vertically */
4390        box_shift_amount(box) = tex_half_scaled(box_height(box) - box_depth(box)) - tex_aux_math_axis(size);
4391    }
4392    if ((node_type(box) == hlist_node) && (openupheight || openupdepth)) {
4393        box_shift_amount(box) -= openupheight/2;
4394        box_shift_amount(box) += openupdepth/2;
4395    }
4396    if (forceitalics && italic && box_list(box)) { 
4397        /*tex 
4398            This features is provided in case one abuses operators in weird ways and expects italic 
4399            correction to be part of the width. Maybe it should be an kernel option so that it can 
4400            be controlled locally. Now here we enter fuzzy specification teritory. For n-ary 
4401            operators we are supposed to use the italic correction for placements of vertical and 
4402            horizontal scripts (limits an nolimits) but when we patch the width that gets messy (we
4403            now need to need to backtrack twice times half the correction). The bad news is that 
4404            there is no way to see if we have a n-ary unless we add a new class and only for the 
4405            lone slanted integrals in lm. So, instead we just zero the correction now. After all, 
4406            we can use a fence instead for these n-ary's. Actually there are probably not that many 
4407            slanted operators, so it' smore about using a letter as such. So, |italiic *= 2| became 
4408            |italic = 0|. 
4409        */
4410        tex_aux_math_insert_italic_kern(tex_tail_of_node_list(box_list(box)), italic, noad_nucleus(target), "operator");
4411        box_width(box) += italic;
4412        italic = 0;
4413    }
4414    node_type(noad_nucleus(target)) = sub_box_node;
4415    kernel_math_list(noad_nucleus(target)) = box;
4416    return italic;
4417}
4418
4419static scaled tex_aux_make_op(halfword target, int style, int size, int italic, int limits_mode, kernset *kerns)
4420{
4421    int forceitalics = node_subtype(target) == operator_noad_subtype && tex_math_has_class_option(operator_noad_subtype, operator_italic_correction_class_option);
4422    if (limits_mode == limits_horizontal_mode) {
4423        /*tex We enforce this and it can't be overruled! */
4424    } else if (! has_noad_option_limits(target) && ! has_noad_option_nolimits(target) && (style == display_style || style == cramped_display_style)) {
4425        limits_mode = limits_vertical_mode;
4426        noad_options(target) |= noad_option_limits; /* so we can track it */
4427    } else if (has_noad_option_nolimits(target)) { 
4428        limits_mode = limits_horizontal_mode;
4429    } else if (has_noad_option_limits(target)) { 
4430        limits_mode = limits_vertical_mode;
4431    }
4432    if (node_type(noad_nucleus(target)) == math_char_node) {
4433        italic = tex_aux_op_wrapup(target, style, size, italic, kerns, forceitalics);
4434    }
4435    switch (limits_mode) {
4436        case limits_horizontal_mode: 
4437            /*tex
4438                We end up here when there is an explicit directive or when we're in displaymode without
4439                an explicit directive. If in text mode we want to have this mode driven placement tweak
4440                we need to use the |\nolimits| directive. Beware: that mode might be changed to a font
4441                property or option itself.
4442            */
4443            return tex_aux_op_no_limits(target, style, size, italic, kerns, forceitalics); /* italic becomes zero */
4444        case limits_vertical_mode:
4445            /*tex
4446
4447                We end up here when we have a limits directive or when that property is set because
4448                we're in displaymode. The following program builds a vlist box |v| for displayed limits. 
4449                The width of the box is not affected by the fact that the limits may be skewed.
4450            */
4451            return tex_aux_op_do_limits(target, style, size, italic, kerns, forceitalics); /* italic becomes zero */
4452        default:
4453            /*tex
4454                We end up here when we're not in displaymode and don't have a (no)limits directive. 
4455                When called the wrong way we loose the nucleus. 
4456            */
4457            return italic; /* italic is retained, happens very seldom */
4458    }
4459}
4460
4461/*tex
4462
4463    A ligature found in a math formula does not create a ligature, because there is no question of
4464    hyphenation afterwards; the ligature will simply be stored in an ordinary |glyph_node|, after
4465    residing in an |ord_noad|.
4466
4467    The |type| is converted to |math_text_char| here if we would not want to apply an italic
4468    correction to the current character unless it belongs to a math font (i.e., a font with
4469    |space=0|).
4470
4471    No boundary characters enter into these ligatures.
4472
4473*/
4474
4475// $ \mathord {a}  $ : ord -> nucleus -> mathchar 
4476// $ \mathord {ab} $ : ord -> nucleus -> submlist -> ord + ord 
4477
4478/*tex 
4479    Have there ever been math fonts with kerns and ligatures? If so it had to be between characters
4480    within the same font. Maybe this was meant for composed charaters? And the 256 limits of the 
4481    number of characters didn't help either. This is why we take the freedom to do things a bit 
4482    different.
4483
4484    We don't have other kerns in opentype math fonts. There are however these staircase kerns that 
4485    are dealt with elsewhere. But for new math fonts we do need to add italic correction occasionally
4486    and staircase kerns only happen with scripts. 
4487
4488    We could add support for ligatures but we don't need those anyway so it's a waste of time and 
4489    bytes. 
4490
4491    The ord checker kicks in after every ord but we can consider a special version where we handle 
4492    |sub_list_node| noads.  And we could maybe check on sloped shapes but then we for sure end up 
4493    in a mess we don't want. 
4494
4495*/
4496
4497static halfword tex_aux_check_ord(halfword current, halfword size, halfword next)
4498{
4499    if (! noad_has_following_scripts(current)) { 
4500        halfword nucleus = noad_nucleus(current); 
4501        switch (node_type(nucleus)) { 
4502        case sub_mlist_node: 
4503            { 
4504             // I'm not that motivated for this and it should be an engine option anyway then.
4505             //
4506             // halfword head = math_list(nucleus);
4507             // halfword tail = tex_tail_of_node_list(head);
4508             // // doesn't work 
4509             // if (node_type(head) == simple_noad && node_prev(current) ) {
4510             //     if (node_type(node_prev(current)) == simple_noad) {
4511             //         head = tex_aux_check_ord(node_prev(current), size, head);
4512             //         math_list(nucleus) = head;
4513             //     }
4514             // }
4515             // // works 
4516             // if (node_type(tail) == simple_noad && node_next(current) ) {
4517             //     tex_aux_check_ord(tail, size, node_next(current));
4518             // }
4519                break;
4520            }
4521        case math_char_node: 
4522            {
4523                halfword curchr = null;
4524                halfword curfnt = null;
4525                if (! next) { 
4526                    next = node_next(current);
4527                }
4528                tex_aux_fetch(nucleus, "ordinal", &curfnt, &curchr);
4529                if (curfnt && curchr) {
4530                    halfword kern = 0;
4531                    halfword italic = 0;
4532                    if (next) {
4533                        halfword nxtnucleus = noad_nucleus(next); 
4534                        halfword nxtfnt = null;
4535                        halfword nxtchr = null;
4536                        if (node_type(nxtnucleus) == math_char_node && kernel_math_family(nucleus) == kernel_math_family(nxtnucleus)) {
4537                            tex_aux_fetch(nxtnucleus, "ordinal", &nxtfnt, &nxtchr);
4538                            if (nxtfnt && nxtchr) {
4539                                halfword mainclass = node_subtype(current);
4540                                /* todo: ligatures */
4541                                if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_kern_pair)) {
4542                                    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)) {
4543                                        /* ignore */
4544                                    } else if (tex_math_has_class_option(mainclass, check_italic_correction_class_option)) {
4545                                        /* ignore */
4546                                    } else if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_italic_kern)) { 
4547                                        kern = tex_aux_math_x_size_scaled(curfnt, tex_get_kern(curfnt, curchr, nxtchr), size);
4548                                    }
4549                                }
4550                                if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_italic_kern)) {
4551                                    if (math_kernel_node_has_option(nucleus, math_kernel_no_italic_correction)) {
4552                                        /* ignore */
4553                                    } else if (tex_math_has_class_option(mainclass, check_kern_pair_class_option)) {
4554                                        /* ignore */
4555                                    } else if (tex_aux_math_engine_control(curfnt, math_control_apply_ordinary_italic_kern)) { 
4556                                        italic = tex_aux_math_x_size_scaled(curfnt, tex_char_italic_from_font(curfnt, curchr), size);
4557                                    }
4558                                }
4559                            }
4560                        }
4561                    }
4562                    if (kern) {
4563                        current = tex_aux_math_insert_font_kern(current, kern, current, "ord");
4564                    }
4565                    if (italic) {
4566                        // todo : after last unless upright but then we need to signal
4567                        current = tex_aux_math_insert_italic_kern(current, italic, current, "ord");
4568                    }
4569                }
4570            }
4571            break;
4572        }
4573    }
4574    return current;
4575}
4576
4577static halfword tex_aux_prepend_hkern_to_new_hlist(halfword box, scaled delta, halfword subtype, const char *trace)
4578{
4579    halfword list = noad_new_hlist(box);
4580    halfword kern = tex_new_kern_node(delta, (quarterword) subtype);
4581    tex_attach_attribute_list_copy(kern, box);
4582    if (list) {
4583        tex_couple_nodes(kern, list);
4584    }
4585    list = kern;
4586    noad_new_hlist(box) = list;
4587    tex_aux_trace_kerns(kern, "adding kern", trace);
4588    return list;
4589}
4590
4591static void tex_aux_append_hkern_to_box_list(halfword box, scaled delta, halfword subtype, const char *trace)
4592{
4593    halfword list = box_list(box);
4594    halfword kern = tex_new_kern_node(delta, (quarterword) subtype);
4595    tex_attach_attribute_list_copy(kern, box);
4596    if (list) {
4597        tex_couple_nodes(tex_tail_of_node_list(list), kern);
4598    } else {
4599        list = kern;
4600    }
4601    box_list(box) = list;
4602    box_width(box) += delta;
4603    tex_aux_trace_kerns(kern, "adding kern", trace);
4604}
4605
4606static void tex_aux_prepend_hkern_to_box_list(halfword box, scaled delta, halfword subtype, const char *trace)
4607{
4608    halfword list = box_list(box);
4609    halfword kern = tex_new_kern_node(delta, (quarterword) subtype);
4610    tex_attach_attribute_list_copy(kern, box);
4611    if (list) {
4612        tex_couple_nodes(kern, list);
4613    }
4614    list = kern;
4615    box_list(box) = list;
4616    box_width(box) += delta;
4617    tex_aux_trace_kerns(kern, "adding kern", trace);
4618}
4619
4620/*tex
4621
4622    The purpose of |make_scripts (q, it)| is to attach the subscript and/or superscript of noad |q|
4623    to the list that starts at |new_hlist (q)|, given that subscript and superscript aren't both
4624    empty. The superscript will be horizontally shifted over |delta1|, the subscript over |delta2|.
4625
4626    We set |shift_down| and |shift_up| to the minimum amounts to shift the baseline of subscripts
4627    and superscripts based on the given nucleus.
4628
4629    Note: We need to look at a character but also at the first one in a sub list and there we
4630    ignore leading kerns and glue. Elsewhere is code that removes kerns assuming that is italic
4631    correction. The heuristics are unreliable for the new fonts so eventualy there will be an
4632    option to ignore such corrections. (We now actually have that level of control.)
4633
4634    Instead of a few mode parameters we now control this via the control options bitset. In this 
4635    case we cheat a bit as there is no relationship with a font (the first |null| parameter that 
4636    gets passed here). In the archive we can find all the variants. 
4637
4638*/
4639
4640static halfword tex_aux_analyze_script(halfword init, scriptdata *data)
4641{
4642    if (init) {
4643        switch (node_type(init)) {
4644            case math_char_node :
4645                if (tex_aux_math_engine_control(null, math_control_analyze_script_nucleus_char)) {
4646                    if (tex_aux_fetch(init, "script char", &(data->fnt), &(data->chr))) {
4647                        return init;
4648                    } else {
4649                        goto NOTHING;
4650                    }
4651                } else {
4652                    break;
4653                }
4654            case sub_mlist_node:
4655                if (tex_aux_math_engine_control(null, math_control_analyze_script_nucleus_list)) {
4656                    init = kernel_math_list(init);
4657                    while (init) {
4658                        switch (node_type(init)) {
4659                            case kern_node:
4660                            case glue_node:
4661                                init = node_next(init);
4662                                break;
4663                            case simple_noad:
4664                                {
4665                                    init = noad_nucleus(init);
4666                                    if (node_type(init) != math_char_node) {
4667                                        return null;
4668                                    } else if (tex_aux_fetch(init, "script list", &(data->fnt), &(data->chr))) {
4669                                        return init;
4670                                    } else {
4671                                        goto NOTHING;
4672                                    }
4673                                }
4674case accent_noad: 
4675    data->whatever = 1;
4676    goto NOTHING;
4677                            default:
4678                                goto NOTHING;
4679                        }
4680                    }
4681                }
4682                break;
4683            case sub_box_node:
4684                if (tex_aux_math_engine_control(null, math_control_analyze_script_nucleus_box)) {
4685                    init = kernel_math_list(init);
4686                    if (init && node_type(init) == hlist_node) {
4687                        init = box_list(init);
4688                    }
4689                    while (init) {
4690                        switch (node_type(init)) {
4691                            case kern_node:
4692                            case glue_node:
4693                                init = node_next(init);
4694                                break;
4695                            case glyph_node:
4696                                if (tex_aux_fetch(init, "script box", &(data->fnt), &(data->chr))) {
4697                                    return init;
4698                                } else {
4699                                    goto NOTHING;
4700                                }
4701                            default:
4702                                goto NOTHING;
4703                        }
4704                    }
4705                }
4706                break;
4707        }
4708    }
4709  NOTHING:
4710    data->fnt = null;
4711    data->chr = null;
4712    return null;
4713}
4714
4715/*tex
4716
4717    These prescripts are kind of special. For instance, should top and bottom scripts be aligned?
4718    When there is are two top or two bottom, should we then just use the maxima? Watch out, the 
4719    implementation changed wrt \LUATEX. 
4720
4721*/
4722
4723static void tex_aux_get_math_sup_shifts(halfword target, halfword sup, halfword style, scaled *shift_up)
4724{
4725    if (has_noad_option_fixed_super_or_sub_script(target) || has_noad_option_fixed_super_and_sub_script(target)) { 
4726        *shift_up = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up);
4727    } else { 
4728        scaled clr = tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_up);
4729        scaled bot = tex_get_math_y_parameter_checked(style, math_parameter_superscript_bottom_min);
4730        if (*shift_up < clr) {
4731            *shift_up = clr;
4732        }
4733        clr = box_depth(sup) + bot;
4734        if (*shift_up < clr) {
4735            *shift_up = clr;
4736        }
4737    }
4738}
4739
4740static void tex_aux_get_math_sub_shifts(halfword target, halfword sub, halfword style, scaled *shift_down)
4741{
4742    if (has_noad_option_fixed_super_or_sub_script(target)) { 
4743        *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
4744    } else if (has_noad_option_fixed_super_and_sub_script(target)) { 
4745        *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
4746    } else { 
4747        scaled clr = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
4748        scaled top = tex_get_math_y_parameter_checked(style, math_parameter_subscript_top_max);
4749        if (*shift_down < clr) {
4750            *shift_down = clr;
4751        }
4752        clr = box_height(sub) - top;
4753        if (*shift_down < clr) {
4754            *shift_down = clr;
4755        }
4756    }
4757}
4758
4759static void tex_aux_get_math_sup_sub_shifts(halfword target, halfword sup, halfword sub, halfword style, scaled *shift_up, scaled *shift_down)
4760{
4761    if (has_noad_option_fixed_super_or_sub_script(target)) { 
4762        *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_down);
4763    } else if (has_noad_option_fixed_super_and_sub_script(target)) { 
4764        *shift_down = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
4765    } else { 
4766        scaled clr = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_shift_down);
4767        scaled gap = tex_get_math_y_parameter_checked(style, math_parameter_subscript_superscript_vgap);
4768        scaled bot = tex_get_math_y_parameter_checked(style, math_parameter_superscript_subscript_bottom_max);
4769        if (*shift_down < clr) {
4770            *shift_down = clr;
4771        }
4772        clr = gap - ((*shift_up - box_depth(sup)) - (box_height(sub) - *shift_down));
4773        if (clr > 0) {
4774            *shift_down += clr;
4775            clr = bot - (*shift_up - box_depth(sup));
4776            if (clr > 0) {
4777                *shift_up += clr;
4778                *shift_down -= clr;
4779            }
4780        }
4781    }
4782}
4783
4784static halfword tex_aux_combine_script(halfword target, halfword width, halfword pre, halfword post, halfword *k1, halfword *k2)
4785{
4786    *k1 = tex_new_kern_node(-(width + box_width(pre)), horizontal_math_kern_subtype);
4787    *k2 = tex_new_kern_node(width, horizontal_math_kern_subtype);
4788    tex_couple_nodes(*k1, pre);
4789    tex_couple_nodes(pre, *k2);
4790    if (post) {
4791        tex_couple_nodes(*k2, post);
4792    }
4793    post = tex_hpack(*k1, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
4794    tex_attach_attribute_list_copy(*k1, target);
4795    tex_attach_attribute_list_copy(*k2, target);
4796    tex_attach_attribute_list_copy(post, target);
4797    node_subtype(post) = math_pre_post_list;
4798    return post;
4799}
4800
4801 /*tex
4802
4803    The following steps are involved:
4804
4805    We look at the subscript character (_i) or first character in a list (_{ij}). We look at the
4806    superscript character (^i) or first character in a list (^{ij}).
4807
4808    Construct a superscript box |x|. The bottom of a superscript should never descend below the
4809    baseline plus one-fourth of the x-height.
4810
4811    Construct a sub/superscript combination box |x|, with the superscript offset by |delta|. When
4812    both subscript and superscript are present, the subscript must be separated from the superscript
4813    by at least four times |preset_rule_thickness|. If this condition would be violated, the
4814    subscript    moves down, after which both subscript and superscript move up so that the bottom
4815    of the superscript is at least as high as the baseline plus four-fifths of the x-height.
4816
4817    Now the horizontal shift for the superscript; the superscript is also to be shifted by |delta1|
4818    (the italic correction).
4819
4820    Construct a subscript box |x| when there is no superscript. When there is a subscript without
4821    a superscript, the top of the subscript should not exceed the baseline plus four-fifths of the
4822    x-height.
4823
4824    We start with some helpers that deal with the staircase kerns in \OPENTYPE\ math.
4825
4826*/
4827
4828/*tex
4829
4830    This function tries to find the kern needed for proper cut-ins. The left side doesn't move, but
4831    the right side does, so the first order of business is to create a staggered fence line on the
4832    left side of the right character.
4833
4834    If the fonts for the left and right bits of a mathkern are not both new-style fonts, then return
4835    a sentinel value meaning: please use old-style italic correction placement
4836
4837    This code is way to complex as it evolved stepwise and we wanted to keep the post scripts code
4838    more or less the same. but ... I'll redo it.
4839
4840*/
4841
4842static scaled tex_aux_math_kern_at(halfword fnt, int chr, int side, int value, int simple)
4843{
4844    /*tex We know that the character exists. */
4845    charinfo *ci = tex_get_charinfo(fnt, chr);
4846    if (ci->math) {
4847        scaled *kerns_heights;
4848        int n_of_kerns = tex_get_charinfo_math_kerns(ci, side);
4849        if (n_of_kerns == 0 || simple) {
4850            /*tex These are yet unscaled. */
4851            switch (side) {
4852                case top_left_kern:
4853                    return tex_char_top_left_kern_from_font(fnt, chr);
4854                case bottom_left_kern:
4855                    return tex_char_bottom_left_kern_from_font(fnt, chr);
4856                case top_right_kern:
4857                    return tex_char_top_right_kern_from_font(fnt, chr);
4858                case bottom_right_kern:
4859                    return tex_char_bottom_right_kern_from_font(fnt, chr);
4860                default:
4861                    return 0;
4862            }
4863        } else {
4864            switch (side) {
4865                case top_left_kern:
4866                    kerns_heights = ci->math->top_left_math_kern_array;
4867                    break;
4868                case bottom_left_kern:
4869                    kerns_heights = ci->math->bottom_left_math_kern_array;
4870                    break;
4871                case top_right_kern:
4872                    kerns_heights = ci->math->top_right_math_kern_array;
4873                    break;
4874                case bottom_right_kern:
4875                    kerns_heights = ci->math->bottom_right_math_kern_array;
4876                    break;
4877                default:
4878                    /*tex Not reached: */
4879                    kerns_heights = NULL;
4880                    return tex_confusion("math kern at");
4881            }
4882        }
4883        if (value < kerns_heights[0]) {
4884            return kerns_heights[1];
4885        } else {
4886            scaled kern = 0;
4887            for (int i = 0; i < n_of_kerns; i++) {
4888                scaled height = kerns_heights[i * 2];
4889                kern = kerns_heights[(i * 2) + 1];
4890                if (height > value) {
4891                    return kern;
4892                }
4893            }
4894            return kern;
4895        }
4896    } else {
4897        return 0;
4898    }
4899}
4900
4901static scaled tex_aux_find_math_kern_simple(halfword f, int c, int cmd, int *found)
4902{
4903    if (tex_aux_math_engine_control(f, math_control_staircase_kern) && tex_char_exists(f, c)) {
4904        /* todo: pre */
4905        scaled krn = tex_aux_math_kern_at(f, c, cmd == superscript_cmd ? top_right_kern : bottom_right_kern, 0, 1);
4906        *found = 1;
4907        return krn ? tex_aux_math_x_size_scaled(f, krn, lmt_math_state.size) : 0;
4908    } else {
4909        return MATH_KERN_NOT_FOUND;
4910    }
4911}
4912
4913inline static scaled tex_aux_max_left_kern_value(scaled *kerns, int n)
4914{
4915    if (kerns && n > 0) {
4916        scaled kern = 0;
4917        for (int i = 0; i < n; i++) {
4918            scaled value = kerns[(i * 2) + 1];
4919            if (value < kern) {
4920                kern = value;
4921            }
4922        }
4923        return -kern;
4924    } else {
4925        return 0;
4926    }
4927}
4928
4929static scaled tex_aux_math_left_kern(halfword fnt, int chr)
4930{
4931    charinfo *ci = tex_get_charinfo(fnt, chr);
4932    if (ci->math) {
4933        scaled top = 0;
4934        scaled bot = 0;
4935        { 
4936            scaled *a = ci->math->top_left_math_kern_array;
4937            halfword n = a ? tex_get_charinfo_math_kerns(ci, top_left_kern) : 0;
4938            if (n) { 
4939                top = tex_aux_max_left_kern_value(a, n);
4940            } else { 
4941                top = tex_char_top_left_kern_from_font(fnt, chr);
4942            }
4943        }
4944        { 
4945            scaled *a = ci->math->bottom_left_math_kern_array;
4946            halfword n = a ? tex_get_charinfo_math_kerns(ci, bottom_left_kern) : 0;
4947            if (n) { 
4948                bot = tex_aux_max_left_kern_value(a, n);
4949            } else { 
4950                bot = tex_char_bottom_left_kern_from_font(fnt, chr);
4951            }
4952        }
4953        return top > bot ? top : bot;
4954    } else {
4955        return 0;
4956    }
4957}
4958
4959/*
4960
4961inline static scaled tex_aux_max_right_kern_value(scaled *kerns, int n)
4962{
4963    if (kerns && n > 0) {
4964        scaled kern = 0;
4965        for (int i = 0; i < n; i++) {
4966            scaled value = kerns[(i * 2) + 1];
4967            if (value > kern) {
4968                kern = value;
4969            }
4970        }
4971        return kern;
4972    } else {
4973        return 0;
4974    }
4975}
4976
4977static scaled tex_aux_math_right_kern(halfword fnt, int chr)
4978{
4979    charinfo *ci = tex_get_charinfo(fnt, chr);
4980    if (ci->math) {
4981        scaled top = 0;
4982        scaled bot = 0;
4983        { 
4984            scaled *a = ci->math->top_right_math_kern_array;
4985            halfword n = a ? tex_get_charinfo_math_kerns(ci, top_right_kern) : 0;
4986            if (n) { 
4987                top = tex_aux_max_right_kern_value(a, n);
4988            } else { 
4989                top = tex_char_top_right_kern_from_font(fnt, chr);
4990            }
4991        }
4992        { 
4993            scaled *a = ci->math->bottom_right_math_kern_array;
4994            halfword n = a ? tex_get_charinfo_math_kerns(ci, bottom_right_kern) : 0;
4995            if (n) { 
4996                bot = tex_aux_max_right_kern_value(a, n);
4997            } else { 
4998                bot = tex_char_bottom_right_kern_from_font(fnt, chr);
4999            }
5000        }
5001        return top > bot ? top : bot;
5002    } else {
5003        return 0;
5004    }
5005}
5006*/
5007
5008static scaled tex_aux_find_math_kern(halfword l_f, int l_c, halfword r_f, int r_c, int cmd, scaled shift, int *found)
5009{
5010    if (tex_aux_math_engine_control(l_f, math_control_staircase_kern) &&
5011        tex_aux_math_engine_control(r_f, math_control_staircase_kern) &&
5012     /* tex_aux_has_opentype_metrics(l_f) && tex_aux_has_opentype_metrics(r_f) && */
5013        tex_char_exists(l_f, l_c) && tex_char_exists(r_f, r_c)) {
5014        scaled krn_l = 0;
5015        scaled krn_r = 0;
5016        scaled krn = 0;
5017        switch (cmd) {
5018            case superscript_cmd:
5019                /*tex bottom of superscript */
5020                {
5021                    scaled corr_height_top = tex_char_height_from_font(l_f, l_c);
5022                    scaled corr_height_bot = -tex_char_depth_from_font(r_f, r_c) + shift;
5023                    krn_l = tex_aux_math_kern_at(l_f, l_c, top_right_kern, corr_height_top, 0);
5024                    krn_r = tex_aux_math_kern_at(r_f, r_c, bottom_left_kern, corr_height_top, 0);
5025                    krn = krn_l + krn_r;
5026                    krn_l = tex_aux_math_kern_at(l_f, l_c, top_right_kern, corr_height_bot, 0);
5027                    krn_r = tex_aux_math_kern_at(r_f, r_c, bottom_left_kern, corr_height_bot, 0);
5028                }
5029                break;
5030            case subscript_cmd:
5031                /*tex top of subscript */
5032                {
5033                    scaled corr_height_top = tex_char_height_from_font(r_f, r_c) - shift;
5034                    scaled corr_height_bot = -tex_char_depth_from_font(l_f, l_c);
5035                    krn_l = tex_aux_math_kern_at(l_f, l_c, bottom_right_kern, corr_height_top, 0);
5036                    krn_r = tex_aux_math_kern_at(r_f, r_c, top_left_kern, corr_height_top, 0);
5037                    krn = krn_l + krn_r;
5038                    krn_l = tex_aux_math_kern_at(l_f, l_c, bottom_right_kern, corr_height_bot, 0);
5039                    krn_r = tex_aux_math_kern_at(r_f, r_c, top_left_kern, corr_height_bot, 0);
5040                }
5041                break;
5042            default:
5043                return tex_confusion("find math kern");
5044        }
5045        *found = 1;
5046        if ((krn_l + krn_r) < krn) {
5047            krn = krn_l + krn_r;
5048        }
5049        return krn ? tex_aux_math_x_size_scaled(l_f, krn, lmt_math_state.size) : 0;
5050    } else {
5051        return MATH_KERN_NOT_FOUND;
5052    }
5053}
5054
5055/* 
5056    $\dot{\mathscr{F}}_2         = G$ % handled by corner kerns
5057    $\mathscr{F}      _2         = G$ % handled by corner kerns
5058    $\mathscr{F}      _{\dot{x}} = G$ % the whatever case 
5059    $\dot{\mathscr{F}}_{\dot{x}} = G$ % handled by analyzed kerns 
5060*/
5061
5062static int tex_aux_get_sup_kern(halfword kernel, scriptdata *sup, scaled shift_up, scaled supshift, scaled *supkern, kernset *kerns)
5063{
5064    int found = 0;
5065    *supkern = MATH_KERN_NOT_FOUND;
5066    if (sup->whatever) {
5067        *supkern = tex_aux_find_math_kern_simple(glyph_font(kernel), glyph_character(kernel), superscript_cmd, &found);
5068        if (found) { 
5069            return found;
5070        }
5071    }
5072    if (sup->node) {
5073        *supkern = tex_aux_find_math_kern(glyph_font(kernel), glyph_character(kernel), sup->fnt, sup->chr, superscript_cmd, shift_up, &found);
5074        if (*supkern == MATH_KERN_NOT_FOUND) {
5075            *supkern = supshift;
5076        } else {
5077            if (*supkern) {
5078                tex_aux_trace_kerns(*supkern, "superscript kern", "regular");
5079            }
5080            *supkern += supshift;
5081        }
5082        return found;
5083    }
5084    if (kerns && kerns->topright) {
5085        *supkern = kerns->topright; 
5086        if (*supkern == MATH_KERN_NOT_FOUND) {
5087            *supkern = supshift;
5088        } else {
5089            if (*supkern) {
5090                tex_aux_trace_kerns(*supkern, "superscript kern", "kernset top right");
5091            }
5092            *supkern += supshift;
5093        }
5094        return found;
5095    }
5096    *supkern = supshift;
5097    return found;
5098}
5099
5100static int tex_aux_get_sub_kern(halfword kernel, scriptdata *sub, scaled shift_down, scaled subshift, scaled *subkern, kernset *kerns)
5101{
5102    int found = 0;
5103    *subkern = MATH_KERN_NOT_FOUND;
5104    if (sub->whatever) {
5105        *subkern = tex_aux_find_math_kern_simple(glyph_font(kernel), glyph_character(kernel), subscript_cmd, &found);
5106        if (found) { 
5107            return found;
5108        }
5109    }
5110    if (sub->node) {
5111        *subkern = tex_aux_find_math_kern(glyph_font(kernel), glyph_character(kernel), sub->fnt, sub->chr, subscript_cmd, shift_down, &found);
5112        if (*subkern == MATH_KERN_NOT_FOUND) {
5113            *subkern = subshift;
5114        } else {
5115            if (*subkern) {
5116                tex_aux_trace_kerns(*subkern, "subscript kern", "regular");
5117            }
5118            *subkern += subshift;
5119        }
5120        return found;
5121    }
5122    if (kerns && kerns->bottomright) {
5123        *subkern = kerns->bottomright; 
5124        if (*subkern == MATH_KERN_NOT_FOUND) {
5125            *subkern = subshift;
5126        } else {
5127            if (*subkern) {
5128                tex_aux_trace_kerns(*subkern, "subscript kern", "kernset bottom right");
5129            }
5130            *subkern += subshift;
5131        }
5132        return found;
5133    }
5134    *subkern = subshift;
5135    return found;
5136}
5137
5138/*tex
5139
5140    The code is quite ugly because these staircase kerns can only be calculated when we know the
5141    heights and depths but when we pack the pre/post scripts we already relatiev position them so
5142    we need to manipulate kerns. I need to figure out why we have slight rounding errors in the
5143    realignments of prescripts. Anyway, because prescripts are not really part of \TEX\ we have
5144    some freedom in dealing with them.
5145
5146    This code is now a bit too complex due to some (probably by now) redundant analysis so at some
5147    point I will rewrite it. Anyway, normally we don't end up in the next one because italic 
5148    correction already has been dealt with and thereby is zerood. In fact, if we end up here I need 
5149    to check why! 
5150
5151*/
5152
5153inline static scaled tex_aux_insert_italic_now(halfword target, halfword kernel, scaled italic)
5154{
5155    switch (node_type(noad_nucleus(target))) {
5156        case math_char_node:
5157        case math_text_char_node:
5158            {
5159                halfword fam = kernel_math_family(noad_nucleus(target));
5160                if (fam != unused_math_family) {
5161                    halfword fnt = tex_fam_fnt(fam, lmt_math_state.size);
5162                    if (! tex_aux_math_engine_control(fnt, math_control_apply_script_italic_kern)) {
5163                        /*tex We ignore the correction. */
5164                        italic = 0;
5165                    } else if (noad_subscr(target)) {
5166                        /*tex We will add the correction before the superscripts and/or primes. */
5167                    } else { 
5168                        /*tex We can add the correction the kernel and then forget about it. */
5169                        tex_aux_math_insert_italic_kern(kernel, italic, noad_nucleus(target), "scripts");
5170                        italic = 0;
5171                    }
5172                } else {
5173                    /*tex We have a weird case, so we ignore the correction. */
5174                    italic = 0;
5175                }
5176            }
5177            break;
5178    }
5179    return italic;
5180}
5181
5182inline static int tex_aux_raise_prime_composed(halfword target)
5183{
5184    int mainclass = -1 ; 
5185    /* maybe also mainclass */
5186    switch (node_type(target)) {
5187        case simple_noad: 
5188            mainclass = node_subtype(target);
5189            break;
5190        case radical_noad:
5191            mainclass = radical_noad_subtype;
5192            break;
5193        case fraction_noad:
5194            mainclass = fraction_noad_subtype;
5195            break;
5196        case accent_noad:
5197            mainclass = accent_noad_subtype; 
5198            break;
5199        case fence_noad:
5200            /* we could be more granular and do open / close nut for now assume symmetry */
5201            mainclass = fenced_noad_subtype;                
5202            break;
5203    }
5204    return mainclass >= 0 ? tex_math_has_class_option(mainclass, raise_prime_option) : 0;                
5205}
5206
5207static halfword tex_aux_shift_to_kern(halfword target, halfword box, scaled shift)
5208{
5209    halfword result; 
5210    if (box_source_anchor(box)) { 
5211        halfword kern = tex_new_kern_node(shift, vertical_math_kern_subtype);
5212        tex_attach_attribute_list_copy(kern, target);
5213        tex_couple_nodes(kern, box);
5214        result = tex_vpack(kern, 0, packing_additional, max_dimension, (singleword) math_direction_par, holding_none_option, NULL);
5215        tex_attach_attribute_list_copy(result, target);
5216        node_subtype(result) = math_scripts_list;
5217        box_shift_amount(result) = shift;
5218    } else { 
5219        box_shift_amount(box) = shift;
5220        result = box;
5221    }
5222    return result;
5223}
5224
5225static int tex_aux_singled(halfword target, scaledwhd kernelsize, int style, halfword single)
5226{
5227    if (single) {
5228        scaled ht = tex_get_math_y_parameter_default(style, math_parameter_superscript_snap, 0);
5229        scaled dp = tex_get_math_y_parameter_default(style, math_parameter_subscript_snap, 0);
5230        return ht > 0 && dp > 0 && kernelsize.ht <= ht && kernelsize.dp <= dp;       
5231    } else {
5232        return 0;
5233    }
5234}
5235
5236static void tex_aux_make_scripts(halfword target, halfword kernel, scaled italic, int style, scaled supshift, scaled subshift, scaled supdrop, kernset *kerns, halfword single)
5237{
5238    halfword result = null;
5239    halfword preresult = null;
5240    scaled prekern = 0;
5241    scaled primekern = 0;
5242    scaled shift_up = 0;
5243    scaled shift_down = 0;
5244    scaled prime_up = 0;
5245    scriptdata postsubdata = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0, .whatever = 0 };
5246    scriptdata postsupdata = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0, .whatever = 0 }; 
5247    scriptdata presubdata  = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0, .whatever = 0 };
5248    scriptdata presupdata  = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0, .whatever = 0 };
5249    scriptdata primedata   = { .node = null, .fnt = null_font, .chr = 0, .box = null, .kern = null, .slack = 0, .shifted = 0, .whatever = 0 };
5250    halfword maxleftkern = 0;
5251 // halfword maxrightkern = 0;
5252    scaled leftslack = 0;
5253    scaled rightslack = 0;
5254    scaledwhd kernelsize = { .wd = 0, .ht = 0, .dp = 0, .ic = 0 };
5255 // scaled primewidth = 0;
5256    scaled topovershoot = 0;
5257    scaled botovershoot = 0;
5258    int italicmultiplier = 1; /* This was a hard coded 2 so it needs more checking! */
5259    int splitscripts = 0;
5260    quarterword primestate = prime_unknown_location;
5261    /*tex 
5262        This features was added when MS and I found that the Latin Modern (and other) fonts have 
5263        rather badly configured script (calligraphic) shapes. There is no provision for proper 
5264        anchoring subscripts and superscripts can overlap with for instance wide accents especially 
5265        when there is not much granularity in them. For that we now register the overshoot of 
5266        accents and compensate for them here.
5267
5268        One assumption is that the shape is somewhat italic and that an overshoot makes it even 
5269        more so. The two factors default to zero, so it only works when the right parameters are 
5270        set.  
5271
5272        It's a mess. By adding more and more and also trying to be a bit like old \TEX\ we now have 
5273        too many kerns. 
5274
5275    */
5276    if (node_type(target) == accent_noad) {
5277        scaled top = tex_get_math_parameter_default(style, math_parameter_accent_top_overshoot, 0);
5278        scaled bot = tex_get_math_parameter_default(style, math_parameter_accent_bottom_overshoot, 0);
5279        topovershoot = scaledround(accent_top_overshoot(target) * top / 100.0);
5280        botovershoot = scaledround(accent_top_overshoot(target) * bot / 100.0);
5281    }
5282    /*tex
5283        So this is somewhat weird. We pass the kernel and also some italic and then act upon the 
5284        target again. This is a bit messy side effect of the transition from old to new fonts. We
5285        also have to make sure that we don't add the correction too soon, that is, before the 
5286        subscript. 
5287    */
5288    if (italic) {
5289        italic = tex_aux_insert_italic_now(target, kernel, italic);
5290    }
5291    /*tex 
5292        In some cases we need to split the scripts, for instance when we have fenced material that 
5293        can get split over lines. 
5294    */
5295    if (node_type(target) == simple_noad) { 
5296        switch (node_subtype(target)) { 
5297            case fenced_noad_subtype: 
5298                splitscripts = tex_math_has_class_option(fenced_noad_subtype, unpack_class_option);
5299                break;
5300            case ghost_noad_subtype: 
5301                splitscripts = has_noad_option_unpacklist(target);
5302                break;
5303        }
5304    }
5305    /*tex 
5306        When we have a single character we need to deal with kerning based on staircase kerns, but 
5307        we also can have explicit kerns defined with single characters, which is more a \CONTEXT\
5308        feature as it is not in \OPENTYPE\ fonts.
5309    */
5310    tex_aux_assign_new_hlist(target, kernel);
5311    kernelsize = tex_natural_hsizes(kernel, null, 0.0, 0, 0);
5312    if (kerns && kerns->dimensions) { 
5313        if (tex_aux_math_engine_control(kerns->font, math_control_ignore_kern_dimensions)) {
5314            /* hack for bad xits fence depth */
5315        } else { 
5316            if (kerns->height) {
5317                kernelsize.ht = kerns->height;
5318            }
5319            if (kerns->depth) {
5320                kernelsize.dp = kerns->depth;
5321            }
5322        }
5323    }
5324    switch (node_type(kernel)) {
5325        case glyph_node:
5326            postsubdata.node = tex_aux_analyze_script(noad_subscr(target), &postsubdata);
5327            postsupdata.node = tex_aux_analyze_script(noad_supscr(target), &postsupdata);
5328            primedata.node = tex_aux_analyze_script(noad_prime(target), &primedata);
5329            maxleftkern = tex_aux_math_left_kern(glyph_font(kernel), glyph_character(kernel));
5330         // maxrightkern = tex_aux_math_right_kern(glyph_font(kernel), glyph_character(kernel));
5331maxleftkern = tex_aux_math_x_scaled(maxleftkern, style);
5332            prime_up = 0; 
5333            shift_up = 0; 
5334            shift_down = 0;
5335            break;            
5336        default:
5337            if (has_noad_option_single(target) || tex_aux_singled(target, kernelsize, style, single)) {
5338                prime_up = 0; 
5339                shift_up = 0; 
5340                shift_down = 0;
5341            } else { 
5342                /*tex Used for optimizing accents. */
5343                kernelsize.ht -= supdrop; 
5344                /*tex These parameters are only applied in an assembly (and often some 0.5 .. 1.5 pt on 12pt). */
5345                prime_up = kernelsize.ht - tex_get_math_y_parameter_default(style, math_parameter_prime_shift_drop, 0);
5346                shift_up = kernelsize.ht - tex_get_math_y_parameter_checked(style, math_parameter_superscript_shift_drop);
5347                shift_down = kernelsize.dp + tex_get_math_y_parameter_checked(style, math_parameter_subscript_shift_drop);
5348            }
5349            break;
5350    }
5351    /*tex
5352        Next we're doing some analysis, needed because of all these parameters than control horizontal and vertical
5353        spacing. We start with primes.  
5354    */
5355    if (noad_prime(target)) {
5356        /* todo extra */
5357        scaled shift = tex_get_math_y_parameter_default(style, math_parameter_prime_shift_up, 0);
5358        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);
5359        scaled distance = tex_get_math_x_parameter_default(style, math_parameter_prime_space_after, 0);
5360     // scaled width = tex_get_math_x_parameter_default(style, math_parameter_prime_width, 0);
5361        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);
5362        box_shift_amount(primedata.box) -= prime_up ? prime_up : shift;
5363        box_shift_amount(primedata.box) -= scaledround(box_height(primedata.box) * raise / 100.0);
5364        kernel_math_list(noad_prime(target)) = null;
5365        tex_flush_node(noad_prime(target));
5366        noad_prime(target) = null;
5367        if (noad_supscr(target)) {
5368            primestate = prime_at_end_location;
5369        } else if (noad_subscr(target)) {
5370            primestate = prime_above_sub_location;
5371        } else {
5372            primestate = prime_at_begin_location;
5373        }
5374        if (distance) {
5375            tex_aux_append_hkern_to_box_list(primedata.box, distance, horizontal_math_kern_subtype, "prime distance");
5376        }
5377        primedata.slack = distance;
5378        switch (primestate) {
5379            /* [prime] [super/sub] */
5380            case prime_at_begin_location:
5381                {
5382                    /* supshift ? */
5383                    tex_aux_get_sup_kern(kernel, &primedata, shift_up, supshift, &primekern, kerns);
5384                    if (italic) {
5385                        /* why no injection */
5386                        primekern += italic;
5387                        italic = 0;
5388                    }
5389                }
5390                break;
5391            /* [prime/sub] [super] */
5392            case prime_above_sub_location:
5393                {
5394                    /* supshift ? */
5395                    tex_aux_get_sup_kern(kernel, &primedata, shift_up, supshift, &primekern, kerns);
5396                    if (italic) {
5397                        /* why no injection */
5398                        primekern += italic;
5399                        italic = 0;
5400                    }
5401                     if (primekern) {
5402                         tex_aux_prepend_hkern_to_box_list(primedata.box, primekern, math_shape_kern_subtype, "prime kern");
5403                         /* now width added */
5404                         primekern = 0; /* added */
5405                     }
5406                }
5407                break;
5408            /* [super/sub] [prime] */
5409            case prime_at_end_location:
5410                {
5411                    primekern = 0;
5412                }
5413                break;
5414        }
5415    }
5416    /*tex 
5417        Each of the scripts gets treated. Traditionally a super and subscript are looked at and 
5418        vercially spaced out together which in turn results in the staricase kerns needing that 
5419        information. Prescripts we handle differently: they are always aligned, so there the 
5420        maximum kern wins. 
5421    */
5422    postsupdata.shifted = noad_supscr(target) && has_noad_option_shiftedsupscript(target);
5423    postsubdata.shifted = noad_subscr(target) && has_noad_option_shiftedsubscript(target);
5424    presupdata.shifted = noad_supprescr(target) && has_noad_option_shiftedsupprescript(target);
5425    presubdata.shifted = noad_subprescr(target) && has_noad_option_shiftedsubprescript(target);
5426    /* 
5427        When we have a shifted super or subscript (stored in the prescripts) we don't need to kern
5428        the super and subscripts. What to do with the shifts?  
5429    */
5430    if (noad_supscr(target)) {
5431        halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_superscript_shift);
5432        postsupdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_superscript_space);
5433        postsupdata.slack += tex_get_math_x_parameter_checked(style, math_parameter_space_after_script);
5434        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);
5435        if (extra) {
5436            box_height(postsupdata.box) += extra;
5437            box_shift_amount(postsupdata.box) -= extra;
5438        }
5439        if (postsupdata.slack) {
5440            tex_aux_append_hkern_to_box_list(postsupdata.box, postsupdata.slack, horizontal_math_kern_subtype, "post sup slack");
5441        }
5442        kernel_math_list(noad_supscr(target)) = null;
5443        tex_flush_node(noad_supscr(target));
5444        noad_supscr(target) = null;
5445    }
5446    if (noad_subscr(target)) {
5447        halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_subscript_shift);
5448        postsubdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_subscript_space);
5449        postsubdata.slack += tex_get_math_x_parameter_checked(style, math_parameter_space_after_script);
5450        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);
5451        if (extra) {
5452            box_depth(postsubdata.box) += extra;
5453            box_shift_amount(postsubdata.box) += extra;
5454        }
5455        if (postsubdata.slack) {
5456            tex_aux_append_hkern_to_box_list(postsubdata.box, postsubdata.slack, horizontal_math_kern_subtype, "post sub slack");
5457        }
5458        kernel_math_list(noad_subscr(target)) = null;
5459        tex_flush_node(noad_subscr(target));
5460        noad_subscr(target) = null;
5461    }
5462    if (noad_supprescr(target)) {
5463        halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_superprescript_shift);
5464        presupdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_superprescript_space);
5465        presupdata.slack += tex_get_math_x_parameter_default(style, math_parameter_space_before_script, 0);
5466        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);
5467        if (maxleftkern) {
5468            tex_aux_append_hkern_to_box_list(presupdata.box, maxleftkern, math_shape_kern_subtype, "max left shape");
5469        }
5470        if (extra) {
5471            box_height(presupdata.box) += extra;
5472            box_shift_amount(presupdata.box) -= extra;
5473        }
5474        if (presupdata.slack) {
5475            tex_aux_prepend_hkern_to_box_list(presupdata.box, presupdata.slack, horizontal_math_kern_subtype, "pre sup slack");
5476        }
5477        kernel_math_list(noad_supprescr(target)) = null;
5478        tex_flush_node(noad_supprescr(target));
5479        noad_supprescr(target) = null;
5480    }
5481    if (noad_subprescr(target)) {
5482        halfword extra = tex_get_math_y_parameter_checked(style, math_parameter_extra_subprescript_shift);
5483        presubdata.slack = tex_get_math_x_parameter_checked(style, math_parameter_extra_subprescript_space);
5484        presubdata.slack += tex_get_math_x_parameter_default(style, math_parameter_space_before_script, 0);
5485        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);
5486        if (maxleftkern) {
5487            tex_aux_append_hkern_to_box_list(presubdata.box, maxleftkern, math_shape_kern_subtype, "max left shape");
5488        }
5489        if (extra) {
5490            box_depth(presubdata.box) += extra;
5491            box_shift_amount(presubdata.box) += extra;
5492        }
5493        if (presubdata.slack) {
5494            tex_aux_prepend_hkern_to_box_list(presubdata.box, presubdata.slack, horizontal_math_kern_subtype, "pre sub slack");
5495        }
5496        kernel_math_list(noad_subprescr(target)) = null;
5497        tex_flush_node(noad_subprescr(target));
5498        noad_subprescr(target) = null;
5499    }
5500    /*tex 
5501        When we're here, the kerns are in the boxes. We now register the state of scripts in the 
5502        noad for (optional) later usage. 
5503    */
5504    if (presupdata.box) {
5505        noad_script_state(target) |= pre_super_script_state;
5506    }
5507    if (presubdata.box) {
5508        noad_script_state(target) |= pre_sub_script_state;
5509    }
5510    if (postsupdata.box) {
5511        noad_script_state(target) |= post_super_script_state;
5512    }
5513    if (postsubdata.box) {
5514        noad_script_state(target) |= post_sub_script_state;
5515    }
5516    if (primedata.box) {
5517        noad_script_state(target) |= prime_script_state;
5518    }
5519    /* */
5520    if (primestate == prime_above_sub_location) {
5521        rightslack = box_width(primedata.box) > box_width(postsubdata.box) ? primedata.slack : postsubdata.slack;
5522    } else if (postsupdata.box) {
5523        if (postsubdata.box) {
5524            /* todo: take deltas */
5525            rightslack = box_width(postsupdata.box) > box_width(postsubdata.box) ? postsupdata.slack : postsubdata.slack;
5526        } else {
5527            rightslack = postsupdata.slack;
5528        }
5529    } else if (postsubdata.box) {
5530        rightslack = postsubdata.slack;
5531    }
5532
5533    if (primestate == prime_above_sub_location) {
5534        halfword list = noad_new_hlist(target);
5535        if (list) {
5536            /*tex We want to keep the size for tracing! */
5537            halfword overshoot = box_width(primedata.box) - box_width(postsubdata.box);
5538            halfword primebox = tex_hpack(primedata.box, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
5539            tex_attach_attribute_list_copy(primebox, primedata.box);
5540            box_width(primebox) = 0; 
5541            tex_couple_nodes(tex_tail_of_node_list(list), primebox);
5542            primedata.box = null; 
5543            if (overshoot > 0) { 
5544                tex_aux_append_hkern_to_box_list(postsubdata.box, overshoot, math_shape_kern_subtype, "prime overshoot kern");
5545            }
5546        } else {
5547            list = primedata.box;
5548        }
5549        noad_new_hlist(target) = list;
5550    }
5551
5552    if (presupdata.box) {
5553        if (presubdata.box) {
5554            /* todo: take deltas */
5555            leftslack = box_width(presupdata.box) > box_width(presubdata.box) ? presupdata.slack : presubdata.slack;
5556        } else {
5557            leftslack = presupdata.slack;
5558        }
5559    } else if (presubdata.box) {
5560        leftslack = presubdata.slack;
5561    }
5562    switch (primestate) {
5563        case prime_at_begin_location:
5564            kernelsize.wd += box_width(primedata.box);
5565            break;
5566        case prime_above_sub_location:
5567            /* only excess */
5568            break;
5569    }
5570    if (postsupdata.box || postsubdata.box) {
5571        /*tex
5572            The post scripts determine the shifts. An option can be to use the max of pre/post.
5573        */
5574        scaled supkern = 0;
5575        scaled subkern = 0;
5576        if (! splitscripts) {
5577            if (presupdata.box) {
5578                prekern = box_width(presupdata.box);
5579                postsupdata.box = tex_aux_combine_script(target, kernelsize.wd, presupdata.box, postsupdata.box, &presupdata.kern, &postsupdata.kern);
5580                presupdata.box = null;
5581            }
5582            if (presubdata.box) {
5583                // test: what with negative extra kerns and what with a negative width
5584                if (box_width(presubdata.box) > prekern) {
5585                    prekern = box_width(presubdata.box);
5586                }
5587                postsubdata.box = tex_aux_combine_script(target, kernelsize.wd, presubdata.box, postsubdata.box, &presubdata.kern, &postsubdata.kern);
5588                presubdata.box = null;
5589            }
5590        }
5591        /*tex 
5592            We want to retain the kern because it is a visual thing but it could be an option to 
5593            only add the excess over the shift. We're talking tiny here. 
5594
5595            We could be clever and deal with combinations of shifted but lets play safe and let
5596            the user worry about it. The sub index always wins. 
5597        */
5598        if (postsubdata.box && postsupdata.shifted) {
5599            halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_subscript_shift_distance);
5600            halfword amount = box_width(postsupdata.box) + shift;
5601            tex_aux_prepend_hkern_to_box_list(postsubdata.box, amount, horizontal_math_kern_subtype, "post shifted");
5602        } else if (postsupdata.box && postsubdata.shifted) {
5603            halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_superscript_shift_distance);
5604            halfword amount = box_width(postsubdata.box) + shift;
5605            tex_aux_prepend_hkern_to_box_list(postsupdata.box, amount, horizontal_math_kern_subtype, "post shifted");
5606        }
5607        if (presubdata.box && presupdata.shifted) {
5608            halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_subprescript_shift_distance);
5609            halfword amount = box_width(presupdata.box) + shift;
5610            tex_aux_append_hkern_to_box_list(presubdata.box, amount, horizontal_math_kern_subtype, "pre shifted");
5611        } else if (presupdata.box && presubdata.shifted) {
5612            halfword shift = tex_get_math_x_parameter_checked(style, math_parameter_superprescript_shift_distance);
5613            halfword amount = box_width(presubdata.box) + shift;
5614            tex_aux_append_hkern_to_box_list(presupdata.box, amount, horizontal_math_kern_subtype, "pre shifted");
5615        }
5616        /* */
5617        if (postsupdata.box) {
5618            /* Do we still want to chain these sups or should we combine it? */
5619            tex_aux_get_math_sup_shifts(target, postsupdata.box, style, &shift_up); /* maybe only in else branch */
5620            if (postsubdata.box) {
5621                tex_aux_get_math_sup_sub_shifts(target, postsupdata.box, postsubdata.box, style, &shift_up, &shift_down);
5622                tex_aux_get_sup_kern(kernel, &postsupdata, shift_up, supshift, &supkern, kerns);
5623                tex_aux_get_sub_kern(kernel, &postsubdata, shift_down, subshift, &subkern, kerns);
5624                if (primestate == prime_at_begin_location) {
5625                    primekern += supkern ;
5626                    subkern = 0;
5627                    supkern = 0;
5628                } else { 
5629                    if (supkern) {
5630                        tex_aux_prepend_hkern_to_box_list(postsupdata.box, supkern, math_shape_kern_subtype, "post sup shape");
5631                    }
5632                    if (subkern) {
5633                        tex_aux_prepend_hkern_to_box_list(postsubdata.box, subkern, math_shape_kern_subtype, "post sub shape");
5634                    }
5635                }
5636                if (italic) {
5637                    tex_aux_prepend_hkern_to_box_list(postsupdata.box, italic, italic_kern_subtype, "italic");
5638                }
5639                if (presubdata.kern) {
5640                    kern_amount(presubdata.kern) += -subkern;
5641                    kern_amount(postsubdata.kern) += subkern;
5642                }
5643                if (presupdata.kern) {
5644                    /* italic needs checking */
5645                    kern_amount(presupdata.kern) += -supkern - italicmultiplier * italic;
5646                    kern_amount(postsupdata.kern) += supkern + italicmultiplier * italic;
5647                }
5648                {
5649                    halfword kern = tex_new_kern_node((shift_up - box_depth(postsupdata.box)) - (box_height(postsubdata.box) - shift_down), vertical_math_kern_subtype);
5650                    tex_attach_attribute_list_copy(kern, target);
5651                    tex_couple_nodes(postsupdata.box, kern);
5652                    tex_couple_nodes(kern, postsubdata.box);
5653                    result = tex_vpack(postsupdata.box, 0, packing_additional, max_dimension, (singleword) math_direction_par, holding_none_option, NULL);
5654                    tex_attach_attribute_list_copy(result, target);
5655                    node_subtype(result) = math_scripts_list;
5656                    box_shift_amount(result) = shift_down;
5657                }
5658            } else {
5659                tex_aux_get_sup_kern(kernel, &postsupdata, shift_up, supshift, &supkern, kerns);
5660                if (primestate == prime_at_begin_location) {
5661                    primekern += supkern ;
5662                    supkern = 0;
5663                } else if (supkern) {
5664                    tex_aux_prepend_hkern_to_box_list(postsupdata.box, supkern, math_shape_kern_subtype, "post sup shape");
5665                }
5666                result = tex_aux_shift_to_kern(target, postsupdata.box, -shift_up);
5667                if (presupdata.kern) {
5668                    kern_amount(presupdata.kern) += -supkern - subkern - italicmultiplier * italic;
5669                    kern_amount(postsupdata.kern) += supkern + subkern + italicmultiplier * italic;
5670                }
5671            }
5672        } else {
5673            tex_aux_get_math_sub_shifts(target, postsubdata.box, style, &shift_down);
5674            tex_aux_get_sub_kern(kernel, &postsubdata, shift_down, subshift, &subkern, kerns);
5675            if (primestate == prime_at_begin_location) {
5676                subkern = 0;
5677            } else if (subkern) {
5678                tex_aux_prepend_hkern_to_box_list(postsubdata.box, subkern, math_shape_kern_subtype, "post sub shape");
5679            }
5680            result = tex_aux_shift_to_kern(target, postsubdata.box, shift_down);
5681            if (presubdata.kern) {
5682                kern_amount(presubdata.kern) += -subkern;
5683                kern_amount(postsubdata.kern) += subkern;
5684            }
5685        }
5686        /* */
5687        if (! splitscripts) {
5688            if (topovershoot) {
5689                /* todo: tracing */
5690                if (noad_script_state(target) & pre_super_script_state) {
5691                    kern_amount(postsubdata.kern) -= topovershoot;
5692                    kern_amount(postsupdata.kern) -= topovershoot;
5693                }
5694                if (noad_script_state(target) & post_sub_script_state) {
5695                    kern_amount(presupdata.kern) += topovershoot;
5696                }
5697            }
5698            if (botovershoot) { 
5699                /* todo: tracing, yet untested */
5700                if (noad_script_state(target) & pre_sub_script_state) {
5701                    kern_amount(presubdata.kern) -= botovershoot;
5702                    kern_amount(presupdata.kern) -= botovershoot;
5703                }
5704                if (noad_script_state(target) & post_sub_script_state) {
5705                    kern_amount(presubdata.kern) += botovershoot;
5706                }
5707            }
5708            goto PICKUP;
5709        }
5710    }
5711    if (presubdata.box) {
5712        if (presupdata.box) {
5713            /* Do we still want to chain these sups or should we combine it? */
5714            tex_aux_get_math_sup_shifts(target, presupdata.box, style, &shift_up);
5715            tex_aux_get_math_sup_sub_shifts(target, presupdata.box, presubdata.box, style, &shift_up, &shift_down);
5716            prekern = box_width(presupdata.box);
5717            // test: what with negative extra kerns and what with a negative width
5718            if (! splitscripts) {
5719                if (box_width(presubdata.box) > prekern) {
5720                    prekern = box_width(presubdata.box);
5721                }
5722                presupdata.box = tex_aux_combine_script(target, kernelsize.wd, presupdata.box, null, &presupdata.kern, &postsupdata.kern);
5723                presubdata.box = tex_aux_combine_script(target, kernelsize.wd, presubdata.box, null, &presubdata.kern, &postsubdata.kern);
5724            }
5725            {
5726                halfword k = tex_new_kern_node((shift_up - box_depth(presupdata.box)) - (box_height(presubdata.box) - shift_down), vertical_math_kern_subtype);
5727                tex_attach_attribute_list_copy(k, target);
5728                tex_couple_nodes(presupdata.box, k);
5729                tex_couple_nodes(k, presubdata.box);
5730                preresult = tex_vpack(presupdata.box, 0, packing_additional, max_dimension, (singleword) math_direction_par, holding_none_option, NULL);
5731                tex_attach_attribute_list_copy(preresult, target);
5732                node_subtype(preresult) = math_scripts_list;
5733                box_shift_amount(preresult) = shift_down;
5734            }
5735        } else {
5736            tex_aux_get_math_sub_shifts(target, presubdata.box, style, &shift_down);
5737            if (! splitscripts) {
5738                prekern = box_width(presubdata.box);
5739                presubdata.box = tex_aux_combine_script(target, kernelsize.wd, presubdata.box, null, &presubdata.kern, &postsubdata.kern);
5740            }
5741            box_shift_amount(presubdata.box) = shift_down;
5742            preresult = presubdata.box;
5743        }
5744    } else if (presupdata.box) {
5745        tex_aux_get_math_sup_shifts(target, presupdata.box, style, &shift_up);
5746        if (! splitscripts) {
5747            prekern = box_width(presupdata.box);
5748            presupdata.box = tex_aux_combine_script(target, kernelsize.wd, presupdata.box, null, &presupdata.kern, &postsupdata.kern);
5749        }
5750        box_shift_amount(presupdata.box) = -shift_up;
5751        preresult = presupdata.box;
5752    }
5753  PICKUP:
5754    if (primestate == prime_at_begin_location) {
5755        halfword list = noad_new_hlist(target);
5756        if (primekern) {
5757            tex_aux_prepend_hkern_to_box_list(primedata.box, primekern, math_shape_kern_subtype, "prime");
5758        }
5759        if (list) {
5760            tex_couple_nodes(tex_tail_of_node_list(list), primedata.box);
5761        } else {
5762            list = primedata.box;
5763        }
5764        noad_new_hlist(target) = list;
5765    }
5766    if (splitscripts) {
5767        halfword list = noad_new_hlist(target);
5768        if (preresult) {
5769            if (list) {
5770                tex_couple_nodes(preresult, list);
5771            }
5772            list = preresult;
5773        }
5774        if (result) {
5775            if (list) {
5776                tex_couple_nodes(tex_tail_of_node_list(list), result);
5777            } else {
5778                list = result;
5779            }
5780        }
5781        noad_new_hlist(target) = list;
5782    } else {
5783        if (preresult) {
5784            result = preresult;
5785        }
5786        if (prekern) {
5787            /* must become horizontal kern */
5788            halfword list = tex_aux_prepend_hkern_to_new_hlist(target, prekern, horizontal_math_kern_subtype, "pre compensation");
5789            tex_couple_nodes(tex_tail_of_node_list(list), result);
5790        } else if (noad_new_hlist(target)) {
5791            tex_couple_nodes(tex_tail_of_node_list(noad_new_hlist(target)), result);
5792        } else {
5793            noad_new_hlist(target) = result;
5794        }
5795    }
5796    if (primestate == prime_at_end_location) {
5797        tex_couple_nodes(tex_tail_of_node_list(result), primedata.box);
5798        rightslack = primedata.slack;
5799    }
5800    if (math_slack_mode_par > 0) {
5801        noad_left_slack(target) = leftslack;
5802        noad_right_slack(target) = rightslack;
5803        if (tracing_math_par >= 2) {
5804            tex_begin_diagnostic();
5805            tex_print_format("[math: script slack, left %p, right %p]", leftslack, rightslack);
5806            tex_end_diagnostic();
5807        }
5808    }
5809}
5810
5811/*tex
5812
5813    The |make_left_right| function constructs a left or right delimiter of the required size and
5814    returns the value |open_noad| or |close_noad|. The |left_noad_side| and |right_noad_side| will
5815    both be based on the original |style|, so they will have consistent sizes.
5816
5817*/
5818
5819// inline static int tex_aux_is_extensible(halfword result)
5820// {
5821//     if (result) {
5822//         switch (node_type(result)) { 
5823//             case hlist_node:
5824//             case vlist_node:
5825//                 switch (node_subtype(result)) { 
5826//                     case math_h_delimiter_list:
5827//                     case math_v_delimiter_list:
5828//                         return 1;
5829//                 }
5830//         }
5831//     }
5832//     return 0;
5833// }
5834
5835static halfword tex_aux_make_left_right(halfword target, int style, scaled max_d, scaled max_h, int size, delimiterextremes *extremes)
5836{
5837    halfword tmp;
5838    scaled ic = 0;
5839    int stack = 0;
5840    halfword mainclass = get_noad_main_class(target);
5841    halfword leftclass = get_noad_left_class(target);
5842    halfword rightclass = get_noad_right_class(target);
5843    scaled height = tex_aux_math_given_y_scaled(noad_height(target));
5844    scaled depth = tex_aux_math_given_y_scaled(noad_depth(target));
5845    int leftoperator = node_type(target) == fence_noad && node_subtype(target) == left_operator_side;
5846    max_h += fence_top_overshoot(target);
5847    max_d += fence_bottom_overshoot(target);
5848    if (extremes) { 
5849        extremes->tfont = null_font;
5850        extremes->bfont = null_font;
5851        extremes->tchar = 0;
5852        extremes->tchar = 0;
5853        extremes->height = 0;
5854        extremes->depth = 0;
5855    }
5856    if (has_noad_option_scale(target)) {
5857        height = tex_aux_math_math_scale(height);
5858        depth = tex_aux_math_math_scale(depth);
5859    }
5860    tex_aux_set_current_math_size(style);
5861    if (height || depth || has_noad_option_exact(target)) {
5862        halfword lst;
5863        scaled delta = height + depth;
5864        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, null);
5865        /* do extremes here */
5866        noad_italic(target) = ic;
5867        /*tex
5868            Beware, a stacked delimiter has a shift but no corrected height/depth (yet).
5869        */
5870        /* or do we need has_noad_option_check(target) */
5871        if (! stack && has_noad_option_exact(target)) {
5872            if (extremes && extremes->height < height) {
5873                height = extremes->height;
5874            }
5875            if (extremes && extremes->depth < depth) {
5876                depth = extremes->depth;
5877            }
5878        }
5879        if (stack) {
5880            box_shift_amount(tmp) = depth;
5881        }
5882        if (has_noad_option_exact(target)) {
5883            height = box_height(tmp) - box_shift_amount(tmp);
5884            depth = box_depth(tmp) + box_shift_amount(tmp);
5885        }
5886        if (has_noad_option_axis(target)) {
5887         // if (has_noad_option_noaxis(target) && tex_aux_is_extensible(tmp)) {
5888            if (has_noad_option_noaxis(target) && stack) {
5889                /*tex A sort of special case: see sized integrals in ctx examples. */
5890            } else { 
5891                halfword axis = tex_aux_math_axis(size);
5892                height += axis;
5893                depth -= axis;
5894                box_shift_amount(tmp) -= axis;
5895            }
5896        }
5897        lst = tex_new_node(hlist_node, 0);
5898        tex_attach_attribute_list_copy(lst, target);
5899        box_dir(lst) = dir_lefttoright ;
5900        box_height(lst) = height;
5901        box_depth(lst) = depth;
5902        box_width(lst) = box_width(tmp);
5903        box_list(lst) = tmp;
5904        tmp = lst;
5905    } else {
5906        int axis = ! has_noad_option_noaxis(target);
5907        scaled delta = 0;
5908        if (leftoperator && has_noad_option_auto(target)) {
5909             /*tex Todo: option for skipping this. */
5910             if (style < text_style) {
5911                 scaled s = tex_get_math_y_parameter_checked(style, math_parameter_operator_size); 
5912                 if (s > max_h + max_d) {
5913                     max_h = scaledround(s / 2.0);
5914                     max_d = max_h;
5915                     delta = max_h + max_d; 
5916                 }
5917             }
5918        }
5919        if (has_noad_option_auto_middle(target)) {
5920            delta = max_h + max_d; 
5921        } else if (! delta) { 
5922            delta = tex_aux_get_delimiter_height(max_h, max_d, axis, size, style); // todo: pass scaled axis
5923        }
5924        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, null);
5925    }
5926    /* delimiter is wiped */
5927    noad_height(target) = height;
5928    noad_depth(target) = depth;
5929    fence_delimiter_list(target) = null;
5930    noad_italic(target) = ic;
5931    /* */
5932    if (noad_source(target)) {
5933        box_source_anchor(tmp) = noad_source(target);
5934     // box_anchor(tmp) = left_origin_anchor;
5935        tex_set_box_geometry(tmp, anchor_geometry);
5936    }
5937    /* */
5938    if (leftoperator) {
5939        halfword s = tex_new_node(sub_box_node, 0);
5940        kernset kerns;
5941        tex_math_wipe_kerns(&kerns);
5942        tex_flush_node_list(noad_supscr(target));
5943        tex_flush_node_list(noad_subscr(target));
5944        tex_flush_node_list(noad_nucleus(target));
5945        if (kernel_math_list(fence_delimiter_top(target))) {
5946            noad_supscr(target) = fence_delimiter_top(target);
5947            fence_delimiter_top(target) = null;
5948        }
5949        if (kernel_math_list(fence_delimiter_bottom(target))) {
5950            noad_subscr(target) = fence_delimiter_bottom(target);
5951            fence_delimiter_bottom(target) = null;
5952        }
5953        kernel_math_list(s) = tmp;
5954        noad_nucleus(target) = s;
5955        /* maybe elsewhere as the above case */
5956        if (extremes && extremes->tfont) { 
5957            if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_top_kern_class_option)) {  
5958                kerns.topright = tex_aux_math_x_size_scaled(extremes->tfont, tex_char_top_right_kern_from_font(extremes->tfont, extremes->tchar), size);
5959            }
5960            if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_bottom_kern_class_option)) {  
5961                kerns.bottomright = tex_aux_math_x_size_scaled(extremes->bfont, tex_char_bottom_right_kern_from_font(extremes->bfont, extremes->bchar), size);
5962            }
5963            if (tex_math_has_class_option(fenced_noad_subtype, prefer_delimiter_dimensions_class_option)) {  
5964                kerns.height = extremes->height;
5965                kerns.depth = extremes->depth;
5966                kerns.dimensions = 1;
5967                kerns.font = extremes->tfont;
5968                kerns.character = extremes->tchar;
5969            }
5970        }
5971        /* returns italic, so maybe noad_italic(target) = ... */
5972        tex_aux_make_op(target, style, size, ic, limits_unknown_mode, &kerns);
5973        /* otherwise a leak: */
5974        kernel_math_list(s) = null;
5975        tex_flush_node(s);
5976    } else {
5977        tex_aux_assign_new_hlist(target, tmp);
5978    }
5979    /* */
5980    switch (node_subtype(target)) {
5981        case left_fence_side:
5982            if (leftclass != unset_noad_class) {
5983                return leftclass; 
5984            } else if (mainclass != unset_noad_class) {
5985                return mainclass;
5986            } else { 
5987                return open_noad_subtype;
5988            }
5989        case middle_fence_side:
5990            if (mainclass != unset_noad_class) {
5991                return mainclass;
5992            } else { 
5993                return middle_noad_subtype;
5994            }
5995        case right_fence_side:
5996            if (rightclass != unset_noad_class) {
5997                return rightclass; 
5998            } else if (mainclass != unset_noad_class) {
5999                return mainclass;
6000            } else { 
6001                return close_noad_subtype;
6002            }
6003        case left_operator_side:
6004            if (leftclass != unset_noad_class) {
6005                return leftclass; 
6006            } else if (mainclass != unset_noad_class) {
6007                return mainclass;
6008            } else { 
6009                return operator_noad_subtype;
6010            }
6011        default:
6012            if (mainclass != unset_noad_class) {
6013                return mainclass;
6014            } else { 
6015                /*tex So one can best set the class! */
6016                return ordinary_noad_subtype;
6017            }
6018    }
6019}
6020
6021inline static int tex_aux_fallback_math_spacing_class(halfword style, halfword mathclass)
6022{
6023    unsigned parent = (unsigned) count_parameter(first_math_class_code + mathclass);
6024    switch (style) {
6025        case display_style:       case cramped_display_style:       return (parent >> 24) & 0xFF;
6026        case text_style:          case cramped_text_style:          return (parent >> 16) & 0xFF;
6027        case script_style:        case cramped_script_style:        return (parent >>  8) & 0xFF;
6028        case script_script_style: case cramped_script_script_style: return (parent >>  0) & 0xFF;
6029        default:                                                    return 0;
6030    }
6031}
6032
6033static halfword tex_aux_math_spacing_glue(halfword ltype, halfword rtype, halfword style, scaled mmu)
6034{
6035    halfword c = tex_to_math_spacing_parameter(ltype, rtype);
6036    halfword s = c;
6037    for (int i = 1; i <= 2; i++) {
6038        if (s >= 0) {
6039            halfword d = 0;
6040            halfword x = tex_get_math_parameter(style, s, &d);
6041            if (x) {
6042                switch (d) {
6043                    case no_val_level:
6044                        break;
6045                    case dimension_val_level:
6046                        if (x) {
6047                            x = tex_aux_math_dimension(x, inter_math_skip_glue, c);
6048                            if (tracing_math_par >= 2) {
6049                                tex_begin_diagnostic();
6050                                tex_print_format("[math: inter atom kern, left %n, right %n, resolved %i, amount %p]", ltype, rtype, s, kern_amount(x));
6051                                tex_end_diagnostic();
6052                            }
6053                            return x;
6054                        }
6055                        goto NONE;
6056                    case glue_val_level:
6057                        if (! tex_glue_is_zero(x)) {
6058                            x = tex_aux_math_glue(x, inter_math_skip_glue, c);
6059                            if (tracing_math_par >= 2) {
6060                                tex_begin_diagnostic();
6061                                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));
6062                                tex_end_diagnostic();
6063                            }
6064                            return x;
6065                        }
6066                        goto NONE;
6067                    case muglue_val_level:
6068                        if (! tex_math_glue_is_zero(x)) {
6069                            x = tex_aux_math_muglue(x, inter_math_skip_glue, mmu, c, style);
6070                            if (tracing_math_par >= 2) {
6071                                tex_begin_diagnostic();
6072                                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));
6073                                tex_end_diagnostic();
6074                            }
6075                            return x;
6076                        }
6077                        goto NONE;
6078                    default:
6079                        if (tracing_math_par >= 2) {
6080                            tex_begin_diagnostic();
6081                            tex_print_format("[math: inter atom (mu) glue, left %n, right %n, resolved %i, unset]", ltype, rtype, s);
6082                            tex_end_diagnostic();
6083                        }
6084                        goto NONE;
6085                }
6086            }
6087            /* try again */
6088            {
6089                halfword lparent = tex_aux_fallback_math_spacing_class(style, ltype);
6090                halfword rparent = tex_aux_fallback_math_spacing_class(style, rtype);
6091                /*tex Let's try the parents (one level). */
6092                if (lparent != ltype || rparent != rtype) {
6093                    s = tex_to_math_spacing_parameter(lparent, rtype);
6094                    if (tex_has_math_parameter(style, s)) {
6095                        goto FOUND;
6096                    }
6097                    s = tex_to_math_spacing_parameter(ltype, rparent);
6098                    if (tex_has_math_parameter(style, s)) {
6099                        goto FOUND;
6100                    }
6101                    s = tex_to_math_spacing_parameter(lparent, rparent);
6102                    if (tex_has_math_parameter(style, s)) {
6103                        goto FOUND;
6104                    }
6105                }
6106                /*tex We fall back on the |all| classes. */
6107                s = tex_to_math_spacing_parameter(ltype, math_all_class);
6108                if (tex_has_math_parameter(style, s)) {
6109                    goto FOUND;
6110                }
6111                s = tex_to_math_spacing_parameter(math_all_class, rtype);
6112                if (tex_has_math_parameter(style, s)) {
6113                    goto FOUND;
6114                }
6115                s = tex_to_math_spacing_parameter(lparent, math_all_class);
6116                if (tex_has_math_parameter(style, s)) {
6117                    goto FOUND;
6118                }
6119                s = tex_to_math_spacing_parameter(math_all_class, rparent);
6120                if (tex_has_math_parameter(style, s)) {
6121                    goto FOUND;
6122                }
6123                /*tex Now we're lost. */
6124                if (tracing_math_par >= 2) {
6125                    tex_begin_diagnostic();
6126                    tex_print_format("[math: inter atom fallback, left %n, right %n, left parent %n, right parent %n, not resolved]", ltype, rtype, lparent, rparent);
6127                    tex_end_diagnostic();
6128                }
6129                goto NONE;
6130             FOUND:
6131                if (tracing_math_par >= 2) {
6132                    tex_begin_diagnostic();
6133                    tex_print_format("[math: inter atom fallback, left %n, right %n, left parent %n, right parent %n, resolved %i]", ltype, rtype, lparent, rparent, s);
6134                    tex_end_diagnostic();
6135                }
6136            }
6137        } else {
6138         /* tex_confusion("math atom spacing"); */
6139            goto NONE;
6140        }
6141    }
6142  NONE:
6143    if (math_spacing_mode_par && c >= 0) {
6144        if (math_spacing_mode_par == 1 && (ltype == math_begin_class || rtype == math_end_class)) { 
6145            return null;
6146        } else {
6147            return tex_aux_math_dimension(0, inter_math_skip_glue, c);
6148        }
6149    } else {
6150        return null;
6151    }
6152}
6153
6154inline static int tex_aux_fallback_math_ruling_class(halfword style, halfword mathclass)
6155{
6156    unsigned parent = (unsigned) count_parameter(first_math_atom_code + mathclass);
6157    switch (style) {
6158        case display_style:       case cramped_display_style:       return (parent >> 24) & 0xFF;
6159        case text_style:          case cramped_text_style:          return (parent >> 16) & 0xFF;
6160        case script_style:        case cramped_script_style:        return (parent >>  8) & 0xFF;
6161        case script_script_style: case cramped_script_script_style: return (parent >>  0) & 0xFF;
6162        default:                                                    return 0;
6163    }
6164}
6165
6166static halfword tex_aux_math_ruling(halfword ltype, halfword rtype, halfword style)
6167{
6168    halfword c = tex_to_math_rules_parameter(ltype, rtype);
6169    halfword s = c;
6170    for (int i = 1; i <= 2; i++) {
6171        if (s >= 0) {
6172            halfword x = tex_get_math_parameter(style, s, NULL);
6173            if (x != MATHPARAMDEFAULT) {
6174                return x;
6175            } else {
6176                halfword lparent = tex_aux_fallback_math_ruling_class(style, ltype);
6177                halfword rparent = tex_aux_fallback_math_ruling_class(style, rtype);
6178                if (lparent != ltype || rparent != rtype) {
6179                    s = tex_to_math_rules_parameter(lparent, rparent);
6180                } else {
6181                    return MATHPARAMDEFAULT;
6182                }
6183            }
6184        } else {
6185            return MATHPARAMDEFAULT;
6186        }
6187    }
6188    return MATHPARAMDEFAULT;
6189}
6190
6191/*tex 
6192    Used in |texmaincontrol| and |texscanning|: 
6193*/
6194
6195halfword tex_math_spacing_glue(halfword ltype, halfword rtype, halfword style)
6196{
6197 // halfword mu = tex_get_math_quad_size_scaled(lmt_math_state.size); /* scales by math x scale */
6198    halfword mu = tex_get_math_quad_size_unscaled(lmt_math_state.size); /* scales by math x scale */
6199    halfword sg = tex_aux_math_spacing_glue(ltype, rtype, style, mu); 
6200    if (node_type(sg) == glue_node) {
6201        tex_add_glue_option(sg, glue_option_no_auto_break);
6202    }
6203    return sg;
6204}
6205
6206/*tex
6207
6208    This is a bit complex function and it can beter be merged into the caller and be more specific
6209    there. The delta parameter can have a value already. When it keeps it value the caller can add
6210    is as italic correction. However, when we have no scripts we do it here.
6211
6212*/
6213
6214static halfword tex_aux_check_nucleus_complexity(halfword target, scaled *italic, halfword style, halfword size, kernset *kerns)
6215{
6216    halfword nucleus = noad_nucleus(target);
6217    if (nucleus) {
6218        if (italic) {
6219            *italic = 0;
6220        }
6221        switch (node_type(nucleus)) {
6222            case math_char_node:
6223            case math_text_char_node:
6224                {
6225                    halfword chr = null;
6226                    halfword fnt = null;
6227                    if (tex_aux_fetch(nucleus, "(text) char", &fnt, &chr)) {
6228                        /*tex We make a math glyph from an ordinary one. */
6229                        halfword glyph;
6230                        quarterword subtype = 0;
6231                        /* todo: make this a mapper */
6232                        switch (node_subtype(nucleus)) {
6233                            case ordinary_noad_subtype:    subtype = glyph_math_ordinary_subtype;    break;
6234                            case operator_noad_subtype:    subtype = glyph_math_operator_subtype;    break;
6235                            case binary_noad_subtype:      subtype = glyph_math_binary_subtype;      break;
6236                            case relation_noad_subtype:    subtype = glyph_math_relation_subtype;    break;
6237                            case open_noad_subtype:        subtype = glyph_math_open_subtype;        break;
6238                            case close_noad_subtype:       subtype = glyph_math_close_subtype;       break;
6239                            case punctuation_noad_subtype: subtype = glyph_math_punctuation_subtype; break;
6240                            case variable_noad_subtype:    subtype = glyph_math_variable_subtype;    break;
6241                            case active_noad_subtype:      subtype = glyph_math_active_subtype;      break;
6242                            case inner_noad_subtype:       subtype = glyph_math_inner_subtype;       break;
6243                            case over_noad_subtype:        subtype = glyph_math_over_subtype;        break;
6244                            case under_noad_subtype:       subtype = glyph_math_under_subtype;       break;
6245                            case fraction_noad_subtype:    subtype = glyph_math_fraction_subtype;    break;
6246                            case radical_noad_subtype:     subtype = glyph_math_radical_subtype;     break;
6247                            case middle_noad_subtype:      subtype = glyph_math_middle_subtype;      break;
6248                            case accent_noad_subtype:      subtype = glyph_math_accent_subtype;      break;
6249                            case fenced_noad_subtype:      subtype = glyph_math_fenced_subtype;      break;
6250                            case ghost_noad_subtype:       subtype = glyph_math_ghost_subtype;       break;
6251                            default:
6252                                if (node_subtype(nucleus) < math_begin_class) {
6253                                    /*tex
6254                                        So at least we can recongize them and have some slack for
6255                                        new ones below this boundary. Nicer would be to be in range
6256                                        but then we have to ditch the normal glyph subtypes. Maybe
6257                                        we should move all classes above this edge.
6258                                    */
6259                                    subtype = glyph_math_extra_subtype + node_subtype(nucleus);
6260                                }
6261                                break;
6262
6263                        }
6264                        glyph = tex_aux_new_math_glyph(fnt, chr, subtype);
6265                        tex_attach_attribute_list_copy(glyph, nucleus);
6266                        if (node_type(nucleus) == math_char_node) {
6267                            glyph_properties(glyph) = kernel_math_properties(nucleus);
6268                            glyph_group(glyph) = kernel_math_group(nucleus);
6269                            glyph_index(glyph) = kernel_math_index(nucleus);
6270                            if (math_kernel_node_has_option(nucleus, math_kernel_auto_discretionary)) { 
6271                                tex_add_glyph_option(glyph, glyph_option_math_discretionary);
6272                            }
6273                            if (math_kernel_node_has_option(nucleus, math_kernel_full_discretionary)) { 
6274                                tex_add_glyph_option(glyph, glyph_option_math_italics_too);
6275                            }
6276                        }
6277                        /*tex
6278                            Do we have a correction at all? In opentype fonts we normally set the
6279                            delta to zero.
6280                        */
6281                        if (math_kernel_node_has_option(nucleus, math_kernel_no_italic_correction)) {
6282                            /*tex
6283                               This node is flagged not to have italic correction.
6284                            */
6285                        } else if (tex_aux_math_followed_by_italic_kern(target, "complexity")) {
6286                            /*tex
6287                               For some reason there is (already) an explicit italic correction so we
6288                               don't add more here. I need a use case.
6289                            */
6290                        } else if (tex_aux_math_engine_control(fnt, math_control_apply_text_italic_kern)) {
6291                            /*tex
6292                                This is a bit messy and needs a more fundamental cleanup giving the
6293                                kind of control that we want.
6294                            */
6295                            if (italic) {
6296                                *italic = tex_aux_math_x_size_scaled(fnt, tex_char_italic_from_font(fnt, chr), size);
6297                                if (*italic) {
6298                                    if (node_type(nucleus) == math_text_char_node) {
6299                                        if (tex_aux_math_engine_control(fnt, math_control_check_text_italic_kern)) {
6300                                            /*tex
6301                                                We add no italic correction in mid-word of (opentype)
6302                                                text font. This is kind of fragile so it might go away
6303                                                or become an option.
6304                                            */
6305                                            if (chr == letter_cmd) {
6306                                                *italic = 0;
6307                                            }
6308                                        }
6309                                        if (tex_aux_math_engine_control(fnt, math_control_check_space_italic_kern)) {
6310                                            /*tex
6311                                                We're now in the traditional branch. it is a bit weird
6312                                                test based on space being present in an old school math
6313                                                font. For now we keep this.
6314                                            */
6315                                            if (tex_get_font_space(fnt)) {
6316                                                /*tex
6317                                                    We add no italic correction in mid-word (traditional)
6318                                                    text font. In the case of a math font, the correction
6319                                                    became part of the width.
6320                                                */
6321                                                *italic = 0;
6322                                            }
6323                                        }
6324                                    }
6325                                    if (*italic && ! noad_has_following_scripts(target)) {
6326                                        /*tex
6327                                            Here we add a correction but then also have to make sure that it
6328                                            doesn't happen later on so we zero |delta| afterwards. The call
6329                                            handles the one script only case (maybe delegate the next too).
6330                                        */
6331                                        tex_aux_math_insert_italic_kern(glyph, *italic, nucleus, "check");
6332                                        *italic = 0;
6333                                    }
6334                                }
6335                            }
6336                        }
6337                        return glyph;
6338                    } else {
6339                        return tex_new_node(hlist_node, unknown_list);
6340                    }
6341                }
6342            case sub_box_node:
6343                return kernel_math_list(nucleus);
6344            case sub_mlist_node:
6345                {
6346                    halfword list = kernel_math_list(nucleus);
6347                    halfword package = null;
6348                    halfword fenced = node_type(target) == simple_noad && node_subtype(target) == fenced_noad_subtype;
6349                    halfword last = fenced ? tex_tail_of_node_list(list) : null;
6350                    int unpack = tex_math_has_class_option(node_subtype(target), unpack_class_option) || has_noad_option_unpacklist(target);
6351                    halfword result = tex_mlist_to_hlist(list, unpack, style, unset_noad_class, unset_noad_class, kerns); /*tex Here we're nesting. */
6352/* ? */ tex_attach_attribute_list_copy(result, target);
6353                    tex_aux_set_current_math_size(style);
6354                    package = tex_hpack(result, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
6355                    if (fenced) {
6356                        node_subtype(package) = math_fence_list;
6357                        if (list && node_type(list) == fence_noad && noad_analyzed(list) != unset_noad_class) {
6358                            set_noad_left_class(target, noad_analyzed(list));
6359                        }
6360                        if (last && node_type(last) == fence_noad && noad_analyzed(last) != unset_noad_class) {
6361                            set_noad_right_class(target, noad_analyzed(last));
6362                        }
6363                    } else if (unpack) {
6364                        node_subtype(package) = math_list_list;
6365                    } else if (noad_class_main(target) == unset_noad_class) {
6366                        node_subtype(package) = math_pack_list;
6367                    } else {
6368                        node_subtype(package) = 0x100 + noad_class_main(target);
6369                    }
6370//                    tex_attach_attribute_list_copy(package, nucleus);
6371tex_attach_attribute_list_copy(package, target);
6372                    return package;
6373                }
6374            case hlist_node:
6375                /* really */
6376                break;
6377            default:
6378                tex_confusion("check nucleus complexity");
6379        }
6380    } else {
6381        tex_normal_warning("math", "recovering from missing nucleus, best check it out");
6382        noad_nucleus(target) = tex_aux_fake_nucleus(ghost_noad_subtype);
6383    }
6384    return tex_new_node(hlist_node, unknown_list);
6385}
6386
6387/*tex
6388    The main reason for keeping the node is that original \TEX\ has no prev links but we do have 
6389    these in \LUATEX. But it is anyway okay to keep this a signal. 
6390*/
6391
6392static halfword tex_aux_make_choice(halfword current, halfword style)
6393{
6394    halfword prv = node_prev(current);
6395    halfword nxt = node_next(current); 
6396    halfword signal = tex_new_node(style_node, former_choice_math_style);
6397    /*tex We replace choice by signal encoded in a style noad, it is no longer a cast! */
6398    tex_try_couple_nodes(prv, signal);
6399    tex_try_couple_nodes(signal, nxt);
6400    switch (node_subtype(current)) { 
6401        case normal_choice_subtype:
6402            {
6403                halfword choice = null;
6404                switch (style) {
6405                    case display_style:
6406                    case cramped_display_style:
6407                        choice = choice_display_mlist(current);
6408                        choice_display_mlist(current) = null;
6409                        break;
6410                    case text_style:
6411                    case cramped_text_style:
6412                        choice = choice_text_mlist(current);
6413                        choice_text_mlist(current) = null;
6414                        break;
6415                    case script_style:
6416                    case cramped_script_style:
6417                        choice = choice_script_mlist(current);
6418                        choice_script_mlist(current) = null;
6419                        break;
6420                    case script_script_style:
6421                    case cramped_script_script_style:
6422                        choice = choice_script_script_mlist(current);
6423                        choice_script_script_mlist(current) = null;
6424                        break;
6425                }
6426                /*tex We inject the choice list after the signal. */
6427                if (choice) {
6428                    tex_couple_nodes(signal, choice);
6429                    tex_try_couple_nodes(tex_tail_of_node_list(choice), nxt);
6430                }
6431            }
6432            break;
6433        case discretionary_choice_subtype:
6434            {
6435                halfword disc = tex_new_disc_node(normal_discretionary_code);
6436                halfword pre = choice_pre_break(current);
6437                halfword post = choice_post_break(current);
6438                halfword replace = choice_no_break(current);
6439                choice_pre_break(current) = null;
6440                choice_post_break(current) = null;
6441                choice_no_break(current) = null;
6442                if (pre) { 
6443                    pre = tex_mlist_to_hlist(pre, 0, style, unset_noad_class, unset_noad_class, NULL);
6444                    tex_set_disc_field(disc, pre_break_code, pre);
6445                }
6446                if (post) { 
6447                    post = tex_mlist_to_hlist(post, 0, style, unset_noad_class, unset_noad_class, NULL);
6448                    tex_set_disc_field(disc, post_break_code, post);
6449                }
6450                if (replace) { 
6451                    replace = tex_mlist_to_hlist(replace, 0, style, unset_noad_class, unset_noad_class, NULL);
6452                    tex_set_disc_field(disc, no_break_code, replace);
6453                }
6454                disc_class(disc) = choice_class(current);
6455                disc = tex_math_make_disc(disc);
6456                tex_couple_nodes(signal, disc);
6457                tex_try_couple_nodes(disc, nxt);
6458            }
6459            break;
6460    }
6461    /*tex We flush the old choice node */
6462    tex_flush_node(current);
6463    return signal;
6464}
6465
6466/*tex
6467    This is just a \quote {fixer}. Todo: prepend the top and/or bottom to the super/subscript,
6468    but we also need to hpack then. Problem: how to determine the slack here? However, slack
6469    is less important because we normally have binding right text here.
6470*/
6471
6472static int tex_aux_make_fenced(halfword current, halfword current_style, halfword size, noad_classes *fenceclasses)
6473{
6474    halfword nucleus = noad_nucleus(current);
6475    (void) current_style;
6476    (void) size;
6477    if (nucleus) {
6478        halfword list = kernel_math_list(nucleus);
6479        if (list && node_type(list) == fence_noad && node_subtype(list) == left_operator_side) {
6480            /* maybe use this more: */
6481            fenceclasses->main = noad_class_main(list);
6482            fenceclasses->left = noad_class_left(list);
6483            fenceclasses->right = noad_class_right(list);
6484            if (noad_supscr(current) && ! kernel_math_list(fence_delimiter_top(list))) {
6485                halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
6486                node_subtype(n) = math_char_node;
6487                noad_nucleus(n) = noad_supscr(current);
6488                kernel_math_list(fence_delimiter_top(list)) = n;
6489                noad_supscr(current) = null;
6490                if (tracing_math_par >= 2) {
6491                    tex_begin_diagnostic();
6492                    tex_print_str("[math: promoting supscript to top delimiter]");
6493                    tex_end_diagnostic();
6494                }
6495            }
6496            if (noad_subscr(current) && ! kernel_math_list(fence_delimiter_bottom(list))) {
6497                halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
6498                node_subtype(n) = math_char_node;
6499                noad_nucleus(n) = noad_subscr(current);
6500                kernel_math_list(fence_delimiter_bottom(list)) = n;
6501                noad_subscr(current) = null;
6502                if (tracing_math_par >= 2) {
6503                    tex_begin_diagnostic();
6504                    tex_print_str("[math: promoting subscript to bottom delimiter]");
6505                    tex_end_diagnostic();
6506                }
6507            }
6508            /*tex
6509                Now we remove the dummy right one. If something is in between we assume it's on
6510                purpose.
6511            */
6512            {
6513                halfword nxt = node_next(list);
6514                if (nxt && node_type(nxt) == fence_noad && node_subtype(nxt) == right_fence_side) {
6515                    /* todo : check for delimiter . or 0 */
6516                    node_next(list) = null;
6517                    tex_flush_node_list(nxt);
6518                }
6519            }
6520            return 1; /* we had a growing one */
6521        }
6522    }
6523    return 0;
6524}
6525
6526static void tex_aux_finish_fenced(halfword current, halfword main_style, halfword main_size, scaled max_depth, scaled max_height, kernset *kerns)
6527{
6528    delimiterextremes extremes = { .tfont = null_font, .tchar = 0, .bfont = null_font, .bchar = 0, .height = 0, .depth = 0 };
6529    noad_analyzed(current) = (singleword) tex_aux_make_left_right(current, main_style, max_depth, max_height, main_size, &extremes);
6530    if (kerns && extremes.tfont) { 
6531        switch (node_subtype(current)) { 
6532            case left_fence_side:
6533            case extended_left_fence_side:
6534                if (tex_math_has_class_option(fenced_noad_subtype, carry_over_left_top_kern_class_option)) {  
6535                     kerns->topleft = tex_aux_math_x_size_scaled(extremes.tfont, tex_char_top_left_kern_from_font(extremes.tfont, extremes.tchar), main_size);
6536                }
6537                if (tex_math_has_class_option(fenced_noad_subtype, carry_over_left_bottom_kern_class_option)) {  
6538                     kerns->bottomleft = tex_aux_math_x_size_scaled(extremes.bfont, tex_char_bottom_left_kern_from_font(extremes.bfont, extremes.bchar), main_size);
6539                }
6540                if (tex_math_has_class_option(fenced_noad_subtype, prefer_delimiter_dimensions_class_option)) {  
6541                    kerns->height = extremes.height;
6542                    kerns->depth = extremes.depth;
6543                    kerns->dimensions = 1;
6544                    kerns->font = extremes.tfont;
6545                }
6546                break;
6547            case right_fence_side:
6548            case extended_right_fence_side:
6549            case left_operator_side:
6550            case no_fence_side:
6551                if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_top_kern_class_option)) {  
6552                    kerns->topright = tex_aux_math_x_size_scaled(extremes.tfont, tex_char_top_right_kern_from_font(extremes.tfont, extremes.tchar), main_size);
6553                }
6554                if (tex_math_has_class_option(fenced_noad_subtype, carry_over_right_bottom_kern_class_option)) {  
6555                    kerns->bottomright = tex_aux_math_x_size_scaled(extremes.bfont, tex_char_bottom_right_kern_from_font(extremes.bfont, extremes.bchar), main_size);
6556                }
6557                if (tex_math_has_class_option(fenced_noad_subtype, prefer_delimiter_dimensions_class_option)) {  
6558                    kerns->height = extremes.height;
6559                    kerns->depth = extremes.depth;
6560                }
6561                break;
6562        }
6563    }
6564}
6565
6566/*tex
6567
6568    Here is the overall plan of |mlist_to_hlist|, and the list of its local variables. In
6569    \LUAMETATEX\ we could actually use the fact that we have a double linked list. Because we have
6570    a more generic class and penalty handling the two stages are clearly separated, also variable
6571    wise.
6572
6573*/
6574
6575static halfword tex_aux_unroll_noad(halfword tail, halfword l, quarterword s)
6576{
6577    while (l) {
6578        halfword n = node_next(l);
6579        node_next(l) = null;
6580        if (node_type(l) == hlist_node && node_subtype(l) == s && ! box_source_anchor(l)) {
6581            if (box_list(l)) {
6582                tex_couple_nodes(tail, box_list(l));
6583                tail = tex_tail_of_node_list(tail);
6584                box_list(l) = null;
6585            }
6586            tex_flush_node(l);
6587        } else {
6588            tex_couple_nodes(tail, l);
6589            tail = l;
6590        }
6591        l = n;
6592    }
6593    return tail;
6594}
6595
6596static halfword tex_aux_unroll_list(halfword tail, halfword l)
6597{
6598    while (l) {
6599        halfword n = node_next(l);
6600        node_next(l) = null;
6601        if (node_type(l) == hlist_node && ! box_source_anchor(l)) {
6602            if (box_list(l)) {
6603                switch (node_subtype(l)) {
6604                    case hbox_list:
6605                    case container_list:
6606                    case math_list_list: /* in case of a ghost (we could remap subtype instead) */
6607                        tex_couple_nodes(tail, box_list(l));
6608                        tail = tex_tail_of_node_list(tail);
6609                        box_list(l) = null;
6610                        break;
6611                    default:
6612                        tex_couple_nodes(tail, l);
6613                        tail = l;
6614                        break;
6615                }
6616            }
6617            tex_flush_node(l);
6618        } else { 
6619            tex_couple_nodes(tail, l);
6620            tail = l;
6621        }
6622        l = n;
6623    }
6624    return tail;
6625}
6626
6627inline static void tex_aux_wipe_noad(halfword n)
6628{
6629    if (tex_nodetype_has_attributes(node_type(n))) {
6630        remove_attribute_list(n);
6631    }
6632    tex_reset_node_properties(n);
6633    tex_free_node(n, get_node_size(node_type(n)));
6634}
6635
6636static halfword tex_aux_append_ghost(halfword ghost, halfword p)
6637{
6638    halfword l = noad_new_hlist(ghost);
6639    if (l) {
6640        if (has_noad_option_unpacklist(ghost)) {
6641            /* always anyway */
6642            p = tex_aux_unroll_noad(p, l, math_list_list);
6643        } else if (has_noad_option_unrolllist(ghost)) {
6644            p = tex_aux_unroll_list(p, l);
6645        } else {
6646            if (node_type(l) == hlist_node && ! node_next(l)) {
6647                node_subtype(l) = math_ghost_list;
6648            }
6649            tex_couple_nodes(p, l);
6650            p = tex_tail_of_node_list(p);
6651        }
6652        noad_new_hlist(ghost) = null;
6653    }
6654    tex_aux_wipe_noad(ghost);
6655    return p;
6656}
6657
6658static halfword tex_aux_get_plus_glyph(halfword current)
6659{
6660    if (node_type(current) == simple_noad) {
6661        halfword list = noad_new_hlist(current);
6662        if (list && node_type(list) == hlist_node) {
6663            list = box_list(list);
6664        }
6665        if (list && node_type(list) == glue_node) {
6666            list = node_next(list);
6667        }
6668        if (list && node_type(list) == glyph_node && ! node_next(list)) {
6669            return list;
6670        }
6671    }
6672    return null;
6673}
6674
6675static void tex_aux_show_math_list(const char *fmt, halfword list)
6676{
6677    tex_begin_diagnostic();
6678    tex_print_format(fmt, lmt_math_state.level);
6679    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);
6680    tex_print_ln();
6681    tex_end_diagnostic();
6682}
6683
6684static halfword tex_aux_check_source(halfword current, halfword list, int repack)
6685{
6686    if (list && noad_source(current)) {
6687        switch (node_type(list)) {
6688            case hlist_node:
6689            case vlist_node:
6690             // printf("anchoring to list: %i\n", noad_source(current));
6691                box_source_anchor(list) = noad_source(current);
6692                tex_set_box_geometry(list, anchor_geometry);
6693                noad_source(current) = 0; 
6694                break;
6695            default:
6696                if (repack) {
6697                    if (tracing_math_par >= 2) {
6698                        tex_begin_diagnostic();
6699                        tex_print_format("[math: packing due to source field %D]", noad_source(current));
6700                        tex_end_diagnostic();
6701                    }
6702                    list = tex_hpack(list, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
6703                 // printf("anchoring to wrapped list: %i\n", noad_source(current));
6704                    tex_attach_attribute_list_copy(list, current);
6705                    box_source_anchor(list) = noad_source(current);
6706                    noad_source(current) = 0; 
6707                    tex_set_box_geometry(list, anchor_geometry);
6708                    noad_new_hlist(current) = list;
6709                    node_subtype(list) = math_pack_list;
6710                }
6711                break;
6712        }
6713    } else {
6714        /* can't happen as we already checked before the call */
6715    }
6716    return list; 
6717}
6718
6719static void tex_aux_wrapup_nucleus_and_add_scripts(halfword current, halfword nxt, int current_style, halfword *italic, kernset *kerns)
6720{
6721    halfword p = tex_aux_check_nucleus_complexity(current, italic, current_style, lmt_math_state.size, kerns);
6722    if (p && noad_source(current)) {
6723        p = tex_aux_check_source(current, p, has_noad_option_source_on_nucleus(current));
6724    }
6725    if (noad_has_scripts(current)) {
6726        scaled drop = 0;
6727        if (node_type(current) == accent_noad && noad_has_superscripts(current)) { 
6728            drop = tex_get_math_y_parameter_default(current_style, math_parameter_accent_superscript_drop, 0);
6729            drop += scaledround(kerns->toptotal * tex_get_math_parameter_default(current_style, math_parameter_accent_superscript_percent, 0) / 100.0);
6730        }
6731        tex_aux_make_scripts(current, p, *italic, current_style, 0, 0, drop, kerns, lmt_math_state.single);
6732        lmt_math_state.single = 0;
6733    } else {
6734        /*tex
6735            Adding italic correction here is kind of fuzzy because some characters already have
6736            that built in. However, we also add it in the scripts so if it's optional here it
6737            also should be there. The compexity tester can have added it in which case delta
6738            is zero.
6739        */
6740        if (nxt && *italic) {
6741            if (node_type(nxt) == simple_noad && tex_math_has_class_option(node_subtype(nxt), no_italic_correction_class_option)) {
6742                *italic = 0;
6743            }
6744            if (*italic) {
6745                /* If we want it as option we need the fontor store it in the noad. */
6746                tex_aux_math_insert_italic_kern(p, *italic, current, "final");
6747            }
6748        }
6749        tex_aux_assign_new_hlist(current, p);
6750    }
6751}
6752
6753/*tex
6754
6755    This function is called recursively, for instance for wrapped content in fence, accent, fraction 
6756    and radical noads. Especially the fences introduce some messy code but I might clean that up 
6757    stepwise. We don't want to get away too much from the original. 
6758
6759    Because we have more than two passes, and the function became way larger, it has been split up
6760    in smaller functions. 
6761
6762*/
6763
6764typedef struct mliststate {
6765    halfword mlist;
6766    int      penalties;
6767    int      main_style;
6768    int      beginclass;
6769    int      endclass;
6770    kernset *kerns;
6771    halfword scale;
6772    scaled   max_height;
6773    scaled   max_depth;
6774    halfword single;
6775    halfword padding;
6776} mliststate;
6777
6778static void tex_mlist_to_hlist_set_boundaries(mliststate *state)
6779{
6780    halfword b = tex_aux_fake_nucleus((quarterword) state->beginclass);
6781    halfword e = tex_aux_fake_nucleus((quarterword) state->endclass);
6782    if (state->mlist) {
6783        tex_couple_nodes(b, state->mlist);
6784    }
6785    state->mlist = b;
6786    tex_couple_nodes(tex_tail_of_node_list(state->mlist), e);
6787    state->beginclass = unset_noad_class;
6788    state->endclass = unset_noad_class;
6789}
6790
6791static void tex_mlist_to_hlist_preroll_radicals(mliststate *state)
6792{
6793    halfword current = state->mlist;
6794    halfword current_style = state->main_style;
6795    halfword height = 0;
6796    halfword depth = 0;
6797    tex_aux_set_current_math_size(current_style);
6798    tex_aux_set_current_math_scale(state->scale);
6799    if (tracing_math_par >= 2) {
6800        tex_aux_show_math_list("[math: radical sizing pass, level %i]", state->mlist);
6801    }
6802    while (current) {
6803        switch (node_type(current)) {
6804            case radical_noad:
6805                {
6806                    halfword body = null;
6807                    tex_aux_preroll_radical(current, current_style, lmt_math_state.size);
6808                    body = noad_new_hlist(current);
6809                    if (box_height(body) > height) {
6810                        height = box_height(body);
6811                    }
6812                    if (box_depth(body) > depth) {
6813                        depth = box_depth(body);
6814                    }
6815                }
6816                break;
6817            case style_node:
6818                tex_aux_make_style(current, &current_style, NULL);
6819                break;
6820            case parameter_node:
6821                tex_aux_set_parameter(current, current_style);
6822                break;
6823        }
6824        current = node_next(current);
6825    }
6826    /*tex
6827        A positive value is assigned, a negative value subtracted and a value of maxdimen will use 
6828        the maximum found dimensions. Todo: use an option to control this instead. 
6829    */
6830    current = state->mlist;
6831    while (current) {
6832        if (node_type(current) == radical_noad) {
6833            switch (node_subtype(current)) { 
6834                case normal_radical_subtype:
6835                case radical_radical_subtype:
6836                case root_radical_subtype:
6837                case rooted_radical_subtype:
6838                    {
6839                        halfword body = noad_new_hlist(current);
6840                        if (radical_height(current) == max_dimension) {
6841                            box_height(body) = height;
6842                        } else if (radical_height(current) < 0) {
6843                            box_height(body) += radical_height(current);
6844                            if (box_height(body) < 0) {
6845                                box_height(body) = 0;
6846                            }
6847                        } else if (radical_height(current)) {
6848                            box_height(body) = radical_height(current);
6849                        }
6850                        if (radical_depth(current) == max_dimension) {
6851                            box_depth(body) = depth;
6852                        } else if (radical_depth(current) < 0) {
6853                            box_depth(body) += radical_depth(current);
6854                            if (box_depth(body) < 0) {
6855                                box_depth(body) = 0;
6856                            }
6857                        } else if (radical_depth(current)) {
6858                            box_depth(body) = radical_depth(current);
6859                        }
6860                    }
6861                    break;
6862            }
6863        }
6864        current = node_next(current);
6865    }
6866}
6867
6868/*tex 
6869    At some point this will all change to well defined kernel/script/italic handling but then we no 
6870    longer are compatible. It depends on fonts being okay. We already have some dead brances. 
6871*/
6872
6873static void tex_mlist_to_hlist_preroll_dimensions(mliststate *state)
6874{
6875    halfword current = state->mlist;
6876    scaled current_mu = 0;
6877    halfword current_style = state->main_style;
6878    int blockrulebased = 0;
6879    /*tex We set the math unit width corresponding to |size|: */
6880    tex_aux_set_current_math_size(current_style);
6881    tex_aux_set_current_math_scale(state->scale);
6882 // current_mu = tex_get_math_quad_size_scaled(lmt_math_state.size);
6883    current_mu = tex_get_math_quad_size_unscaled(lmt_math_state.size);
6884    if (tracing_math_par >= 2) {
6885        tex_aux_show_math_list("[math: first pass, level %i]", state->mlist);
6886    }
6887    while (current) {
6888        /*tex The italic correction offset for subscript and superscript: */
6889        scaled italic = 0;
6890        halfword nxt = node_next(current);
6891        noad_classes fenceclasses = { unset_noad_class, unset_noad_class, unset_noad_class };
6892        kernset localkerns;
6893        tex_math_wipe_kerns(&localkerns);
6894        /*tex
6895            At some point we had nicely cleaned up switch driven code here but we ended up with a
6896            more generic approach. The reference is still in the pre-2022 zips and git repository.
6897
6898            The fact that we have configurable atom spacing (with inheritance) means that we can
6899            now have a rather simple switch without any remapping and RESWITCH magic.
6900        */
6901        if (blockrulebased > 0) {
6902            blockrulebased -= 1;
6903        }
6904        switch (node_type(current)) {
6905            case simple_noad:
6906                /*tex
6907                    Because we have added features we no longer combine the case in clever ways to
6908                    minimize code. Let the compiler do that for us. We could be generic and treat
6909                    all the same but for now we just emulate some of traditional \TEX's selectivity.
6910                */
6911                if (blockrulebased > 0) {
6912                    noad_options(current) |= noad_option_no_ruling;
6913                    blockrulebased = 0;
6914                }
6915                switch (node_subtype(current)) {
6916                    case under_noad_subtype:
6917                        tex_aux_make_under(current, current_style, lmt_math_state.size, math_rules_fam_par);
6918                        break;
6919                    case over_noad_subtype:
6920                        tex_aux_make_over(current, current_style, lmt_math_state.size, math_rules_fam_par);
6921                        break;
6922                    case vcenter_noad_subtype:
6923                        tex_aux_make_vcenter(current, current_style, lmt_math_state.size);
6924                        break;
6925                    case fenced_noad_subtype:
6926                        if (tex_aux_make_fenced(current, current_style, lmt_math_state.size, &fenceclasses)) {
6927                            /*tex We have a left operator so we fall through! */
6928                        } else {
6929                            break;
6930                        }
6931                    case operator_noad_subtype:
6932                        /* compatibility */
6933                        if (! (has_noad_option_limits(current) || has_noad_option_nolimits(current))) {
6934                            /* otherwise we don't enter the placement function */
6935                            noad_options(current) |= (current_style == display_style || current_style == cramped_display_style) ? noad_option_limits : noad_option_no_limits;
6936                        }
6937                        goto PROCESS;
6938                    default:
6939                        /* setting both forces check */
6940                        if ((has_noad_option_limits(current) && has_noad_option_nolimits(current))) {
6941                            if (current_style == display_style || current_style == cramped_display_style) {
6942                                noad_options(current) = unset_option(noad_options(current), noad_option_no_limits);
6943                                noad_options(current) |= noad_option_limits;
6944                            } else {
6945                                noad_options(current) = unset_option(noad_options(current), noad_option_limits);
6946                                noad_options(current) |= noad_option_no_limits;
6947                            }
6948                        }
6949                      PROCESS:
6950                        if (  // node_subtype(q) == operator_noad_subtype
6951                                // ||
6952                                   has_noad_option_limits(current)       || has_noad_option_nolimits(current)
6953                                || has_noad_option_openupheight(current) || has_noad_option_openupdepth(current)
6954                                || has_noad_option_adapttoleft(current)  || has_noad_option_adapttoright(current)
6955                            ) {
6956                            if (node_subtype(current) == fenced_noad_subtype && ! noad_has_scripts(current)) {
6957                                /*tex
6958                                    This is a special case: the right counterpart of the left operator
6959                                    can trigger a boxing of all that comes before so we need to enforce
6960                                    nolimits. Mikael Sundqvist will reveal all this in the CMS manual.
6961                                */
6962                                italic = tex_aux_make_op(current, current_style, lmt_math_state.size, 0, limits_horizontal_mode, NULL);
6963                            } else {
6964                                italic = tex_aux_make_op(current, current_style, lmt_math_state.size, 0, limits_unknown_mode, NULL);
6965                            }
6966                            /* tex_math_has_class_option(node_subtype(current),keep_correction_class_code) */
6967                            if (node_subtype(current) != operator_noad_subtype) {
6968                                italic = 0;
6969                            }
6970                            if (fenceclasses.main != unset_noad_class) {
6971                                noad_class_main(current) = fenceclasses.main;
6972                            }
6973                            if (fenceclasses.left != unset_noad_class) {
6974                                noad_class_left(current) = fenceclasses.left;
6975                            }
6976                            if (fenceclasses.right != unset_noad_class) {
6977                                noad_class_right(current) = fenceclasses.right;
6978                            }
6979                            if (has_noad_option_limits(current) || has_noad_option_nolimits(current)) {
6980                                goto CHECK_DIMENSIONS;
6981                            }
6982                        } else {
6983                            // tex_aux_make_ord(current, lmt_math_state.size);
6984                            tex_aux_check_ord(current, lmt_math_state.size, null);
6985                        }
6986                        break;
6987                }
6988                break;
6989            case fence_noad:
6990                {
6991                    state->single = has_noad_option_single(current); 
6992                    /* why still ... */
6993                    current_style = state->main_style;
6994                    tex_aux_set_current_math_size(current_style);
6995                 // current_mu = tex_get_math_quad_size_scaled(lmt_math_state.size);
6996                    current_mu = tex_get_math_quad_size_unscaled(lmt_math_state.size);
6997                    /* ... till here */
6998                    goto DONE_WITH_NODE;
6999                }
7000            case fraction_noad:
7001                tex_aux_make_fraction(current, current_style, lmt_math_state.size, state->kerns);
7002                goto CHECK_DIMENSIONS;
7003            case radical_noad:
7004                tex_aux_make_radical(current, current_style, lmt_math_state.size, &localkerns);
7005                break;
7006            case accent_noad:
7007                tex_aux_make_accent(current, current_style, lmt_math_state.size, &localkerns);
7008                break;
7009            case style_node:
7010                tex_aux_make_style(current, &current_style, &current_mu);
7011                goto DONE_WITH_NODE;
7012            case choice_node:
7013                current = tex_aux_make_choice(current, current_style);
7014                goto DONE_WITH_NODE;
7015            case parameter_node:
7016                /* maybe not needed as we do a first pass */
7017                tex_aux_set_parameter(current, current_style);
7018                goto DONE_WITH_NODE;
7019            case insert_node:
7020            case mark_node:
7021            case adjust_node:
7022            case boundary_node:
7023            case whatsit_node:
7024            case penalty_node:
7025            case disc_node:
7026            case par_node: /* for local boxes */
7027                goto DONE_WITH_NODE;
7028            case rule_node:
7029                tex_aux_check_math_strut_rule(current, current_style);
7030                if (rule_height(current) > state->max_height) {
7031                    state->max_height = rule_height(current);
7032                }
7033                if (rule_depth(current) > state->max_depth) {
7034                    state->max_depth = rule_depth(current);
7035                }
7036                goto DONE_WITH_NODE;
7037            case glue_node:
7038                if (node_subtype(current) == rulebased_math_glue) {
7039                    blockrulebased = 2;
7040                }
7041                tex_aux_make_glue(current, current_mu, current_style);
7042                goto DONE_WITH_NODE;
7043            case kern_node:
7044                tex_aux_make_kern(current, current_mu, current_style);
7045                goto DONE_WITH_NODE;
7046            default:
7047                tex_confusion("mlist to hlist, case 1");
7048        }
7049        /*tex
7050            When we get to the following part of the program, we have \quote {fallen through} from
7051            cases that did not lead to |check_dimensions| or |done_with_noad| or |done_with_node|.
7052            Thus, |q|~points to a noad whose nucleus may need to be converted to an hlist, and
7053            whose subscripts and superscripts need to be appended if they are present.
7054
7055            If |nucleus(q)| is not a |math_char|, the variable |italic| is the amount by which a
7056            superscript should be moved right with respect to a subscript when both are present.
7057        */
7058        tex_aux_wrapup_nucleus_and_add_scripts(current, nxt, current_style, &italic, &localkerns);
7059      CHECK_DIMENSIONS:
7060        {
7061            scaledwhd siz = tex_natural_hsizes(noad_new_hlist(current), null, normal_glue_multiplier, normal_glue_sign, normal_glue_sign);
7062            if (siz.ht > state->max_height) {
7063                state->max_height = siz.ht;
7064            }
7065            if (siz.dp > state->max_depth) {
7066                state->max_depth = siz.dp;
7067            }
7068        }
7069//    DONE_WITH_NODE:
7070        if ((node_type(current) == simple_noad) && noad_new_hlist(current)) { 
7071            if (has_noad_option_phantom(current) || has_noad_option_void(current)) {
7072                noad_new_hlist(current) = tex_aux_make_list_phantom(noad_new_hlist(current), has_noad_option_void(current), get_attribute_list(current));
7073            }
7074        } 
7075      DONE_WITH_NODE:
7076        current = node_next(current);
7077    }
7078}
7079
7080/*tex 
7081
7082    The two pass approach is the result of some experiments with bad fonts. Actually, when we were 
7083    testing middle bars we found that many fonts have flaws: slightly off dimensions (compared to 
7084    for instance parentheses, square brackets and curly braces, and then these can be inconsistent 
7085    in height and depth), missing double or triple bars or partially implemented ones (no variants 
7086    or extensibles), inconsistent rule widths, weird positioning in the baseline etc. It doesn't 
7087    help either that \UNICODE\ lacks left, middle and right bars. In the end we decided to come up
7088    with companion fonts that provide fixed bars but the engine feature remains. 
7089
7090    The first pass should actually be a preroll and not finalize the fences yet because we can have
7091    inconsistent single and double bars but for now we forget about this. 
7092
7093*/
7094
7095static void tex_mlist_to_hlist_size_fences(mliststate *state)
7096{
7097    halfword current = state->mlist;
7098    halfword current_style = state->main_style;
7099    scaled height = 0;
7100    scaled depth = 0;
7101    tex_aux_set_current_math_size(current_style);
7102    tex_aux_set_current_math_scale(state->scale);
7103    if (tracing_math_par >= 2) {
7104        tex_aux_show_math_list("[math: fence sizing pass, level %i]", state->mlist);
7105    }
7106    /* first pass */
7107    while (current) {
7108        switch (node_type(current)) {
7109            case fence_noad:
7110                if (node_subtype(current) != middle_fence_side) {
7111                    tex_aux_finish_fenced(current, current_style, lmt_math_state.size, state->max_depth, state->max_height, state->kerns);
7112                    if (node_subtype(current) == left_fence_side || node_subtype(current) == right_fence_side) {
7113                        halfword list = noad_new_hlist(current);
7114                        if (list) { 
7115                            height = box_height(list);
7116                            depth = box_depth(list);
7117                        }
7118                    }
7119                }
7120                break;
7121            case style_node:
7122                tex_aux_make_style(current, &current_style, NULL);
7123                break;
7124            case parameter_node:
7125                /* tricky as this is sort of persistent, we need to reset it at the start */
7126                tex_aux_set_parameter(current, current_style);
7127                break;
7128        }
7129        current = node_next(current);
7130    }
7131    current = state->mlist;
7132    while (current) {
7133        switch (node_type(current)) {
7134            case fence_noad:
7135                if (node_subtype(current) == middle_fence_side) { 
7136                    int automiddle = has_noad_option_auto_middle(current) && (height || depth);
7137                    tex_aux_finish_fenced(current, current_style, lmt_math_state.size, automiddle ? depth : state->max_depth, automiddle ? height : state->max_height, state->kerns);
7138                }
7139                break;
7140        }
7141        current = node_next(current);
7142    }
7143}
7144
7145/*tex 
7146    The mathboundary feature, where 0/2 push, 1/3 pop, 2/3 take an penalty delta, is only there for 
7147    special testing by MS and me, so don't depend on that for now. 
7148*/
7149
7150static void tex_mlist_to_hlist_finalize_list(mliststate *state)
7151{
7152    halfword recent = null; /*tex Watch out: can be wiped, so more a signal! */
7153    int recent_type = 0;
7154    int recent_subtype = ordinary_noad_subtype;
7155    halfword current_style = state->main_style;
7156    halfword fenced = null;
7157    halfword packedfence = null;
7158    halfword recent_left_slack = 0;
7159    halfword recent_right_slack = 0;
7160    halfword recent_class_overload = unset_noad_class;
7161    halfword recent_script_state = 0;
7162    halfword recent_plus_glyph = null;
7163    scaled current_mu = 0;
7164    halfword current = state->mlist;
7165    halfword p = temp_head;
7166    halfword ghost = null;
7167    int boundarylevel = 0;
7168    int boundaryfactor = scaling_factor;
7169    int nestinglevel = 0;
7170    int nestingfactor = scaling_factor;
7171    node_next(p) = null;
7172    tex_aux_set_current_math_size(current_style);
7173    tex_aux_set_current_math_scale(state->scale);
7174 // current_mu = tex_get_math_quad_size_scaled(lmt_math_state.size);
7175    current_mu = tex_get_math_quad_size_unscaled(lmt_math_state.size);
7176    if (math_penalties_mode_par) {
7177        state->penalties = 1; /* move to caller ? */
7178    }
7179    if (tracing_math_par >= 2) {
7180        tex_aux_show_math_list("[math: second pass, level %i]", state->mlist);
7181    }
7182  RESTART:
7183    while (current) {
7184        /*tex
7185            If node |q| is a style node, change the style and |goto delete_q|; otherwise if it is
7186            not a noad, put it into the hlist, advance |q|, and |goto done|; otherwise set |s| to
7187            the size of noad |q|, set |t| to the associated type (|ord_noad.. inner_noad|), and set
7188            |pen| to the associated penalty.
7189
7190            Just before doing the big |case| switch in the second pass, the program sets up default
7191            values so that most of the branches are short.
7192
7193            We need to remain somewhat compatible so we still handle some open and close fence
7194            setting (marked as safeguard) here but as we (1) take the class from the delimiter,
7195            when set, or (2) derive it from the fence subtype, we don't really need it. In some
7196            cases, like with bars that serve a dual purpose, it will always be a mess.
7197
7198        */
7199        /*tex the effective |type| of noad |q| during the second pass */
7200        halfword current_type = simple_noad;
7201        /*tex the effective |subtype| of noad |q| during the second pass */
7202        halfword current_subtype = ordinary_noad_subtype;
7203        /*tex penalties to be inserted */
7204        halfword post_penalty = infinite_penalty;
7205        halfword pre_penalty = infinite_penalty;
7206        /*tex experiment */
7207        halfword current_left_slack = 0;
7208        halfword current_right_slack = 0;
7209        halfword current_script_state = 0;
7210        halfword current_plus_glyph = 0;
7211        halfword old_recent = 0;
7212        halfword old_current = 0;
7213      HERE:
7214        switch (node_type(current)) {
7215            case simple_noad:
7216                if (node_subtype(current) == ghost_noad_subtype) {
7217                    /* for now, what to do with edges */
7218                    halfword nxt = node_next(current);
7219                    if (ghost) {
7220                        // check for noad_new_hlist(ghost)
7221                        halfword p = tex_tail_of_node_list(noad_new_hlist(ghost)); 
7222                        noad_class_right(ghost) = noad_class_right(current);
7223                        p = tex_aux_append_ghost(current, p);
7224                        noad_new_hlist(ghost) = tex_head_of_node_list(p); 
7225                    } else {
7226                        ghost = current;
7227                    }
7228                    current = nxt;
7229                    if (current) {
7230                        goto HERE;
7231                    } else {
7232                        goto RESTART;
7233                    }
7234                } else {
7235                    /*tex
7236                        Here we have a wrapped list of left, middle, right and content nodes.  
7237                    */
7238                    current_subtype = node_subtype(current);
7239                    current_left_slack = noad_left_slack(current);
7240                    current_right_slack = noad_right_slack(current);
7241                    current_script_state = noad_script_state(current);
7242                    switch (current_subtype) {
7243                        case fenced_noad_subtype:
7244                            {
7245                                fenced = current;
7246                                if (get_noad_right_class(fenced) != unset_noad_class) {
7247                                    current_subtype = get_noad_left_class(fenced);
7248                                } else if (get_noad_main_class(fenced) != unset_noad_class) { // needs testing by MS
7249                                    current_subtype = get_noad_main_class(fenced);
7250                                } else {
7251                                    current_subtype = open_noad_subtype; /* safeguard, see comment above */
7252                                }
7253                                break;
7254                            }
7255                        default:
7256                            {
7257                                halfword list = noad_new_hlist(current);
7258                                if (list && tex_is_math_disc(list)) {
7259                                    current_type = simple_noad;
7260                                    current_subtype = disc_class(box_list(list));
7261                                }
7262                                if (list && noad_source(current)) {
7263                                    tex_aux_check_source(current, list, 1);
7264                                } 
7265                                break;
7266                            }
7267                    }
7268                    if (get_noad_left_class(current) != unset_noad_class) {
7269                        current_subtype = get_noad_left_class(current);
7270                    } else if (get_noad_main_class(current) != unset_noad_class) {
7271                        current_subtype = get_noad_main_class(current);
7272                    }
7273                }
7274                break;
7275            case radical_noad:
7276                switch (node_subtype(current)) {
7277                    case normal_radical_subtype:
7278                    case radical_radical_subtype:
7279                    case root_radical_subtype:
7280                    case rooted_radical_subtype:
7281                    case delimited_radical_subtype:
7282                        current_type = simple_noad;
7283                        current_subtype = radical_noad_subtype;
7284                        break;
7285                    case under_delimiter_radical_subtype:
7286                    case delimiter_under_radical_subtype:
7287                        current_type = simple_noad;
7288                        current_subtype = under_noad_subtype;
7289                        break;
7290                    case over_delimiter_radical_subtype:
7291                    case delimiter_over_radical_subtype:
7292                        current_type = simple_noad;
7293                        current_subtype = over_noad_subtype;
7294                        break;
7295                    case h_extensible_radical_subtype:
7296                        current_type = simple_noad;
7297                        current_subtype = accent_noad_subtype;
7298                        break;
7299                }
7300                break;
7301            case accent_noad:
7302                current_type = simple_noad; /*tex Same kind of fields. */
7303             // current_subtype = accent_noad_subtype;
7304                current_subtype = get_noad_main_class(current);
7305                current_left_slack = noad_left_slack(current);
7306                current_right_slack = noad_right_slack(current);
7307                break;
7308            case fraction_noad:
7309                current_type = simple_noad; /*tex Same kind of fields. */
7310                current_subtype = fraction_noad_subtype; /* inner_noad_type */
7311                break;
7312            case fence_noad:
7313                /*tex Here we have a left, right, middle */
7314                current_type = simple_noad; /*tex Same kind of fields. */
7315                current_subtype = noad_analyzed(current);
7316                if (fence_nesting_factor(current) && fence_nesting_factor(current) != scaling_factor) { 
7317                    switch(current_subtype) { 
7318                        case open_noad_subtype:
7319                            boundarylevel++;
7320                            boundaryfactor = fence_nesting_factor(current);
7321                            break;
7322                        case close_noad_subtype:
7323                            if (boundarylevel > 0) {
7324                                boundarylevel--;
7325                                if (boundarylevel == 0) { 
7326                                    boundaryfactor = scaling_factor;
7327                                } else { 
7328                                    boundaryfactor = fence_nesting_factor(current);
7329                                }
7330                            } else { 
7331                                boundaryfactor = scaling_factor;
7332                            }
7333                            break;
7334                    }
7335                }
7336                packedfence = current;
7337                break;
7338            case style_node:
7339                tex_aux_make_style(current, &current_style, &current_mu);
7340                recent = current;
7341                current = node_next(current);
7342                tex_aux_wipe_noad(recent);
7343                goto RESTART;
7344            case parameter_node:
7345                tex_aux_set_parameter(current, current_style);
7346                recent = current;
7347                current = node_next(current);
7348                tex_aux_wipe_noad(recent);
7349                goto RESTART;
7350            case glue_node:
7351                switch (node_subtype(current)) {
7352                    case conditional_math_glue:
7353                    case rulebased_math_glue:
7354                        {
7355                            halfword t = current;
7356                            current = node_next(current);
7357                            tex_flush_node(t);
7358                            goto MOVEON;
7359                        }
7360                    default:
7361                        break;
7362                }
7363            case boundary_node:
7364                if (node_subtype(current) == math_boundary) {
7365                    halfword l = boundary_data(current);
7366                    switch(l) {
7367                        case 0: 
7368                        case 2: 
7369                            boundarylevel++;
7370                            if (l == 2) { 
7371                                boundaryfactor = boundary_reserved(current) ? boundary_reserved(current) : scaling_factor;
7372                            }
7373                            break;
7374                        case 1: 
7375                        case 3: 
7376                            if (boundarylevel > 0) {
7377                                boundarylevel--;
7378                                if (boundarylevel == 0) { 
7379                                    boundaryfactor = scaling_factor;
7380                                } else if (l == 2) { 
7381                                    boundaryfactor = boundary_reserved(current) ? boundary_reserved(current) : scaling_factor;
7382                                }
7383                            } else { 
7384                                tex_formatted_warning("math", "invalid math boundary %i nesting", l);
7385                            }
7386                            break;
7387                        default:
7388                            tex_formatted_warning("math", "invalid math boundary value");
7389                            /* error */
7390                            break;
7391                    }
7392                }
7393                goto PICKUP;
7394            // case glyph_node:
7395            case disc_node:
7396            case hlist_node:
7397            case whatsit_node:
7398            case penalty_node:
7399            case rule_node:
7400            case adjust_node:
7401            case insert_node:
7402            case mark_node:
7403            case par_node:
7404            case kern_node:
7405              PICKUP:
7406                tex_couple_nodes(p, current);
7407                p = current;
7408                current = node_next(current);
7409                node_next(p) = null;
7410              MOVEON:
7411                if (current) {
7412                    /*tex These nodes are invisible! */
7413                    switch (node_type(p)) {
7414                        case boundary_node:
7415                        case adjust_node:
7416                        case insert_node:
7417                        case mark_node:
7418                        case par_node:
7419                            goto HERE;
7420                        case rule_node:
7421                            if (node_subtype(p) == strut_rule_subtype) {
7422                                goto HERE;
7423                            }
7424                    }
7425                }
7426                continue;
7427                //  goto NEXT_NODE;
7428            default:
7429                tex_confusion("mlist to hlist, case 2");
7430        }
7431        /*tex
7432            Apply some logic. The hard coded pairwise comparison is replaced by a generic one
7433            because we can have more classes. For a while spacing and pairing was under a mode
7434            control but that made no sense. We start with the begin class.  
7435
7436            Setting |state->beginclass| still fragile ... todo. 
7437        */
7438        recent_class_overload = get_noad_right_class(current);
7439        if (current_type == simple_noad && state->beginclass == unset_noad_class) {
7440            if (noad_new_hlist(current)) { 
7441                tex_flush_node(noad_new_hlist(current));
7442                noad_new_hlist(current) = null;
7443            }
7444            state->beginclass = current_subtype;
7445            /* */
7446            recent_type = current_type;
7447            recent_subtype = current_subtype;
7448            recent = current;
7449            current = node_next(current);
7450            goto WIPE;
7451        }
7452        if (recent_subtype == math_begin_class) {
7453            state->beginclass = current_subtype;
7454        }
7455        /*tex 
7456            This is a special case where a sign starts something marked as (like) numeric, in 
7457            which there will be different spacing applied. 
7458        */
7459        if (tex_math_has_class_option(current_subtype, look_ahead_for_end_class_option)) {
7460            halfword endhack = node_next(current);
7461            if (endhack && node_type(endhack) == simple_noad && (node_subtype(endhack) == math_end_class || get_noad_main_class(endhack) == math_end_class)) {
7462                halfword value = tex_aux_math_ruling(current_subtype, math_end_class, current_style);
7463                if (value != MATHPARAMDEFAULT) {
7464                    // recent_subtype = (value >> 16) & 0xFF;
7465                    // current_subtype = value & 0xFF;
7466                    current_subtype = (value >> 16) & 0xFF;
7467                }
7468
7469            }
7470        }
7471        old_recent = recent_subtype;
7472        old_current = current_subtype;
7473        if (current_subtype != unset_noad_class && recent_subtype != unset_noad_class && current_type == simple_noad) {
7474            if (recent_type == simple_noad && ! has_noad_option_noruling(current)) {
7475                halfword value = tex_aux_math_ruling(recent_subtype, current_subtype, current_style);
7476                if (value != MATHPARAMDEFAULT) {
7477                    recent_subtype = (value >> 16) & 0xFF;
7478                    current_subtype = value & 0xFF;
7479                }
7480            }
7481            if (tracing_math_par >= 2) {
7482                tex_begin_diagnostic();
7483                if (old_recent != recent_subtype || old_current != current_subtype) {
7484                    tex_print_format("[math: atom ruling, recent %n, current %n, new recent %n, new current %n]", old_recent, old_current, recent_subtype, current_subtype);
7485                } else {
7486                    tex_print_format("[math: atom ruling, recent %n, current %n]", old_recent, old_current);
7487                }
7488                tex_end_diagnostic();
7489            }
7490        }
7491        /*tex Now we set the inter-atom penalties: */
7492        if (ghost && ! has_noad_option_right(ghost)) {
7493            p = tex_aux_append_ghost(ghost, p);
7494            ghost = null;
7495        }
7496        if (current_type == simple_noad) {
7497            pre_penalty = tex_aux_math_penalty(state->main_style, 1, current_subtype);
7498            post_penalty = tex_aux_math_penalty(state->main_style, 0, current_subtype);
7499        }
7500        /*tex Dirty trick: */ /* todo: use kerns info */
7501        current_plus_glyph = tex_aux_get_plus_glyph(current);
7502        /*tex Append inter-element spacing based on |r_type| and |t| */
7503        if (current_plus_glyph && recent_script_state) {
7504            /*tex This is a very special case and used {x^2 / 3| kind of situations: */
7505            halfword plus = tex_aux_checked_left_kern(current_plus_glyph, recent_script_state, current_subtype, lmt_math_state.size);
7506            if (plus) {
7507                halfword kern = tex_new_kern_node(plus, math_shape_kern_subtype);
7508                tex_attach_attribute_list_copy(kern, current);
7509                tex_couple_nodes(p, kern);
7510                p = kern;
7511                if (tracing_math_par >= 2) {
7512                    tex_begin_diagnostic();
7513                    tex_print_format("[math: state driven left shape kern %p]", plus);
7514                    tex_end_diagnostic();
7515                }
7516            }
7517        }
7518        if (recent_type > 0) {
7519            halfword last = node_type(p); /* can be temp */
7520            halfword glue = tex_aux_math_spacing_glue(recent_subtype, current_subtype, current_style, current_mu);
7521            halfword kern = null;
7522            if (glue) {
7523                tex_attach_attribute_list_copy(glue, current);
7524            }
7525            if (recent_right_slack) {
7526                halfword kern = tex_new_kern_node(-recent_right_slack, horizontal_math_kern_subtype);
7527                tex_attach_attribute_list_copy(kern, current);
7528                tex_couple_nodes(p, kern);
7529                p = kern;
7530                if (current_subtype >= 0 && tex_math_has_class_option(current_subtype, no_pre_slack_class_option)) {
7531                    /* */
7532                } else if (! glue) {
7533                    glue = tex_aux_math_dimension(recent_right_slack, inter_math_skip_glue, -2);
7534                } else {
7535                    glue_amount(glue) += recent_right_slack;
7536                }
7537                if (tracing_math_par >= 2) {
7538                    tex_begin_diagnostic();
7539                    tex_print_format("[math: migrating right slack %p]", recent_right_slack);
7540                    tex_end_diagnostic();
7541                }
7542                recent_right_slack = 0;
7543            }
7544            if (recent_plus_glyph && current_script_state) {
7545                /*tex This is a very special case and used {x^2 / 3| kind of situations: */
7546                halfword plus = tex_aux_checked_right_kern(recent_plus_glyph, current_script_state, recent_subtype, lmt_math_state.size);
7547                if (plus) {
7548                    halfword kern = tex_new_kern_node(plus, math_shape_kern_subtype);
7549                    tex_attach_attribute_list_copy(kern, current);
7550                    tex_couple_nodes(p, kern);
7551                    p = kern;
7552                    if (tracing_math_par >= 2) {
7553                        tex_begin_diagnostic();
7554                        tex_print_format("[math: state driven right shape kern %p]", plus);
7555                        tex_end_diagnostic();
7556                    }
7557                }
7558            }
7559            if (current_left_slack) {
7560                kern = tex_new_kern_node(-current_left_slack, horizontal_math_kern_subtype);
7561                tex_attach_attribute_list_copy(kern, p);
7562                /* tex_couple_nodes(node_prev(p), kern); */ /* close to the molecule */
7563                /* tex_couple_nodes(kern, p);            */ /* close to the molecule */
7564                if (recent_subtype >= 0 && tex_math_has_class_option(recent_subtype, no_post_slack_class_option)) {
7565                    /* */
7566                } else if (! glue) {
7567                    glue = tex_aux_math_dimension(current_left_slack, inter_math_skip_glue, -1);
7568                } else {
7569                    glue_amount(glue) += current_left_slack;
7570                }
7571                current_left_slack = 0;
7572            }
7573            /*tex
7574                Do we still want this check in infinite. 
7575            */
7576            if (tex_math_has_class_option(current_subtype, push_nesting_class_option)) {
7577                nestinglevel++;
7578                switch (current_style) { 
7579                    case display_style:
7580                    case cramped_display_style:
7581                        nestingfactor = math_display_penalty_factor_par ? math_display_penalty_factor_par : scaling_factor;
7582                        break;
7583                    default:
7584                        nestingfactor = math_inline_penalty_factor_par ? math_inline_penalty_factor_par : scaling_factor;
7585                        break;
7586                }
7587            } else if (tex_math_has_class_option(current_subtype, pop_nesting_class_option) && nestinglevel > 0) {
7588                nestinglevel--;
7589                if (nestinglevel == 0) {
7590                    nestingfactor = scaling_factor;
7591                }
7592            } 
7593            if (state->penalties && node_type(last) != penalty_node && pre_penalty <= infinite_penalty && (! boundarylevel || (boundaryfactor != scaling_factor || nestingfactor != scaling_factor))) {
7594                if (boundaryfactor != scaling_factor) {
7595                    pre_penalty = tex_xn_over_d(pre_penalty, boundaryfactor, scaling_factor);
7596                } else if (nestingfactor != scaling_factor && tex_math_has_class_option(current_subtype, obey_nesting_class_option)) {
7597                    pre_penalty = tex_xn_over_d(pre_penalty, nestingfactor, scaling_factor);
7598                }
7599                if (pre_penalty < infinite_penalty) {
7600                    /*tex no checking of prev node type */
7601                    halfword penalty = tex_new_penalty_node(pre_penalty, math_pre_penalty_subtype);
7602                    tex_attach_attribute_list_copy(penalty, current);
7603                    tex_couple_nodes(p, penalty);
7604                    p = penalty;
7605                    if (tracing_math_par >= 2) {
7606                        tex_begin_diagnostic();
7607                        tex_print_format("[math: pre penalty, left %n, right %n, amount %i]", recent_subtype, current_subtype, penalty_amount(penalty));
7608                        tex_end_diagnostic();
7609                    }
7610                }
7611            }
7612            if (tex_math_has_class_option(current_subtype, remove_italic_correction_class_option)) {
7613                if (node_type(p) == kern_node && node_subtype(p) == italic_kern_subtype) {
7614                    halfword prv = node_prev(p);
7615                    if (prv) {
7616                        if (tracing_math_par >= 2) {
7617                            tex_begin_diagnostic();
7618                            tex_print_format("[math: removing italic correction %D between %i and %i]", kern_amount(p), recent_subtype, current_subtype);
7619                            tex_end_diagnostic();
7620                        }
7621                        tex_flush_node(p);
7622                        p = prv;
7623                    }
7624                }
7625            }
7626            if (glue) {
7627                tex_couple_nodes(p, glue);
7628                p = glue;
7629            }
7630            if (kern) {
7631                tex_couple_nodes(p, kern);
7632                p = kern;
7633            }
7634        }
7635        if (ghost) {
7636            p = tex_aux_append_ghost(ghost, p);
7637            ghost = null;
7638        }
7639        {
7640            halfword l = noad_new_hlist(current);
7641            if (! l) { 
7642                /* curious */
7643            } else if (node_type(l) == hlist_node && box_source_anchor(l)) {
7644                tex_couple_nodes(p, l);
7645            } else if (packedfence) { 
7646                /*tex This branch probably can go away, see below. */
7647                /*tex Watch out: we can have |[prescripts] [fencelist] [postscripts]| */
7648                if (tex_math_has_class_option(fenced_noad_subtype, unpack_class_option)) {
7649                    p = tex_aux_unroll_noad(p, l, math_fence_list);
7650                } else { 
7651                    tex_couple_nodes(p, l);
7652                }
7653            } else if ((current_subtype == open_noad_subtype || current_subtype == fenced_noad_subtype) && tex_math_has_class_option(fenced_noad_subtype, unpack_class_option)) {
7654                /*tex tricky as we have an open subtype for spacing now. */
7655                p = tex_aux_unroll_noad(p, l, math_fence_list);
7656            } else if (has_noad_option_unpacklist(current) || tex_math_has_class_option(current_subtype, unpack_class_option)) {
7657                /*tex So here we only unpack a math list. */
7658                p = tex_aux_unroll_noad(p, l, math_list_list);
7659            } else if (has_noad_option_unrolllist(current)) {
7660                p = tex_aux_unroll_list(p, l);
7661            } else if (tex_is_math_disc(l)) {
7662                /* hm, temp nodes here */
7663                tex_couple_nodes(p, box_list(l));
7664                box_list(l) = null;
7665                tex_flush_node(l);
7666            } else if (current_type == simple_noad && (current_subtype == math_end_class || current_subtype == math_begin_class)) {
7667                 if (noad_new_hlist(current)) { 
7668                      tex_flush_node(noad_new_hlist(current));
7669                      noad_new_hlist(current) = null;
7670                 }
7671            } else {
7672                tex_couple_nodes(p, l);
7673            }
7674            p = tex_tail_of_node_list(p);
7675            if (fenced) {
7676                if (get_noad_right_class(fenced) != unset_noad_class) {
7677                    current_subtype = get_noad_right_class(fenced);
7678                } else if (get_noad_main_class(fenced) != unset_noad_class) { // needs testing by MS
7679                    current_subtype = get_noad_main_class(fenced);
7680                } else {
7681                    current_subtype = close_noad_subtype; /* safeguard, see comment above */
7682                }
7683                fenced = null;
7684            }
7685            noad_new_hlist(current) = null;
7686            packedfence = null;
7687        }
7688        /*tex
7689            Append any |new_hlist| entries for |q|, and any appropriate penalties. We insert a
7690            penalty node after the hlist entries of noad |q| if |pen| is not an \quote {infinite}
7691            penalty, and if the node immediately following |q| is not a penalty node or a
7692            |rel_noad| or absent entirely. We could combine more here but for beter understanding
7693            we keep the branches seperated. This code is not performance sentitive anyway.
7694
7695            We can actually drop the omit check because we pair by class. 
7696        */
7697        if (state->penalties && node_next(current) && post_penalty <= infinite_penalty && (! boundarylevel || (boundaryfactor != scaling_factor || nestingfactor != scaling_factor))) {
7698            if (boundaryfactor != scaling_factor) {
7699                post_penalty = tex_xn_over_d(post_penalty, boundaryfactor, scaling_factor);
7700            } else if (nestingfactor != scaling_factor && tex_math_has_class_option(current_subtype, obey_nesting_class_option)) {
7701                post_penalty = tex_xn_over_d(post_penalty, nestingfactor, scaling_factor);
7702            }
7703            if (post_penalty < infinite_penalty) {
7704                halfword recent = node_next(current);
7705                recent_type = node_type(recent);
7706                recent_subtype = node_subtype(recent);
7707                /* todo: maybe also check the mainclass of the recent  */
7708                if ((recent_type != penalty_node) && ! (recent_type == simple_noad && tex_math_has_class_option(recent_subtype, omit_penalty_class_option))) {
7709                    halfword penalty = tex_new_penalty_node(post_penalty, math_post_penalty_subtype);
7710                    tex_attach_attribute_list_copy(penalty, current);
7711                    tex_couple_nodes(p, penalty);
7712                    p = penalty;
7713                    if (tracing_math_par >= 2) {
7714                        tex_begin_diagnostic();
7715                        tex_print_format("[math: post penalty, left %n, right %n, amount %i]", recent_subtype, current_subtype, penalty_amount(penalty));
7716                        tex_end_diagnostic();
7717                    }
7718                }
7719            }
7720        }
7721        if (recent_class_overload != unset_noad_class) {
7722            current_type = simple_noad;
7723            current_subtype = recent_class_overload;
7724        }
7725        if (current_type == simple_noad && current_subtype != math_end_class) {
7726            state->endclass = current_subtype;
7727        }
7728        recent_type = current_type;
7729        recent_subtype = current_subtype;
7730        recent_left_slack = current_left_slack;
7731        recent_right_slack = current_right_slack;
7732        recent_script_state = current_script_state;
7733        recent_plus_glyph = current_plus_glyph;
7734        /*tex 
7735            We could zero or even remove the existing slack kern but this is (at least currently) 
7736            nicer for tracing. In fact we could have an (optional) clean up run that removes 
7737            zero kerns and zero glue with no penalties around it. 
7738        */
7739        // if (first && recent_left_slack) {
7740        if (p == temp_head && recent_left_slack) {
7741            halfword kern = tex_new_kern_node(-recent_left_slack, horizontal_math_kern_subtype);
7742            halfword head = node_next(temp_head);
7743            tex_attach_attribute_list_copy(kern, p);
7744            tex_couple_nodes(kern, head);
7745            node_next(temp_head) = kern;
7746            if (tracing_math_par >= 2) {
7747                tex_begin_diagnostic();
7748                tex_print_format("[math: nilling recent left slack %D]", recent_left_slack);
7749                tex_end_diagnostic();
7750            }
7751        }
7752        recent = current;
7753        current = node_next(current);
7754        if (! current && recent_right_slack) {
7755            halfword kern = tex_new_kern_node(-recent_right_slack, horizontal_math_kern_subtype);
7756            tex_attach_attribute_list_copy(kern, p);
7757            tex_couple_nodes(p, kern);
7758            p = kern;
7759            if (tracing_math_par >= 2) {
7760                tex_begin_diagnostic();
7761                tex_print_format("[math: nilling recent right slack %D]", recent_right_slack);
7762                tex_end_diagnostic();
7763            }
7764        }
7765        // first = 0;
7766        /*tex
7767            The m|-|to|-|hlist conversion takes place in|-|place, so the various dependant fields
7768            may not be freed (as would happen if |flush_node| was called). A low|-|level |free_node|
7769            is easier than attempting to nullify such dependant fields for all possible node and
7770            noad types.
7771        */
7772      WIPE:
7773        tex_aux_wipe_noad(recent);
7774    }
7775    if (tracing_math_par >= 3) {
7776        tex_aux_show_math_list("[math: result, level %i]", node_next(temp_head));
7777    }
7778}
7779
7780halfword tex_mlist_to_hlist(halfword mlist, int penalties, int main_style, int beginclass, int endclass, kernset *kerns) /* classes should be quarterwords */
7781{
7782    /*tex
7783        We start with a little housekeeping. There are now only two variables that live across the
7784        two passes. We actually could split this function in two. For practical reasons we have 
7785        collected all relevant state parameters in a structure. The values in there can be adapted 
7786        in this state. 
7787    */
7788    mliststate state;
7789    state.mlist = mlist;
7790    state.penalties = penalties;
7791    state.main_style = main_style;
7792    state.beginclass = beginclass == unset_noad_class ? math_begin_class : beginclass;
7793    state.endclass = endclass == unset_noad_class ? math_end_class : endclass;
7794    state.kerns = kerns;
7795    state.scale = glyph_scale_par;
7796    state.max_height = 0;
7797    state.max_depth = 0;
7798    state.single = 0;
7799    if (state.kerns) { 
7800        tex_math_wipe_kerns(state.kerns);
7801    }
7802    ++lmt_math_state.level;
7803    /*tex
7804        Here we can deal with end_class spacing: we can inject a dummy current atom with no content and
7805        just a class. In fact, we can always add a begin and endclass. A nucleus is kind of mandate. 
7806    */
7807    tex_mlist_to_hlist_set_boundaries(&state);
7808    /*tex
7809        This first pass processes the bodies of radicals so that we can normalize them when height
7810        and/or depth are set.
7811    */
7812    tex_mlist_to_hlist_preroll_radicals(&state);
7813    /*
7814        Make a second pass over the mlist. This is needed in order to get the maximum height and 
7815        depth in order to make fences match.
7816    */
7817    tex_mlist_to_hlist_preroll_dimensions(&state);
7818    /*tex
7819        The fence sizing is done in the third pass. Using a dedicated pass permits experimenting.
7820    */
7821    tex_mlist_to_hlist_size_fences(&state);
7822    /*tex
7823        Make a fourth pass over the mlist; traditionally this was the second pass. We removing all 
7824        noads and insert the proper spacing (glue) and penalties. The binary checking is gone and 
7825        replaced by generic arbitrary inter atom mapping control, so for the hard coded older logic 
7826        one has to check the (development) git repository.
7827
7828        The original comment for this pass is: \quotation {We have now tied up all the loose ends of 
7829        the first pass of |mlist_to_hlist|. The second pass simply goes through and hooks everything 
7830        together with the proper glue and penalties. It also handles the |fence_noad|s that might be 
7831        present, since |max_hl| and |max_d| are now known. Variable |p| points to a node at the 
7832        current end of the final hlist.} However, in \LUAMETATEX\ the fence sizing has already be 
7833        done in the previous pass. 
7834    */
7835    tex_mlist_to_hlist_finalize_list(&state);
7836    /*tex
7837        We're done now and can restore the possibly changed values as well as provide some feedback
7838        about the result.
7839    */
7840    tex_unsave_math_data(cur_level + lmt_math_state.level);
7841    cur_list.math_begin = state.beginclass;
7842    cur_list.math_end = state.endclass;
7843    lmt_math_state.single = state.single;
7844    glyph_scale_par = state.scale;
7845    --lmt_math_state.level;
7846    node_prev(node_next(temp_head)) = null;
7847    return node_next(temp_head);
7848}
7849