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