texadjust.c /size: 24 Kb    last modification: 2025-02-21 11:03
1/*
2    See license.txt in the root of this project.
3*/
4# include "luametatex.h"
5
6/*tex 
7
8Traditional \TEX\ only has |\vadjust| that injects its content after the line that it's embedded 
9in. The \PDFTEX\ engine added a |pre| keyword so that we can also inject before a line. Here we
10extend the functionality again. First of all we migrate the pre and post material to the outer 
11vertical level. This goes via dedicated fields in the list nodes. We also have more keywords 
12that control depth before and after the line so that we can get more consistent spacing. Appending 
13and prepending can be controlled too. There is of course more overhead than in traditional \TEX, 
14but it can normally be neglected because |\vadjust| is not used that often. 
15
16*/
17
18typedef struct adjust_properties {
19    halfword options;
20    halfword code;
21    halfword index;
22    scaled   depthbefore;
23    scaled   depthafter;
24    halfword attrlist;
25    scaled   except; 
26    halfword padding;
27} adjust_properties;
28
29typedef enum saved_adjust_entries {
30    saved_adjust_location_entry     = 0,
31    saved_adjust_options_entry      = 0,
32    saved_adjust_index_entry        = 0,
33    saved_adjust_attr_list_entry    = 1,
34    saved_adjust_depth_before_entry = 1,
35    saved_adjust_depth_after_entry  = 1,
36    saved_adjust_except_entry       = 2,
37    saved_adjust_target_entry       = 2,
38    saved_adjust_n_of_records       = 3,
39} saved_adjust_entries;
40
41static inline void saved_adjust_initialize(void)
42{
43    saved_type(0) = saved_record_0;
44    saved_type(1) = saved_record_1;
45    saved_type(2) = saved_record_2;
46    saved_record(0) = adjust_save_type;
47    saved_record(1) = adjust_save_type;
48    saved_record(2) = adjust_save_type;
49}
50
51# define saved_adjust_location     saved_value_1(saved_adjust_location_entry)
52# define saved_adjust_options      saved_value_2(saved_adjust_options_entry)
53# define saved_adjust_index        saved_value_3(saved_adjust_index_entry)
54# define saved_adjust_attr_list    saved_value_1(saved_adjust_attr_list_entry)
55# define saved_adjust_depth_before saved_value_2(saved_adjust_depth_before_entry)
56# define saved_adjust_depth_after  saved_value_3(saved_adjust_depth_after_entry)
57# define saved_adjust_except       saved_value_1(saved_adjust_except_entry)
58# define saved_adjust_target       saved_value_2(saved_adjust_target_entry)
59
60void tex_show_adjust_group(void)
61{
62    tex_print_str_esc("vadjust");
63    if (saved_adjust_location == pre_adjust_code) {
64        tex_print_str(" pre");
65    }
66    if (saved_adjust_options & adjust_option_before) {
67        tex_print_str(" before");
68    }
69}
70
71int tex_show_adjust_record(void)
72{
73    tex_print_str("adjust ");
74    switch (saved_type(0)) { 
75       case saved_record_0:
76            tex_print_format("location %i, options %i, index %i", saved_value_1(0), saved_value_2(0), saved_value_3(0));
77            break;
78       case saved_record_1:
79            tex_print_format("attrlist %i, depth before %p, depth after %p", saved_value_1(0), saved_value_2(0), saved_value_3(0));
80            break;
81       case saved_record_2:
82            tex_print_format("target %i", saved_value_1(0));
83            break;
84       default: 
85            return 0;
86    }
87    return 1;
88}
89
90static void tex_scan_adjust_keys(adjust_properties *properties)
91{
92    properties->code = post_adjust_code;
93    properties->options = adjust_option_none;
94    properties->index = 0;
95    properties->depthbefore = 0;
96    properties->depthafter = 0;
97    properties->attrlist = null;
98    properties->except = 0;
99    while (1) {
100        switch (tex_scan_character("abdeipABDEIP", 0, 1, 0)) {
101            case 'a': case 'A':
102                switch (tex_scan_character("ftFT", 0, 0, 0)) {
103                    case 'f': case 'F':
104                        if (tex_scan_mandate_keyword("after", 2)) {
105                            properties->options &= ~(adjust_option_before | properties->options);
106                        }
107                        break;
108                    case 't': case 'T':
109                        if (tex_scan_mandate_keyword("attr", 2)) {
110                            properties->attrlist = tex_scan_attribute(properties->attrlist);
111                        }
112                        break;
113                    default:
114                        tex_aux_show_keyword_error("after|attr");
115                        goto DONE;
116                }   
117                break;
118            case 'b': case 'B':
119                switch (tex_scan_character("aeAE", 0, 0, 0)) {
120                    case 'a': case 'A':
121                        if (tex_scan_mandate_keyword("baseline", 2)) {
122                            properties->options |= adjust_option_baseline;
123                        }
124                        break;
125                    case 'e': case 'E':
126                        if (tex_scan_mandate_keyword("before", 2)) {
127                            properties->options |= adjust_option_before;
128                        }
129                        break;
130                    default:
131                        tex_aux_show_keyword_error("baseline|before");
132                        goto DONE;
133               }
134                break;
135            case 'd': case 'D':
136                if (tex_scan_mandate_keyword("depth", 1)) {
137                    switch (tex_scan_character("abclABCL", 0, 1, 0)) { /* so a space is permitted */
138                        case 'a': case 'A':
139                            if (tex_scan_mandate_keyword("after", 1)) {
140                                properties->options |= adjust_option_depth_after;
141                                properties->depthafter = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
142                            }
143                            break;
144                        case 'b': case 'B':
145                            if (tex_scan_mandate_keyword("before", 1)) {
146                                properties->options |= adjust_option_depth_before;
147                                properties->depthbefore = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
148                            }
149                            break;
150                        case 'c': case 'C':
151                            if (tex_scan_mandate_keyword("check", 1)) {
152                                properties->options |= adjust_option_depth_check;
153                            }
154                            break;
155                        case 'l': case 'L':
156                            if (tex_scan_mandate_keyword("last", 1)) {
157                                properties->options |= adjust_option_depth_last;
158                            }
159                            break;
160                        default:
161                            tex_aux_show_keyword_error("after|before|check|last");
162                            goto DONE;
163                    }
164                }
165                break;
166            case 'e': case 'E':
167                if (tex_scan_mandate_keyword("except", 1)) {
168                    properties->except = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
169                    properties->options |= adjust_option_except;
170                }
171                break;
172            case 'i': case 'I':
173                if (tex_scan_mandate_keyword("index", 1)) {
174                    properties->index = tex_scan_integer(0, NULL, NULL);
175                    if (! tex_valid_adjust_index(properties->index)) {
176                        properties->index = 0; /* for now no error */
177                    }
178                }
179                break;
180            case 'p': case 'P':
181                switch (tex_scan_character("roRO", 0, 0, 0)) {
182                    case 'r': case 'R':
183                        if (tex_scan_mandate_keyword("pre", 2)) {
184                            properties->code = pre_adjust_code;
185                        }
186                        break;
187                    case 'o': case 'O':
188                        if (tex_scan_mandate_keyword("post", 2)) {
189                            properties->code = post_adjust_code;
190                        }
191                        break;
192                    default:
193                        tex_aux_show_keyword_error("pre|post");
194                        goto DONE;
195                }
196                break;
197            default:
198                goto DONE;
199        }
200    }
201  DONE:
202    if (! properties->attrlist) {
203        properties->attrlist = tex_current_attribute_list();
204        add_attribute_reference(properties->attrlist); /* needs checking */
205    }
206}
207
208int tex_valid_adjust_index(halfword n)
209{
210    return n >= 0;
211}
212
213void tex_set_vadjust(halfword target)
214{
215    adjust_properties properties;
216    tex_scan_adjust_keys(&properties);
217    saved_adjust_initialize();
218    saved_adjust_location = properties.code;
219    saved_adjust_options = properties.options;
220    saved_adjust_index = properties.index;
221    saved_adjust_attr_list = properties.attrlist;
222    saved_adjust_depth_before = properties.depthbefore;
223    saved_adjust_depth_after = properties.depthafter;
224    saved_adjust_except = properties.except;
225    saved_adjust_target = target;
226    lmt_save_state.save_stack_data.ptr += saved_adjust_n_of_records;
227    tex_new_save_level(vadjust_group);
228    tex_scan_left_brace();
229    tex_normal_paragraph(vadjust_par_context);
230    tex_push_nest();
231    cur_list.mode = internal_vmode;
232    cur_list.prev_depth = ignore_depth_criterion_par;
233}
234
235void tex_run_vadjust(void)
236{
237    tex_set_vadjust(-1);
238}
239
240void tex_finish_vadjust_group(void)
241{
242    if (! tex_wrapped_up_paragraph(vadjust_par_context, 0)) {
243        halfword box, adjust, target; /*tex for short-term use */
244        tex_end_paragraph(vadjust_group, vadjust_par_context);
245        tex_unsave();
246        lmt_save_state.save_stack_data.ptr -= saved_adjust_n_of_records;
247        box = tex_vpack(node_next(cur_list.head), 0, packing_additional, max_dimension, direction_unknown, holding_none_option, NULL);
248        tex_pop_nest();
249        adjust = tex_new_node(adjust_node, (quarterword) saved_adjust_location);
250        target = saved_adjust_target;
251        adjust_list(adjust) = box_list(box);
252        adjust_options(adjust) = (halfword) saved_adjust_options;
253        adjust_index(adjust) = (halfword) saved_adjust_index;
254        adjust_depth_before(adjust) = (halfword) saved_adjust_depth_before;
255        adjust_depth_after(adjust) = (halfword) saved_adjust_depth_after;
256        adjust_except(adjust) = (halfword) saved_adjust_except;
257        tex_attach_attribute_list_attribute(adjust, (halfword) saved_adjust_attr_list);
258        if (target < 1) {
259            tex_tail_append(adjust);
260        } else { 
261            tex_adjust_attach(target, adjust);
262        }
263        if (has_adjust_option(adjust, adjust_option_except) && ! adjust_list(adjust)) { 
264            /*tex 
265                We create a simple adjust node. Anything more complex is kind of weird anyway. We 
266                need something in the list in order to make migration work out anyway. 
267            */
268            node_subtype(adjust) = post_adjust_code;
269            adjust_depth_before(adjust) = 0;
270            adjust_depth_after(adjust) = 0;
271            adjust_list(adjust) = tex_new_node(boundary_node, adjust_boundary);
272        }
273        box_list(box) = null;
274        tex_flush_node(box);
275        /*tex We never do the callback ... maybe move it outside. */
276        if (target < 0 && lmt_nest_state.nest_data.ptr == 0) {
277            tex_build_page(vadjust_page_context, 0);
278        }
279    }
280}
281
282/*tex Append or prepend vadjust nodes. Here head is a temp node! */
283
284halfword tex_append_adjust_list(halfword head, halfword tail, halfword adjust, const char *detail)
285{
286    while (adjust && node_type(adjust) == adjust_node) {
287        halfword next = node_next(adjust);
288        if (tail == head) {
289            node_next(head) = adjust;
290        } else {
291            tex_couple_nodes(tail, adjust);
292        }
293        if (tracing_adjusts_par > 1) {
294            tex_begin_diagnostic();
295            tex_print_format("[adjust: index %i, location %s, append, %s]", adjust_index(adjust), tex_aux_subtype_str(adjust), detail);
296            tex_print_node_list(adjust_list(adjust), "adjust",show_box_depth_par, show_box_breadth_par);
297            tex_end_diagnostic();
298        }
299        tail = adjust;
300        adjust = next;
301    }
302    return tail;
303}
304
305halfword tex_prepend_adjust_list(halfword head, halfword tail, halfword adjust, const char *detail)
306{
307    while (adjust && node_type(adjust) == adjust_node) {
308        halfword next = node_next(adjust);
309        if (tail == head) {
310            node_next(head) = adjust;
311            tail = adjust;
312        } else {
313            tex_try_couple_nodes(adjust, node_next(node_next(head)));
314            tex_couple_nodes(node_next(head), adjust);
315        }
316        if (tracing_adjusts_par > 1) {
317            tex_begin_diagnostic();
318            tex_print_format("[adjust: index %i, location %s, prepend, %s]", adjust_index(adjust), tex_aux_subtype_str(adjust), detail);
319            tex_print_node_list(adjust_list(adjust), "adjust", show_box_depth_par, show_box_breadth_par);
320            tex_end_diagnostic();
321        }
322        adjust = next;
323    }
324    return tail;
325}
326
327void tex_inject_adjust_list(halfword adjust, int obeyoptions, halfword nextnode, const line_break_properties *properties)
328{
329    if (adjust && node_type(adjust) == temp_node) {
330        adjust = node_next(adjust);
331    }
332    while (adjust && node_type(adjust) == adjust_node) {
333        halfword next = node_next(adjust);
334        halfword list = adjust_list(adjust);
335        if (tracing_adjusts_par > 1) {
336            tex_begin_diagnostic();
337            tex_print_format("[adjust: index %i, location %s, inject]", adjust_index(adjust), tex_aux_subtype_str(adjust));
338            tex_print_node_list(adjust_list(adjust), "adjust", show_box_depth_par, show_box_breadth_par);
339            tex_end_diagnostic();
340        }
341        if (list) {
342            if (obeyoptions) {
343                /*tex 
344                    We can have an except here which means that we can have a adjust boundary in 
345                    order to get here at all. We need to wipe that one so that it can't interfere
346                    in (for instance) callbacks.
347                */
348                if (node_type(list) == boundary_node && node_subtype(list) == adjust_boundary) {
349                    tex_flush_node_list(list);
350                } else { 
351                    if (has_adjust_option(adjust, adjust_option_baseline)) { 
352                        /*tex
353                            Here we attach data to a line. On the todo is to prepend and append to 
354                            the lines (nicer when we number lines). We could also look at rules. 
355                        */
356                        switch (node_type(list)) { 
357                            case hlist_node:
358                            case vlist_node:
359                                if (nextnode) { 
360                                    /*tex 
361                                        This is the |pre| case where |nextnode| is the line to be appended 
362                                        after the adjust box |list|.
363                                    */
364                                    if (node_type(nextnode) == hlist_node || node_type(nextnode) == vlist_node) {
365                                        if (box_height(nextnode) > box_height(list)) {
366                                            box_height(list) = box_height(nextnode);
367                                        }
368                                        if (box_depth(list) > box_depth(nextnode)) {
369                                            box_depth(nextnode) = box_depth(list);
370                                        }
371                                        /* not ok yet */
372                                        box_y_offset(nextnode) += box_height(nextnode);
373                                        tex_check_box_geometry(nextnode);
374                                        /* till here */
375                                        box_height(nextnode) = 0;
376                                        box_depth(list) = 0;
377                                    }
378                                } else { 
379                                    /*tex 
380                                        Here we have the |post| case where the line will end up before the 
381                                        adjusted content.
382                                    */
383                                    halfword prevnode = cur_list.tail;
384                                    if (node_type(prevnode) == hlist_node || node_type(prevnode) == vlist_node) {
385                                        if (box_height(prevnode) < box_height(list)) {
386                                            box_height(prevnode) = box_height(list);
387                                        }
388                                        if (box_depth(list) < box_depth(prevnode)) {
389                                            box_depth(list) = box_depth(prevnode);
390                                        }
391                                        box_height(list) = 0;
392                                        box_depth(prevnode) = 0;
393                                    }
394                                }
395                                break;
396                        }
397                    }
398                    if (has_adjust_option(adjust, adjust_option_depth_before)) { 
399                        cur_list.prev_depth = adjust_depth_before(adjust);
400                    }
401                    if (has_adjust_option(adjust, adjust_option_depth_check)) { 
402                        tex_append_to_vlist(list, -1, properties);
403                    } else { 
404                        tex_tail_append_list(list);
405                    }
406                    if (has_adjust_option(adjust, adjust_option_depth_after)) { 
407                        cur_list.prev_depth = adjust_depth_after(adjust);
408                    } else if (has_adjust_option(adjust, adjust_option_depth_last)) { 
409                        /*tex We could also look at rules. */
410                        switch (node_type(list)) { 
411                            case hlist_node:
412                            case vlist_node:
413                                cur_list.prev_depth = box_depth(list);
414                                break;
415                        }
416                    }
417                }
418            } else { 
419                tex_tail_append_list(list);
420            }
421            if (! lmt_page_builder_state.output_active) {
422                lmt_append_line_filter_callback(post_adjust_append_line_context, adjust_index(adjust));
423            }
424            adjust_list(adjust) = null;
425        }
426        tex_flush_node(adjust);
427        adjust = next;
428    }
429}
430
431
432void tex_adjust_attach(halfword box, halfword adjust)
433{
434    if (adjust_list(adjust)) {
435        node_prev(adjust) = null;
436        node_next(adjust) = null;
437        if (tracing_adjusts_par > 1) {
438            tex_begin_diagnostic();
439            tex_print_format("[adjust: index %i, location %s, attach]", adjust_index(adjust), tex_aux_subtype_str(adjust));
440            tex_print_node_list(adjust_list(adjust), "attach",show_box_depth_par, show_box_breadth_par);
441            tex_end_diagnostic();
442        }
443        switch (node_subtype(adjust)) {
444            case pre_adjust_code:
445                if (! box_pre_adjusted(box)) {
446                    box_pre_adjusted(box) = adjust;
447                } else if (has_adjust_option(adjust, adjust_option_before)) {
448                    tex_couple_nodes(adjust, box_pre_adjusted(box));
449                    box_pre_adjusted(box) = adjust;
450                } else {
451                    tex_couple_nodes(tex_tail_of_node_list(box_pre_adjusted(box)), adjust);
452                }
453                node_subtype(adjust) = local_adjust_code;
454                break;
455            case post_adjust_code:
456                if (! box_post_adjusted(box)) {
457                    box_post_adjusted(box) = adjust;
458                } else if (has_adjust_option(adjust, adjust_option_before)) {
459                    tex_couple_nodes(adjust, box_post_adjusted(box));
460                    box_post_adjusted(box) = adjust;
461                } else {
462                    tex_couple_nodes(tex_tail_of_node_list(box_post_adjusted(box)), adjust);
463                }
464                node_subtype(adjust) = local_adjust_code;
465                break;
466            case local_adjust_code:
467                tex_normal_error("vadjust post", "unexpected local attach");
468                break;
469        }
470    } else {
471        tex_flush_node(adjust);
472    }
473}
474
475void tex_adjust_passon(halfword box, halfword adjust)
476{
477    halfword head = adjust ? adjust_list(adjust) : null;
478    (void) box;
479    if (head) {
480        node_prev(adjust) = null;
481        node_next(adjust) = null;
482        switch (node_subtype(adjust)) {
483            case pre_adjust_code:
484                if (lmt_packaging_state.pre_adjust_tail) {
485                    if (lmt_packaging_state.pre_adjust_tail != pre_adjust_head && has_adjust_option(adjust, adjust_option_before)) {
486                        lmt_packaging_state.pre_adjust_tail = tex_prepend_adjust_list(pre_adjust_head, lmt_packaging_state.pre_adjust_tail, adjust, "passon");
487                    } else {
488                        lmt_packaging_state.pre_adjust_tail = tex_append_adjust_list(pre_adjust_head, lmt_packaging_state.pre_adjust_tail, adjust, "passon");
489                    }
490                } else {
491                    tex_normal_error("vadjust pre", "invalid list");
492                }
493                break;
494            case post_adjust_code:
495                if (has_adjust_option(adjust, adjust_option_except) && adjust_except(adjust) > lmt_packaging_state.except) { 
496                    lmt_packaging_state.except = adjust_except(adjust);
497                }
498                if (lmt_packaging_state.post_adjust_tail) {
499                    if (lmt_packaging_state.post_adjust_tail != post_adjust_head && has_adjust_option(adjust, adjust_option_before)) {
500                        lmt_packaging_state.post_adjust_tail = tex_prepend_adjust_list(post_adjust_head, lmt_packaging_state.post_adjust_tail, adjust, "passon");
501                    } else {
502                        lmt_packaging_state.post_adjust_tail = tex_append_adjust_list(post_adjust_head, lmt_packaging_state.post_adjust_tail, adjust, "passon");
503                    }
504                } else {
505                    tex_normal_error("vadjust post", "invalid list");
506                }
507                break;
508            case local_adjust_code:
509                tex_normal_error("vadjust post", "unexpected local passon");
510                break;
511        }
512    } else {
513        tex_flush_node(adjust);
514    }
515}
516
517static void tex_aux_show_flush_adjust(halfword adjust, const char *what, const char *detail)
518{
519    if (tracing_adjusts_par > 1) {
520        tex_begin_diagnostic();
521        tex_print_format("[adjust: index %i, location %s, flush, %s]", adjust_index(adjust), tex_aux_subtype_str(adjust), detail);
522        tex_print_node_list(adjust_list(adjust), what, show_box_depth_par, show_box_breadth_par);
523        tex_end_diagnostic();
524    }
525}
526
527halfword tex_flush_adjust_append(halfword adjust, halfword tail)
528{
529    while (adjust) {
530        halfword p = adjust;
531        halfword h = adjust_list(adjust);
532        if (h) {
533            int ishmode = is_h_mode(cur_list.mode);
534            tex_aux_show_flush_adjust(p, "append", ishmode ? "repack" : "direct");
535            if (ishmode) { 
536                halfword n = tex_new_node(adjust_node, post_adjust_code);
537                // tex_attach_attribute_list_copy(n, post_adjusted);
538                adjust_list(n) = h;
539                h = n;
540            }
541            tex_try_couple_nodes(tail, h);
542            tail = tex_tail_of_node_list(h);
543            adjust_list(p) = null;
544        }
545        adjust = node_next(p);
546        tex_flush_node(p);
547    }
548    return tail;
549}
550
551halfword tex_flush_adjust_prepend(halfword adjust, halfword tail)
552{
553    while (adjust) {
554        halfword p = adjust;
555        halfword h = adjust_list(adjust);
556        if (h) {
557            int ishmode = is_h_mode(cur_list.mode);
558            tex_aux_show_flush_adjust(p, "prepend", ishmode ? "repack" : "direct");
559            if (ishmode) { 
560                halfword n = tex_new_node(adjust_node, pre_adjust_code);
561                // tex_attach_attribute_list_copy(n, pre_adjusted);
562                adjust_list(n) = h;
563                h = n;
564            }
565            tex_try_couple_nodes(tail, h);
566            tail = tex_tail_of_node_list(h);
567            adjust_list(p) = null;
568        }
569        adjust = node_next(p);
570        tex_flush_node(p);
571    }
572    return tail;
573}
574
575void tex_initialize_adjust(void)
576{
577}
578
579void tex_cleanup_adjust(void)
580{
581}
582