texinserts.c /size: 18 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
9    In traditional \TEX\ inserts are implemented using a quadruple of box, dimen, count and skip
10    registers. This means that the allocate macro |\newinsert| as well as the other allocators
11    have to keep a range of registers free. In \CONTEXT\ (\MKII\ and \MKIV) for instance the
12    indices 132 upto 254 are reserved for that.
13
14    When pondering about improvements this implementation detail always puts some strains on
15    the possible solutions and it is for that reason that an alternative code path is present,
16    one that keep the relevant data in dedicated data structures. When that got implemented all
17    accessors ended up here. Most were already abstracted anyway. For now it means that the old
18    interface still works (and is default). By setting the |\insertmode| to 2 the alternative
19    path is chosen. For practical reasons the first time an insert is used that value gets
20    frozen; a mixed approach was too messy.
21
22    Actually the new variant, which is tagged |class| instead of |index|, also better suits the
23    extended box model. There is access to the basic three dimension but that's all. One can wrap
24    in a box and mess with others but doing that with the boxes inserts makes no sense because
25    the output routine expects simple boxes.
26
27    A side effect is of course that we now have more primitives, starting with |\insert...| and
28    also helpers at the \LUA\ end. A few more will follow and likely some enhancements will show
29    up too.
30
31    In this new mode we also store the floatingpenalty and maxdepth so these can now differ per
32    class. They were already stored in the node, but this way we don't need to set the shared
33    variable every time we do an insert.
34
35*/
36
37insert_state_info lmt_insert_state = {
38    .inserts     = NULL,
39    .insert_data = {
40        .minimum   = min_insert_size,
41        .maximum   = max_insert_size,
42        .size      = memory_data_unset,
43        .step      = stp_insert_size,
44        .allocated = 0,
45        .itemsize  = sizeof(insert_record),
46        .top       = 0,
47        .ptr       = 0,
48        .initial   = memory_data_unset,
49        .offset    = 0,
50    },
51    .mode        = unset_insert_mode,
52    .storing     = 0,
53};
54
55typedef enum saved_insert_entries {
56    saved_insert_index_entry  = 0, /* value_1 */
57    saved_insert_n_of_records = 1,
58} saved_insert_entries;
59
60# define saved_insert_index saved_value_1(saved_insert_index_entry)
61
62inline static void saved_inserts_initialize(void)
63{
64    saved_type(0) = saved_record_0;
65    saved_record(0) = insert_save_type;
66}
67
68void tex_show_insert_group(void)
69{
70    tex_print_str_esc("insert");
71    tex_print_int(saved_insert_index);
72}
73
74int tex_show_insert_record(void)
75{
76    tex_print_str("insert ");
77    switch (save_type(lmt_save_state.save_stack_data.ptr)) { 
78       case saved_record_0:
79            tex_print_format("index %i", saved_insert_index);
80            break;
81        default: 
82            return 0;
83    }
84    return 1;
85}
86
87void tex_initialize_inserts(void)
88{
89    insert_record *tmp = aux_allocate_clear_array(sizeof(insert_record), lmt_insert_state.insert_data.minimum, 1);
90    if (tmp) {
91        lmt_insert_state.inserts = tmp;
92        lmt_insert_state.insert_data.allocated = lmt_insert_state.insert_data.minimum * sizeof(insert_record);
93        lmt_insert_state.insert_data.top = lmt_insert_state.insert_data.minimum;
94        lmt_insert_state.insert_data.ptr = 0;
95    } else {
96        tex_overflow_error("inserts", lmt_insert_state.insert_data.minimum);
97    }
98}
99
100/*tex
101    This one is not sparse but we don't have many inserts so we're okay. I need to check the 0/1
102    offsets here.
103*/
104
105int tex_valid_insert_id(halfword n)
106{
107    switch (lmt_insert_state.mode) {
108        case index_insert_mode:
109            return (n >= 0 && n <= max_box_register_index);
110        case class_insert_mode:
111            if (n <= 0) {
112                tex_handle_error(
113                    normal_error_type,
114                    "In \\insertmode 2 you can't use zero as index.",
115                    NULL
116                );
117            } else if (n <= lmt_insert_state.insert_data.ptr) {
118                return 1;
119            } else if (n < lmt_insert_state.insert_data.top) {
120                lmt_insert_state.insert_data.ptr = n;
121                return 1;
122            } else if (n < lmt_insert_state.insert_data.maximum && lmt_insert_state.insert_data.top < lmt_insert_state.insert_data.maximum) {
123                insert_record *tmp ;
124                int top = n + lmt_insert_state.insert_data.step;
125                if (top > lmt_insert_state.insert_data.maximum) {
126                    top = lmt_insert_state.insert_data.maximum;
127                }
128                tmp = aux_reallocate_array(lmt_insert_state.inserts, sizeof(insert_record), top, 1); // 1 slack
129                if (tmp) {
130                    size_t extra = ((size_t) top - lmt_insert_state.insert_data.top) * sizeof(insert_record);
131                    memset(&tmp[lmt_insert_state.insert_data.top + 1], 0, extra);
132                 // memset(&tmp[lmt_insert_state.insert_data.top], 0, extra);
133                    lmt_insert_state.inserts = tmp;
134                    lmt_insert_state.insert_data.allocated += (int) extra;
135                    lmt_insert_state.insert_data.top = top;
136                    lmt_insert_state.insert_data.ptr = n;
137                    return 1;
138                }
139            }
140            tex_overflow_error("inserts", lmt_insert_state.insert_data.maximum);
141    }
142    return 0;
143}
144
145scaled tex_get_insert_limit(halfword i)
146{
147    if (tex_valid_insert_id(i)) {
148        return lmt_insert_state.mode == index_insert_mode ? insert_maxheight(i) : lmt_insert_state.inserts[i].limit;
149    } else {
150        return 0;
151    }
152}
153
154halfword tex_get_insert_multiplier(halfword i)
155{
156    if (tex_valid_insert_id(i)) {
157        return lmt_insert_state.mode == index_insert_mode ? insert_multiplier(i) : lmt_insert_state.inserts[i].multiplier;
158    } else {
159        return 0;
160    }
161}
162
163halfword tex_get_insert_penalty(halfword i)
164{
165    if (tex_valid_insert_id(i)) {
166        return lmt_insert_state.mode == index_insert_mode ? floating_penalty_par : lmt_insert_state.inserts[i].penalty;
167    } else {
168        return 0;
169    }
170}
171
172halfword tex_get_insert_maxdepth(halfword i)
173{
174    if (tex_valid_insert_id(i)) {
175        return lmt_insert_state.mode == index_insert_mode ? split_max_depth_par : lmt_insert_state.inserts[i].maxdepth;
176    } else {
177        return 0;
178    }
179}
180
181halfword tex_get_insert_distance(halfword i)
182{
183    if (tex_valid_insert_id(i)) {
184        return lmt_insert_state.mode == index_insert_mode ? insert_distance(i) : lmt_insert_state.inserts[i].distance;
185    } else {
186        return 0;
187    }
188}
189
190inline static halfword tex_aux_insert_box(halfword i)
191{
192    if (tex_valid_insert_id(i)) {
193        return lmt_insert_state.mode == index_insert_mode ? insert_content(i) : lmt_insert_state.inserts[i].content;
194    } else {
195        return null;
196    }
197}
198
199scaled tex_get_insert_height(halfword i)
200{
201    halfword b = tex_aux_insert_box(i);
202    return b ? box_height(b) : 0;
203}
204
205scaled tex_get_insert_depth(halfword i)
206{
207    halfword b = tex_aux_insert_box(i);
208    return b ? box_depth(b) : 0;
209}
210
211scaled tex_get_insert_width(halfword i)
212{
213    halfword b = tex_aux_insert_box(i);
214    return b ? box_width(b) : 0;
215}
216
217halfword tex_get_insert_content(halfword i)
218{
219    return tex_aux_insert_box(i);
220}
221
222halfword tex_get_insert_storage(halfword i)
223{
224    if (lmt_insert_state.mode == class_insert_mode && tex_valid_insert_id(i)) {
225        return has_insert_option(i, insert_option_storing);
226    } else {
227        return 0;
228    }
229}
230
231void tex_set_insert_limit(halfword i, scaled v)
232{
233    if (tex_valid_insert_id(i)) {
234        switch (lmt_insert_state.mode) {
235            case index_insert_mode: insert_maxheight(i) = v; break;
236            case class_insert_mode: lmt_insert_state.inserts[i].limit = v; break;
237        }
238    }
239}
240
241void tex_set_insert_multiplier(halfword i, halfword v) {
242    if (tex_valid_insert_id(i)) {
243        switch (lmt_insert_state.mode) {
244            case index_insert_mode: insert_multiplier(i) = v; break;
245            case class_insert_mode: lmt_insert_state.inserts[i].multiplier = v; break;
246        }
247    }
248}
249
250void tex_set_insert_penalty(halfword i, halfword v) {
251    if (tex_valid_insert_id(i) && lmt_insert_state.mode == class_insert_mode) {
252        lmt_insert_state.inserts[i].options = set_insert_option(lmt_insert_state.inserts[i].options, insert_option_penalty);
253        lmt_insert_state.inserts[i].penalty = v;
254    }
255}
256
257void tex_set_insert_maxdepth(halfword i, halfword v) {
258    if (tex_valid_insert_id(i) && lmt_insert_state.mode == class_insert_mode) {
259        lmt_insert_state.inserts[i].options = set_insert_option(lmt_insert_state.inserts[i].options, insert_option_maxdepth);
260        lmt_insert_state.inserts[i].maxdepth = v;
261    }
262}
263
264void tex_set_insert_distance(halfword i, halfword v) {
265    if (tex_valid_insert_id(i)) {
266        int d = null;
267        switch (lmt_insert_state.mode) {
268            case index_insert_mode:
269                d = insert_distance(i);
270                insert_distance(i) = v;
271                break;
272            case class_insert_mode:
273                d = lmt_insert_state.inserts[i].distance;
274                lmt_insert_state.inserts[i].distance = v;
275                break;
276        }
277        tex_flush_node(d);
278    }
279}
280
281void tex_set_insert_height(halfword i, scaled v) {
282    halfword b = tex_aux_insert_box(i);
283    if (b) {
284        box_height(b) = v;
285    }
286}
287
288void tex_set_insert_depth(halfword i, scaled v) {
289    halfword b = tex_aux_insert_box(i);
290    if (b) {
291        box_depth(b) = v;
292    }
293}
294
295void tex_set_insert_width(halfword i, scaled v) {
296    halfword b = tex_aux_insert_box(i);
297    if (b) {
298        box_width(b) = v;
299    }
300}
301
302void tex_set_insert_content(halfword i, halfword v) {
303    switch (lmt_insert_state.mode) {
304        case index_insert_mode: insert_content(i) = v; break;
305        case class_insert_mode: if (tex_valid_insert_id(i)) { lmt_insert_state.inserts[i].content = v; } break;
306    }
307}
308
309void tex_set_insert_storage(halfword i, halfword v)
310{
311    if (lmt_insert_state.mode == class_insert_mode && tex_valid_insert_id(i)) {
312        lmt_insert_state.inserts[i].options = v
313      ? set_insert_option(lmt_insert_state.inserts[i].options, insert_option_storing)
314      : unset_insert_option(lmt_insert_state.inserts[i].options, insert_option_storing);
315    }
316}
317
318void tex_wipe_insert(halfword i) {
319    if (lmt_insert_state.mode == class_insert_mode && i >= 0 && i <= lmt_insert_state.insert_data.ptr) {
320//  if (lmt_insert_state.mode == class_insert_mode && tex_valid_insert_id(i)) {
321        halfword b = lmt_insert_state.inserts[i].content;
322        if (b) {
323            tex_flush_node(b);
324            lmt_insert_state.inserts[i].content = null;
325        }
326    }
327}
328
329halfword lmt_get_insert_distance(halfword i, int slot)
330{
331    int callback_id = lmt_callback_defined(insert_distance_callback);
332    if (callback_id != 0) {
333        halfword replacement = null;
334        lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "dd->N", i, slot, &replacement);
335        if (replacement) {
336            return replacement;
337        } else {
338            halfword distance = null;
339            switch (lmt_insert_state.mode) {
340                case index_insert_mode:
341                    distance = insert_distance(i);
342                    break;
343                case class_insert_mode:
344                    if (tex_valid_insert_id(i)) {
345                        distance = lmt_insert_state.inserts[i].distance;
346                    }
347                    break;
348            }
349            if (distance) {
350                return tex_copy_node(distance);
351            }
352        }
353    }
354    return tex_new_glue_spec_node(null);
355}
356
357halfword tex_get_insert_progress(halfword i)
358{
359    if (tex_valid_insert_id(i)) {
360        halfword p = page_insert_head;
361        while (p && i >= insert_index(node_next(p))) {
362            p = node_next(p);
363            if (p == page_insert_head) {
364                break;
365            }
366        }
367        return insert_index(p) == i ? insert_total_height(p) : 0;
368    } else {
369        return 0;
370    }
371}
372
373/*tex The |class_insert| zero serves as a garbage bin. */
374
375halfword tex_scan_insert_index(void)
376{
377    halfword index = 0;
378    switch (lmt_insert_state.mode) {
379        case unset_insert_mode:
380            lmt_insert_state.mode = index_insert_mode;
381            // fall-through
382        case index_insert_mode:
383            index = tex_scan_box_register_number();
384            if (index == output_box_par) {
385                tex_handle_error(
386                    normal_error_type,
387                    "You can't \\insert%i",
388                    output_box_par,
389                    "I'm changing to \\insert0; box \\outputbox is special."
390                );
391                index = 0;
392            }
393            break;
394        case class_insert_mode:
395            index = tex_scan_integer(0, NULL);
396            if (! tex_valid_insert_id(index)) {
397                index = 0;
398            }
399            break;
400    }
401    return index;
402}
403
404void tex_set_insert_mode(halfword mode)
405{
406    if (lmt_insert_state.mode == unset_insert_mode && (mode == index_insert_mode || mode == class_insert_mode)) {
407        lmt_insert_state.mode = mode;
408    } else if (mode != lmt_insert_state.mode) {
409        tex_handle_error(
410            normal_error_type,
411            "Bad \\insertmode (%i)",
412            mode,
413            "This mode can be set once and has value 1 or 2. It will be automatically\n"
414            "set when \\insert is used."
415        );
416    }
417}
418
419int tex_insert_is_void(halfword i)
420{
421    halfword b = tex_aux_insert_box(i);
422    return (! b) || box_list(b) == null; /*tex So also an empty box test! */
423}
424
425/* playground */
426
427int tex_insert_stored(void)
428{
429    return lmt_insert_state.head != null;
430}
431
432void tex_insert_restore(halfword n)
433{
434    if (lmt_insert_state.tail) {
435        tex_couple_nodes(lmt_insert_state.tail, n);
436    } else {
437        lmt_insert_state.head = n;
438    }
439    lmt_insert_state.tail = n;
440}
441
442void tex_insert_store(halfword i, halfword n)
443{
444    if (tex_get_insert_storage(i)) {
445        tex_insert_restore(n);
446    }
447}
448
449/* not sparse (yet) ... makes no sense (unless we make the list pointers) */
450
451void tex_dump_insert_data(dumpstream f) {
452    dump_int(f, lmt_insert_state.mode);
453    dump_int(f, lmt_insert_state.insert_data.ptr);
454    dump_int(f, lmt_insert_state.insert_data.top);
455    dump_things(f, lmt_insert_state.inserts[0], lmt_insert_state.insert_data.ptr);
456}
457
458void tex_undump_insert_data(dumpstream f) {
459    insert_record *tmp;
460    undump_int(f, lmt_insert_state.mode);
461    undump_int(f, lmt_insert_state.insert_data.ptr);
462    undump_int(f, lmt_insert_state.insert_data.top);
463    tmp = aux_allocate_clear_array(sizeof(insert_record), lmt_insert_state.insert_data.top, 1);
464    if (tmp) {
465        lmt_insert_state.inserts = tmp;
466        lmt_insert_state.insert_data.allocated = lmt_insert_state.insert_data.top * sizeof(insert_record);
467        undump_things(f, lmt_insert_state.inserts[0], lmt_insert_state.insert_data.ptr);
468    } else {
469        tex_overflow_error("inserts", lmt_insert_state.insert_data.top);
470    }
471}
472
473/*tex
474    Inserts, not the easiest mechanism and a candicate for more opening up.
475*/
476
477void tex_run_insert(void)
478{
479    saved_inserts_initialize();
480    saved_insert_index = tex_scan_insert_index();
481    lmt_save_state.save_stack_data.ptr += saved_insert_n_of_records;
482    tex_new_save_level(insert_group);
483    tex_scan_left_brace();
484    tex_normal_paragraph(insert_par_context);
485    tex_push_nest();
486    cur_list.mode = internal_vmode;
487    cur_list.prev_depth = ignore_depth_criterion_par;
488}
489
490void tex_finish_insert_group(void)
491{
492    if (! tex_wrapped_up_paragraph(insert_par_context, 0)) {
493        halfword p, q; /*tex for short-term use */
494        scaled d;      /*tex holds |split_max_depth| in |insert_group| */
495        halfword f;    /*tex holds |floating_penalty| in |insert_group| */
496        tex_end_paragraph(insert_group, insert_par_context);
497        q = tex_new_glue_node(split_top_skip_par, top_skip_code);
498        d = split_max_depth_par;
499        f = floating_penalty_par;
500        tex_unsave();
501        lmt_save_state.save_stack_data.ptr -= saved_insert_n_of_records;
502     // p = tex_vpack(node_next(cur_list.head), 0, packing_additional, max_dimension, direction_unknown);
503     // /* we don't do this: */
504     // /* p = tex_filtered_vpack(node_next(cur_list.head), 0, packing_additional, max_dimension, insert_group, direction_unknown, 0, 0); */
505     // /* because it can induce loops. */
506     // tex_pop_nest();
507        p = node_next(cur_list.head);
508        tex_pop_nest();
509        p = tex_vpack(p, 0, packing_additional, max_dimension, direction_unknown, holding_none_option, NULL);
510        {
511            halfword index = saved_insert_index;
512            halfword insert = tex_new_node(insert_node, 0);
513            halfword maxdepth = tex_get_insert_maxdepth(index);
514            halfword floating = tex_get_insert_penalty(index);
515            if (tex_get_insert_storage(index)) {
516                tex_insert_store(index, insert);
517            } else {
518                tex_tail_append(insert);
519            }
520            /*tex
521                An |\insert| is just a list. We package it because we want to know the height but
522                then discard the wrapper |vlist| node. So the |insert_list| is not packaged.
523            */
524            insert_index(insert) = index;
525            insert_total_height(insert) = box_total(p);
526            insert_list(insert) = box_list(p);
527            insert_split_top(insert) = q;
528            insert_max_depth(insert) = has_insert_option(index, insert_option_maxdepth) ? d : maxdepth;
529            insert_float_cost(insert) = has_insert_option(index, insert_option_penalty) ? f : floating;
530            box_list(p) = null;
531            tex_flush_node(p);
532            if (tracing_inserts_par > 0) {
533                tex_begin_diagnostic();
534                tex_print_levels();
535                tex_print_format("[insert: setting, index %i, height %p, penalty %i]",
536                    index, insert_total_height(insert), insert_float_cost(insert));
537                if (tracing_inserts_par > 1) {
538                    tex_print_node_list(insert_list(insert), "insert", show_box_depth_par, show_box_breadth_par);
539                }
540                tex_end_diagnostic();
541            }
542        }
543        /* we never do the callback ... maybe move it outside */
544        if (lmt_nest_state.nest_data.ptr == 0) {
545            tex_build_page(insert_page_context, 0);
546        }
547    }
548}
549