1% pdfdest.w
2%
3% Copyright 2009-2011 Taco Hoekwater <taco@@luatex.org>
4%
5% This file is part of LuaTeX.
6%
7% LuaTeX is free software; you can redistribute it and/or modify it under
8% the terms of the GNU General Public License as published by the Free
9% Software Foundation; either version 2 of the License, or (at your
10% option) any later version.
11%
12% LuaTeX is distributed in the hope that it will be useful, but WITHOUT
13% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14% FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15% License for more details.
16%
17% You should have received a copy of the GNU General Public License along
18% with LuaTeX; if not, see <http://www.gnu.org/licenses/>.
19
20@ @c
21
22
23#include "ptexlib.h"
24
25@ @c
26#define pdf_dest_margin          dimen_par(pdf_dest_margin_code)
27
28@ Here we implement subroutines for work with objects and related things.
29Some of them are used in former parts too, so we need to declare them
30forward.
31
32@c
33void init_dest_names(PDF pdf)
34{
35    pdf->dest_names_size = inf_dest_names_size;
36    pdf->dest_names = xmallocarray(dest_name_entry, inf_dest_names_size);       /* will grow dynamically */
37}
38
39@ @c
40void append_dest_name(PDF pdf, char *s, int n)
41{
42    int a;
43    if (pdf->dest_names_ptr == sup_dest_names_size)
44        overflow("number of destination names (dest_names_size)",
45                 (unsigned) pdf->dest_names_size);
46    if (pdf->dest_names_ptr == pdf->dest_names_size) {
47        a = pdf->dest_names_size / 5;
48        if (pdf->dest_names_size < sup_dest_names_size - a)
49            pdf->dest_names_size = pdf->dest_names_size + a;
50        else
51            pdf->dest_names_size = sup_dest_names_size;
52        pdf->dest_names =
53            xreallocarray(pdf->dest_names, dest_name_entry,
54                          (unsigned) pdf->dest_names_size);
55    }
56    pdf->dest_names[pdf->dest_names_ptr].objname = xstrdup(s);
57    pdf->dest_names[pdf->dest_names_ptr].objnum = n;
58    pdf->dest_names_ptr++;
59}
60
61
62@ When a destination is created we need to check whether another destination
63with the same identifier already exists and give a warning if needed.
64
65@c
66void warn_dest_dup(int id, small_number byname, const char *s1, const char *s2)
67{
68    pdf_warning(s1, "destination with the same identifier (", false, false);
69    if (byname > 0) {
70        tprint("name");
71        print_mark(id);
72    } else {
73        tprint("num");
74        print_int(id);
75    }
76    tprint(") ");
77    tprint(s2);
78    print_ln();
79    show_context();
80}
81
82
83@ @c
84void do_dest(PDF pdf, halfword p, halfword parent_box, scaledpos cur)
85{
86    scaledpos pos = pdf->posstruct->pos;
87    scaled_whd alt_rule;
88    int k;
89    if (global_shipping_mode == SHIPPING_FORM)
90        pdf_error("ext4", "destinations cannot be inside an XForm");
91    if (doing_leaders)
92        return;
93    k = pdf_get_obj(pdf, obj_type_dest, pdf_dest_id(p), pdf_dest_named_id(p));
94    if (obj_dest_ptr(pdf, k) != null) {
95        warn_dest_dup(pdf_dest_id(p), (small_number) pdf_dest_named_id(p),
96                      "ext4", "has been already used, duplicate ignored");
97        return;
98    }
99    obj_dest_ptr(pdf, k) = p;
100    addto_page_resources(pdf, obj_type_dest, k);
101    alt_rule.wd = width(p);
102    alt_rule.ht = height(p);
103    alt_rule.dp = depth(p);
104    /* the different branches for matrixused is somewhat strange and should always be used  */
105    switch (pdf_dest_type(p)) {
106    case pdf_dest_xyz:
107        if (matrixused())
108            set_rect_dimens(pdf, p, parent_box, cur, alt_rule, pdf_dest_margin);
109        else {
110            pdf_ann_left(p) = pos.h;
111            pdf_ann_top(p) = pos.v;
112        }
113        break;
114    case pdf_dest_fith:
115    case pdf_dest_fitbh:
116        if (matrixused())
117            set_rect_dimens(pdf, p, parent_box, cur, alt_rule, pdf_dest_margin);
118        else
119            pdf_ann_top(p) = pos.v;
120        break;
121    case pdf_dest_fitv:
122    case pdf_dest_fitbv:
123        if (matrixused())
124            set_rect_dimens(pdf, p, parent_box, cur, alt_rule, pdf_dest_margin);
125        else
126            pdf_ann_left(p) = pos.h;
127        break;
128    case pdf_dest_fit:
129    case pdf_dest_fitb:
130        break;
131    case pdf_dest_fitr:
132        set_rect_dimens(pdf, p, parent_box, cur, alt_rule, pdf_dest_margin);
133    }
134}
135
136@ @c
137void write_out_pdf_mark_destinations(PDF pdf)
138{
139    pdf_object_list *k;
140    if ((k = get_page_resources_list(pdf, obj_type_dest)) != NULL) {
141        while (k != NULL) {
142            if (is_obj_written(pdf, k->info)) {
143                pdf_error("ext5",
144                          "destination has been already written (this shouldn't happen)");
145            } else {
146                int i;
147                i = obj_dest_ptr(pdf, k->info);
148                pdf_begin_obj(pdf, k->info, OBJSTM_ALWAYS);
149                if (pdf_dest_named_id(i) > 0) {
150                    pdf_begin_dict(pdf);
151                    pdf_add_name(pdf, "D");
152                }
153                pdf_begin_array(pdf);
154                pdf_add_ref(pdf, pdf->last_page);
155                switch (pdf_dest_type(i)) {
156                case pdf_dest_xyz:
157                    pdf_add_name(pdf, "XYZ");
158                    pdf_add_mag_bp(pdf, pdf_ann_left(i));
159                    pdf_add_mag_bp(pdf, pdf_ann_top(i));
160                    if (pdf_dest_xyz_zoom(i) == null) {
161                        pdf_add_null(pdf);
162                    } else {
163                        if (pdf->cave == 1)
164                            pdf_out(pdf, ' ');
165                        pdf_print_int(pdf, pdf_dest_xyz_zoom(i) / 1000);
166                        pdf_out(pdf, '.');
167                        pdf_print_int(pdf, (pdf_dest_xyz_zoom(i) % 1000));
168                        pdf->cave = 1;
169                    }
170                    break;
171                case pdf_dest_fit:
172                    pdf_add_name(pdf, "Fit");
173                    break;
174                case pdf_dest_fith:
175                    pdf_add_name(pdf, "FitH");
176                    pdf_add_mag_bp(pdf, pdf_ann_top(i));
177                    break;
178                case pdf_dest_fitv:
179                    pdf_add_name(pdf, "FitV");
180                    pdf_add_mag_bp(pdf, pdf_ann_left(i));
181                    break;
182                case pdf_dest_fitb:
183                    pdf_add_name(pdf, "FitB");
184                    break;
185                case pdf_dest_fitbh:
186                    pdf_add_name(pdf, "FitBH");
187                    pdf_add_mag_bp(pdf, pdf_ann_top(i));
188                    break;
189                case pdf_dest_fitbv:
190                    pdf_add_name(pdf, "FitBV");
191                    pdf_add_mag_bp(pdf, pdf_ann_left(i));
192                    break;
193                case pdf_dest_fitr:
194                    pdf_add_name(pdf, "FitR");
195                    pdf_add_rect_spec(pdf, i);
196                    break;
197                default:
198                    pdf_error("ext5", "unknown dest type");
199                    break;
200                }
201                pdf_end_array(pdf);
202                if (pdf_dest_named_id(i) > 0)
203                    pdf_end_dict(pdf);
204                pdf_end_obj(pdf);
205            }
206            k = k->link;
207        }
208    }
209}
210
211@ @c
212void scan_pdfdest(PDF pdf)
213{
214    halfword q;
215    int k;
216    str_number i;
217    scaled_whd alt_rule;
218    q = cur_list.tail_field;
219    new_whatsit(pdf_dest_node);
220    if (scan_keyword("num")) {
221        scan_int();
222        if (cur_val <= 0)
223            pdf_error("ext1", "num identifier must be positive");
224        if (cur_val > max_halfword)
225            pdf_error("ext1", "number too big");
226        set_pdf_dest_id(cur_list.tail_field, cur_val);
227        set_pdf_dest_named_id(cur_list.tail_field, 0);
228    } else if (scan_keyword("name")) {
229        scan_pdf_ext_toks();
230        set_pdf_dest_id(cur_list.tail_field, def_ref);
231        set_pdf_dest_named_id(cur_list.tail_field, 1);
232    } else {
233        pdf_error("ext1", "identifier type missing");
234    }
235    if (scan_keyword("xyz")) {
236        set_pdf_dest_type(cur_list.tail_field, pdf_dest_xyz);
237        if (scan_keyword("zoom")) {
238            scan_int();
239            if (cur_val > max_halfword)
240                pdf_error("ext1", "number too big");
241            set_pdf_dest_xyz_zoom(cur_list.tail_field, cur_val);
242        } else {
243            set_pdf_dest_xyz_zoom(cur_list.tail_field, null);
244        }
245    } else if (scan_keyword("fitbh")) {
246        set_pdf_dest_type(cur_list.tail_field, pdf_dest_fitbh);
247    } else if (scan_keyword("fitbv")) {
248        set_pdf_dest_type(cur_list.tail_field, pdf_dest_fitbv);
249    } else if (scan_keyword("fitb")) {
250        set_pdf_dest_type(cur_list.tail_field, pdf_dest_fitb);
251    } else if (scan_keyword("fith")) {
252        set_pdf_dest_type(cur_list.tail_field, pdf_dest_fith);
253    } else if (scan_keyword("fitv")) {
254        set_pdf_dest_type(cur_list.tail_field, pdf_dest_fitv);
255    } else if (scan_keyword("fitr")) {
256        set_pdf_dest_type(cur_list.tail_field, pdf_dest_fitr);
257    } else if (scan_keyword("fit")) {
258        set_pdf_dest_type(cur_list.tail_field, pdf_dest_fit);
259    } else {
260        pdf_error("ext1", "destination type missing");
261    }
262    /* Scan an optional space */
263    get_x_token();
264    if (cur_cmd != spacer_cmd)
265        back_input();
266
267    if (pdf_dest_type(cur_list.tail_field) == pdf_dest_fitr) {
268        alt_rule = scan_alt_rule();     /* scans |<rule spec>| to |alt_rule| */
269        set_width(cur_list.tail_field, alt_rule.wd);
270        set_height(cur_list.tail_field, alt_rule.ht);
271        set_depth(cur_list.tail_field, alt_rule.dp);
272    }
273    if (pdf_dest_named_id(cur_list.tail_field) != 0) {
274        i = tokens_to_string(pdf_dest_id(cur_list.tail_field));
275        k = find_obj(pdf, obj_type_dest, i, true);
276        flush_str(i);
277    } else {
278        k = find_obj(pdf, obj_type_dest, pdf_dest_id(cur_list.tail_field),
279                     false);
280    }
281    if ((k != 0) && (obj_dest_ptr(pdf, k) != null)) {
282        warn_dest_dup(pdf_dest_id(cur_list.tail_field),
283                      (small_number) pdf_dest_named_id(cur_list.tail_field),
284                      "ext4", "has been already used, duplicate ignored");
285        flush_node_list(cur_list.tail_field);
286        cur_list.tail_field = q;
287        vlink(q) = null;
288    }
289}
290
291@ sorts |dest_names| by names
292@c
293static int dest_cmp(const void *a, const void *b)
294{
295    dest_name_entry aa = *(const dest_name_entry *) a;
296    dest_name_entry bb = *(const dest_name_entry *) b;
297    return strcmp(aa.objname, bb.objname);
298}
299
300@ @c
301void sort_dest_names(PDF pdf)
302{
303    qsort(pdf->dest_names, (size_t) pdf->dest_names_ptr,
304          sizeof(dest_name_entry), dest_cmp);
305}
306
307@ Output the name tree. The tree nature of the destination list forces the
308storing of intermediate data in |obj_info| and |obj_aux| fields, which
309is further uglified by the fact that |obj_tab| entries do not accept char
310pointers.
311
312@c
313int output_name_tree(PDF pdf)
314{
315    boolean is_names = true;    /* flag for name tree output: is it Names or Kids? */
316    int b = 0, j, l;
317    int k = 0;                  /* index of current child of |l|; if |k < pdf_dest_names_ptr|
318                                   then this is pointer to |dest_names| array;
319                                   otherwise it is the pointer to |obj_tab| (object number) */
320    int m;
321    int dests = 0;
322    int names_head = 0, names_tail = 0;
323    if (pdf->dest_names_ptr == 0) {
324        goto DONE;
325    }
326    sort_dest_names(pdf);
327
328    while (true) {
329        do {
330            l = pdf_create_obj(pdf, obj_type_others, 0);        /* create a new node */
331            if (b == 0)
332                b = l;          /* first in this level */
333            if (names_head == 0) {
334                names_head = l;
335                names_tail = l;
336            } else {
337                set_obj_link(pdf, names_tail, l);
338                names_tail = l;
339            }
340            set_obj_link(pdf, names_tail, 0);
341            /* Output the current node in this level */
342            pdf_begin_obj(pdf, l, OBJSTM_ALWAYS);
343            pdf_begin_dict(pdf);
344            j = 0;
345            if (is_names) {
346                set_obj_start(pdf, l, pdf->dest_names[k].objname);
347                pdf_add_name(pdf, "Names");
348                pdf_begin_array(pdf);
349                do {
350                    pdf_add_string(pdf, pdf->dest_names[k].objname);
351                    pdf_add_ref(pdf, pdf->dest_names[k].objnum);
352                    j++;
353                    k++;
354                } while (j != name_tree_kids_max && k != pdf->dest_names_ptr);
355                pdf_end_array(pdf);
356                set_obj_stop(pdf, l, pdf->dest_names[k - 1].objname);   /* for later */
357                if (k == pdf->dest_names_ptr) {
358                    is_names = false;
359                    k = names_head;
360                    b = 0;
361                }
362
363            } else {
364                set_obj_start(pdf, l, obj_start(pdf, k));
365                pdf_add_name(pdf, "Kids");
366                pdf_begin_array(pdf);
367                do {
368                    pdf_add_ref(pdf, k);
369                    set_obj_stop(pdf, l, obj_stop(pdf, k));
370                    k = obj_link(pdf, k);
371                    j++;
372                } while (j != name_tree_kids_max && k != b
373                         && obj_link(pdf, k) != 0);
374                pdf_end_array(pdf);
375                if (k == b)
376                    b = 0;
377            }
378            pdf_add_name(pdf, "Limits");
379            pdf_begin_array(pdf);
380            pdf_add_string(pdf, obj_start(pdf, l));
381            pdf_add_string(pdf, obj_stop(pdf, l));
382            pdf_end_array(pdf);
383            pdf_end_dict(pdf);
384            pdf_end_obj(pdf);
385        } while (b != 0);
386
387        if (k == l) {
388            dests = l;
389            goto DONE;
390        }
391    }
392
393  DONE:
394    if ((dests != 0) || (pdf_names_toks != null)) {
395        m = pdf_create_obj(pdf, obj_type_others, 0);
396        pdf_begin_obj(pdf, m, OBJSTM_ALWAYS);
397        pdf_begin_dict(pdf);
398        if (dests != 0)
399            pdf_dict_add_ref(pdf, "Dests", dests);
400        if (pdf_names_toks != null) {
401            pdf_print_toks(pdf, pdf_names_toks);
402            delete_token_ref(pdf_names_toks);
403            pdf_names_toks = null;
404        }
405        print_pdf_table_string(pdf, "names");
406        pdf_end_dict(pdf);
407        pdf_end_obj(pdf);
408        return m;
409    } else {
410        return 0;
411    }
412}
413