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