texlocalboxes.c /size: 14 Kb    last modification: 2024-01-16 10:22
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6
7/*tex
8    The concept of local left and right boxes originates in \OMEGA\ but in \LUATEX\ it already was
9    adapted and made more robust. Here we use an upgraded version with more features. These boxes
10    are sort of a mix between marks (states) and inserts (with dimensions).
11
12    We have linked lists of left or right boxes. This permits selective updating and multiple usage
13    of these boxes. It also means that we need to do additional packing and width calculations.
14
15    When we were in transition local boxes were handled as special boxes (alongside leader and
16    shipout boxes but they got their own cmd again when we were done).
17*/
18
19/*tex
20    Here we set fields in a new par node. We could have an extra width |_par| hut it doesn't really
21    pay off (now).
22*/
23
24inline static scaled tex_aux_local_boxes_width(halfword n)
25{
26    scaled width = 0;
27    while (n) {
28        if (node_type(n) == hlist_node) {
29            width += box_width(n);
30        } else {
31            /*tex Actually this is an error. */
32        }
33        n = node_next(n);
34    }
35    return width;
36}
37
38void tex_add_local_boxes(halfword p)
39{
40    if (local_left_box_par) {
41        halfword copy = tex_copy_node_list(local_left_box_par, null);
42        tex_set_local_left_width(p, tex_aux_local_boxes_width(copy));
43        par_box_left(p) = copy;
44    }
45    if (local_right_box_par) {
46        halfword copy = tex_copy_node_list(local_right_box_par, null);
47        tex_set_local_right_width(p, tex_aux_local_boxes_width(copy));
48        par_box_right(p) = copy;
49    }
50    if (local_middle_box_par) {
51        halfword copy = tex_copy_node_list(local_middle_box_par, null);
52        par_box_middle(p) = copy;
53    }
54}
55
56/*tex
57    Pass on to Lua or inject in the current list. So, we still have a linked list here
58    with only boxes.
59*/
60
61halfword tex_get_local_boxes(halfword location)
62{
63    switch (location) {
64        case local_left_box_code  : return tex_use_local_boxes(local_left_box_par,   local_left_box_code);
65        case local_right_box_code : return tex_use_local_boxes(local_right_box_par,  local_right_box_code);
66        case local_middle_box_code: return tex_use_local_boxes(local_middle_box_par, local_middle_box_code);
67    }
68    return null;
69}
70
71/*tex Set them from Lua, watch out; not an eq update */
72
73void tex_set_local_boxes(halfword b, halfword location)
74{
75    switch (location) {
76        case local_left_box_code  : tex_flush_node_list(local_left_box_par);   local_left_box_par   = b; break;
77        case local_right_box_code : tex_flush_node_list(local_right_box_par);  local_right_box_par  = b; break;
78        case local_middle_box_code: tex_flush_node_list(local_middle_box_par); local_middle_box_par = b; break;
79    }
80}
81
82/*tex Set them from TeX, watch out; this is an eq update */
83
84static halfword tex_aux_reset_boxes(halfword head, halfword index)
85{
86    if (head && index) {
87        halfword current = head;
88        while (current) {
89            halfword next = node_next(current);
90            if (node_type(current) == hlist_node && box_index(current) == index) {
91                if (current == head) {
92                    head = node_next(head);
93                    node_prev(head) = null;
94                    next = head;
95                } else {
96                    tex_try_couple_nodes(node_prev(current), next);
97                }
98                tex_flush_node(current);
99                break;
100            } else {
101                current = next;
102            }
103        }
104        return head;
105    } else {
106        tex_flush_node_list(head);
107        return null;
108    }
109}
110
111void tex_reset_local_boxes(halfword index, halfword location)
112{
113    switch (location) {
114        case local_left_box_code  : local_left_box_par  = tex_aux_reset_boxes(local_left_box_par,   index); break;
115        case local_right_box_code : local_right_box_par = tex_aux_reset_boxes(local_right_box_par,  index); break;
116        case local_middle_box_code: local_right_box_par = tex_aux_reset_boxes(local_middle_box_par, index); break;
117    }
118}
119
120static halfword tex_aux_update_boxes(halfword head, halfword b, halfword index)
121{
122    if (head && index) {
123        halfword current = head;
124        while (current) {
125            halfword next = node_next(current);
126            if (node_type(current) == hlist_node && box_index(current) == index) {
127                tex_try_couple_nodes(b, node_next(current));
128                if (current == head) {
129                    head = b;
130                } else {
131                    tex_couple_nodes(node_prev(current), b);
132                }
133                tex_flush_node(current);
134                break;
135            } else if (next) {
136                current = next;
137            } else {
138                tex_couple_nodes(current, b);
139                break;
140            }
141        }
142        return head;
143    }
144    return b;
145}
146
147void tex_update_local_boxes(halfword b, halfword index, halfword location) /* todo: avoid copying */
148{
149    switch (location) {
150        case local_left_box_code:
151            if (b) {
152                halfword c = local_left_box_par ? tex_copy_node_list(local_left_box_par, null) : null;
153                b = tex_aux_update_boxes(c, b, index);
154            } else if (index) {
155                halfword c = local_left_box_par ? tex_copy_node_list(local_left_box_par, null) : null;
156                b = tex_aux_reset_boxes(c, index);
157            }
158            update_tex_local_left_box(b);
159            break;
160        case local_right_box_code:
161            if (b) {
162                halfword c = local_right_box_par ? tex_copy_node_list(local_right_box_par, null) : null;
163                b = tex_aux_update_boxes(c, b, index);
164            } else if (index) {
165                halfword c = local_right_box_par ? tex_copy_node_list(local_right_box_par, null) : null;
166                b = tex_aux_reset_boxes(c, index);
167            }
168            update_tex_local_right_box(b);
169            break;
170        default:
171            if (b) {
172                halfword c = local_middle_box_par ? tex_copy_node_list(local_middle_box_par, null) : null;
173                b = tex_aux_update_boxes(c, b, index);
174            } else if (index) {
175                halfword c = local_middle_box_par ? tex_copy_node_list(local_middle_box_par, null) : null;
176                b = tex_aux_reset_boxes(c, index);
177            }
178            update_tex_local_middle_box(b);
179            break;
180    }
181}
182
183/*tex The |par| option: */
184
185/* todo: use helper */
186
187static halfword tex_aux_replace_local_box(halfword b, halfword index, halfword par_box)
188{
189    if (b) {
190        halfword c = par_box ? tex_copy_node_list(par_box, null) : null;
191        b = tex_aux_update_boxes(c, b, index);
192    } else if (index) {
193        halfword c = par_box ? tex_copy_node_list(par_box, null) : null;
194        b = tex_aux_reset_boxes(c, index);
195    }
196    if (par_box) {
197        tex_flush_node_list(par_box);
198    }
199    return b;
200}
201
202void tex_replace_local_boxes(halfword par, halfword b, halfword index, halfword location) /* todo: avoid copying */
203{
204    switch (location) {
205        case local_left_box_code:
206            par_box_left(par) = tex_aux_replace_local_box(b, index, par_box_left(par));
207            par_box_left_width(par) = tex_aux_local_boxes_width(b);
208            break;
209        case local_right_box_code:
210            par_box_right(par) = tex_aux_replace_local_box(b, index, par_box_right(par));
211            par_box_right_width(par) = tex_aux_local_boxes_width(b);
212            break;
213        case local_middle_box_code:
214            par_box_middle(par) = tex_aux_replace_local_box(b, index, par_box_middle(par));
215            /*tex We keep the zero width! */
216            break;
217    }
218}
219
220/*tex Get them for line injection. */
221
222halfword tex_use_local_boxes(halfword p, halfword location)
223{
224    if (p) {
225        p = tex_hpack(tex_copy_node_list(p, null), 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
226        switch (location) {
227            case local_left_box_code  : node_subtype(p) = local_left_list  ; break;
228            case local_right_box_code : node_subtype(p) = local_right_list ; break;
229            case local_middle_box_code: node_subtype(p) = local_middle_list; break;
230        }
231    }
232    return p;
233}
234
235/* */
236
237void tex_scan_local_boxes_keys(quarterword *options, halfword *index)
238{
239    *options = 0;
240    *index = 0;
241    while (1) {
242        switch (tex_scan_character("iklpIKLP", 0, 1, 0)) {
243            case 'i': case 'I':
244                if (tex_scan_mandate_keyword("index", 1)) {
245                    *index = tex_scan_box_index();
246                }
247                break;
248            case 'k': case 'K':
249                if (tex_scan_mandate_keyword("keep", 1)) {
250                    *options |= local_box_keep_option;
251                }
252                break;
253            case 'l': case 'L':
254                if (tex_scan_mandate_keyword("local", 1)) {
255                    *options |= local_box_local_option;
256                }
257                break;
258            case 'p': case 'P':
259                if (tex_scan_mandate_keyword("par", 1)) {
260                    *options |= local_box_par_option;
261                }
262                break;
263            default:
264                return;
265        }
266    }
267}
268
269halfword tex_valid_box_index(halfword n)
270{
271    return box_index_in_range(n);
272}
273
274
275scaled tex_get_local_left_width(halfword p)
276{
277    return par_box_left_width(p);
278}
279
280scaled tex_get_local_right_width(halfword p)
281{
282    return par_box_right_width(p);
283}
284
285void tex_set_local_left_width(halfword p, scaled width)
286{
287    par_box_left_width(p) = width;
288}
289
290void tex_set_local_right_width(halfword p, scaled width)
291{
292    par_box_right_width(p) = width;
293}
294
295halfword tex_get_local_interline_penalty(halfword p)
296{
297 // return par_penalty_interline(p);
298    return par_inter_line_penalty(p);
299}
300
301halfword tex_get_local_broken_penalty(halfword p)
302{
303 // return par_penalty_broken(p);
304    return par_broken_penalty(p);
305}
306
307halfword tex_get_local_tolerance(halfword p)
308{
309    return par_tolerance(p);
310}
311
312halfword tex_get_local_pre_tolerance(halfword p)
313{
314    return par_pre_tolerance(p);
315}
316
317void tex_set_local_interline_penalty(halfword p, halfword penalty)
318{
319 // par_penalty_interline(p) = penalty;
320    par_inter_line_penalty(p) = penalty;
321}
322
323void tex_set_local_broken_penalty(halfword p, halfword penalty)
324{
325 // par_penalty_broken(p) = penalty;
326    par_broken_penalty(p) = penalty;
327}
328
329void tex_set_local_tolerance(halfword p, halfword tolerance)
330{
331    par_tolerance(p) = tolerance;
332}
333
334void tex_set_local_pre_tolerance(halfword p, halfword tolerance)
335{
336    par_pre_tolerance(p) = tolerance;
337}
338
339typedef enum saved_localbox_entries {
340    saved_localbox_location_entry = 0,
341    saved_localbox_index_entry    = 0,
342    saved_localbox_options_entry  = 0,
343    saved_localbox_n_of_records   = 1,
344} saved_localbox_entries;
345
346inline static void saved_localbox_initialize(void)
347{
348  saved_type(0) = saved_record_0;
349  saved_record(0) = local_box_save_type;
350}
351
352inline static int saved_localbox_okay(void)
353{
354  return saved_type(0) == saved_record_0 && saved_record(0) == local_box_save_type;
355}
356
357# define saved_localbox_location saved_value_1(saved_localbox_location_entry)
358# define saved_localbox_index    saved_value_2(saved_localbox_index_entry)
359# define saved_localbox_options  saved_value_3(saved_localbox_options_entry)
360
361int tex_show_localbox_record(void)
362{
363    tex_print_str("localbox ");
364    switch (saved_type(0)) { 
365       case saved_record_0:
366            tex_print_format("location %i, index %i, options %i", saved_value_1(0), saved_value_2(0), saved_value_3(0));
367            break;
368        default: 
369            return 0;
370    }
371    return 1;
372}
373
374void tex_aux_scan_local_box(int code) {
375    quarterword options = 0;
376    halfword index = 0;
377    tex_scan_local_boxes_keys(&options, &index);
378    saved_localbox_initialize();
379    saved_localbox_location = code;
380    saved_localbox_index = index;
381    saved_localbox_options = options;
382    lmt_save_state.save_stack_data.ptr += saved_localbox_n_of_records;
383    tex_new_save_level(local_box_group);
384    tex_scan_left_brace();
385    tex_push_nest();
386    cur_list.mode = restricted_hmode;
387    cur_list.space_factor = default_space_factor;
388}
389
390void tex_aux_finish_local_box(void)
391{
392    tex_unsave();
393    lmt_save_state.save_stack_data.ptr -= saved_localbox_n_of_records;
394    if (saved_localbox_okay()) {
395        /* here we could just decrement ptr and then access */
396        halfword location = saved_localbox_location;
397        quarterword options = saved_localbox_options;
398        halfword index = saved_localbox_index;
399        int islocal = (options & local_box_local_option) == local_box_local_option;
400        int keep = (options & local_box_keep_option) == local_box_keep_option;
401        int atpar = (options & local_box_par_option) == local_box_par_option;
402        halfword p = node_next(cur_list.head);
403        tex_pop_nest();
404        if (p) {
405            /*tex Somehow |filtered_hpack| goes beyond the first node so we loose it. */
406            node_prev(p) = null;
407            if (tex_list_has_glyph(p)) {
408                tex_handle_hyphenation(p, null);
409                p = tex_handle_glyphrun(p, local_box_group, text_direction_par);
410            }
411            if (p) {
412                p = lmt_hpack_filter_callback(p, 0, packing_additional, local_box_group, direction_unknown, null);
413            }
414            /*tex
415                We really need something packed so we play safe! This feature is inherited but could
416                have been delegated to a callback anyway.
417            */
418            p = tex_hpack(p, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
419            node_subtype(p) = local_list;
420            box_index(p) = index;
421         // attach_current_attribute_list(p); // leaks
422        }
423        // what to do with reset
424        if (islocal) {
425            /*tex There no copy needed either! */
426        } else {
427            tex_update_local_boxes(p, index, location);
428        }
429        if (cur_mode == hmode || cur_mode == mmode) {
430            if (atpar) {
431                halfword par = tex_find_par_par(cur_list.head);
432                if (par) {
433                    if (p && ! islocal) {
434                        p = tex_copy_node(p);
435                    }
436                    tex_replace_local_boxes(par, p, index, location);
437                }
438            } else {
439                /*tex
440                    We had a null check here but we also want to be able to reset these boxes so we
441                    no longer check.
442                */
443                tex_tail_append(tex_new_par_node(local_box_par_subtype));
444                if (! keep) {
445                    /*tex So we can group and keep it. */
446                    update_tex_internal_par_state(internal_par_state_par + 1);
447                }
448            }
449        }
450    } else {
451        tex_confusion("build local box");
452    }
453}
454