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