Implemented an index. Good _grief_, that was hard work to get all
[sgt/halibut] / bk_paper.c
CommitLineData
43341922 1/*
2 * Paper printing pre-backend for Halibut.
3 *
4 * This module does all the processing common to both PostScript
5 * and PDF output: selecting fonts, line wrapping and page breaking
6 * in accordance with font metrics, laying out the contents and
7 * index pages, generally doing all the page layout. After this,
8 * bk_ps.c and bk_pdf.c should only need to do linear translations
9 * into their literal output format.
10 */
11
12/*
13 * To be done:
14 *
43341922 15 * - index
16 *
a8070b46 17 * - header/footer? Page numbers at least would be handy. Fully
18 * configurable footer can wait, though.
19 *
43341922 20 * That should bring us to the same level of functionality that
21 * original-Halibut had, and the same in PDF plus the obvious
22 * interactive navigation features. After that, in future work:
23 *
24 * - linearised PDF, perhaps?
25 *
26 * - I'm uncertain of whether I need to include a ToUnicode CMap
27 * in each of my font definitions in PDF. Currently things (by
28 * which I mean cut and paste out of acroread) seem to be
29 * working fairly happily without it, but I don't know.
30 *
31 * - configurability
32 *
33 * - title pages
34 */
35
36#include <assert.h>
37#include <stdio.h>
38
39#include "halibut.h"
40#include "paper.h"
41
be76d597 42typedef struct paper_conf_Tag paper_conf;
c6536773 43typedef struct paper_idx_Tag paper_idx;
be76d597 44
45struct paper_conf_Tag {
46 int paper_width;
47 int paper_height;
48 int left_margin;
49 int top_margin;
50 int right_margin;
51 int bottom_margin;
52 int indent_list_bullet;
53 int indent_list;
54 int indent_quote;
55 int base_leading;
56 int base_para_spacing;
57 int chapter_top_space;
58 int sect_num_left_space;
59 int chapter_underline_depth;
60 int chapter_underline_thickness;
61 int rule_thickness;
62 int base_font_size;
2bfd1b76 63 int contents_indent_step;
64 int contents_margin;
65 int leader_separation;
c6536773 66 int index_gutter;
67 int index_cols;
68 int index_minsep;
be76d597 69 /* These are derived from the above */
70 int base_width;
71 int page_height;
c6536773 72 int index_colwidth;
be76d597 73 /* Fonts used in the configuration */
74 font_data *tr, *ti, *hr, *hi, *cr, *co, *cb;
75};
76
c6536773 77struct paper_idx_Tag {
78 /*
79 * Word list giving the page numbers on which this index entry
80 * appears. Also the last word in the list, for ease of
81 * construction.
82 */
83 word *words;
84 word *lastword;
85 /*
86 * The last page added to the list (so we can ensure we don't
87 * add one twice).
88 */
89 page_data *lastpage;
90};
91
43341922 92static font_data *make_std_font(font_list *fontlist, char const *name);
93static void wrap_paragraph(para_data *pdata, word *words,
94 int w, int i1, int i2);
95static page_data *page_breaks(line_data *first, line_data *last,
c6536773 96 int page_height, int ncols, int headspace);
2bfd1b76 97static int render_string(page_data *page, font_data *font, int fontsize,
98 int x, int y, wchar_t *str);
99static int render_line(line_data *ldata, int left_x, int top_y,
c6536773 100 xref_dest *dest, keywordlist *keywords, indexdata *idx);
101static void render_para(para_data *pdata, paper_conf *conf,
102 keywordlist *keywords, indexdata *idx,
103 paragraph *index_placeholder, page_data *index_page);
515d216b 104static int paper_width_simple(para_data *pdata, word *text);
be76d597 105static para_data *code_paragraph(int indent, word *words, paper_conf *conf);
106static para_data *rule_paragraph(int indent, paper_conf *conf);
23765aeb 107static void add_rect_to_page(page_data *page, int x, int y, int w, int h);
2bfd1b76 108static para_data *make_para_data(int ptype, int paux, int indent, int rmargin,
be76d597 109 word *pkwtext, word *pkwtext2, word *pwords,
110 paper_conf *conf);
111static void standard_line_spacing(para_data *pdata, paper_conf *conf);
112static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
113 word *second);
2bfd1b76 114static word *fake_word(wchar_t *text);
c6536773 115static word *fake_space_word(void);
2bfd1b76 116static word *prepare_contents_title(word *first, wchar_t *separator,
117 word *second);
c6536773 118static void fold_into_page(page_data *dest, page_data *src, int right_shift);
43341922 119
120void *paper_pre_backend(paragraph *sourceform, keywordlist *keywords,
121 indexdata *idx) {
122 paragraph *p;
123 document *doc;
2bfd1b76 124 int indent, used_contents;
be76d597 125 para_data *pdata, *firstpara = NULL, *lastpara = NULL;
2bfd1b76 126 para_data *firstcont, *lastcont;
c6536773 127 line_data *firstline, *lastline, *firstcontline, *lastcontline;
43341922 128 page_data *pages;
129 font_list *fontlist;
be76d597 130 paper_conf *conf;
c6536773 131 int has_index;
132 int pagenum;
133 paragraph index_placeholder_para;
134 page_data *first_index_page;
43341922 135
136 /*
137 * FIXME: All these things ought to become configurable.
138 */
be76d597 139 conf = mknew(paper_conf);
140 conf->paper_width = 595 * 4096;
141 conf->paper_height = 841 * 4096;
142 conf->left_margin = 72 * 4096;
143 conf->top_margin = 72 * 4096;
144 conf->right_margin = 72 * 4096;
145 conf->bottom_margin = 108 * 4096;
146 conf->indent_list_bullet = 6 * 4096;
147 conf->indent_list = 24 * 4096;
148 conf->indent_quote = 18 * 4096;
149 conf->base_leading = 4096;
150 conf->base_para_spacing = 10 * 4096;
151 conf->chapter_top_space = 72 * 4096;
152 conf->sect_num_left_space = 12 * 4096;
153 conf->chapter_underline_depth = 14 * 4096;
154 conf->chapter_underline_thickness = 3 * 4096;
155 conf->rule_thickness = 1 * 4096;
156 conf->base_font_size = 12;
2bfd1b76 157 conf->contents_indent_step = 24 * 4096;
158 conf->contents_margin = 84 * 4096;
159 conf->leader_separation = 12 * 4096;
c6536773 160 conf->index_gutter = 36 * 4096;
161 conf->index_cols = 2;
162 conf->index_minsep = 18 * 4096;
be76d597 163
164 conf->base_width =
165 conf->paper_width - conf->left_margin - conf->right_margin;
166 conf->page_height =
167 conf->paper_height - conf->top_margin - conf->bottom_margin;
c6536773 168 conf->index_colwidth =
169 (conf->base_width - (conf->index_cols-1) * conf->index_gutter)
170 / conf->index_cols;
43341922 171
43341922 172 IGNORE(idx); /* FIXME */
43341922 173
174 /*
175 * First, set up some font structures.
176 */
177 fontlist = mknew(font_list);
178 fontlist->head = fontlist->tail = NULL;
be76d597 179 conf->tr = make_std_font(fontlist, "Times-Roman");
180 conf->ti = make_std_font(fontlist, "Times-Italic");
181 conf->hr = make_std_font(fontlist, "Helvetica-Bold");
182 conf->hi = make_std_font(fontlist, "Helvetica-BoldOblique");
183 conf->cr = make_std_font(fontlist, "Courier");
184 conf->co = make_std_font(fontlist, "Courier-Oblique");
185 conf->cb = make_std_font(fontlist, "Courier-Bold");
43341922 186
187 /*
c6536773 188 * Set up a data structure to collect page numbers for each
189 * index entry.
190 */
191 {
192 int i;
193 indexentry *entry;
194
195 has_index = FALSE;
196
197 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
198 paper_idx *pi = mknew(paper_idx);
199
200 has_index = TRUE;
201
202 pi->words = pi->lastword = NULL;
203 pi->lastpage = NULL;
204
205 entry->backend_data = pi;
206 }
207 }
208
209 /*
2bfd1b76 210 * Format the contents entry for each heading.
211 */
212 {
213 word *contents_title;
214 contents_title = fake_word(L"Contents");
215
216 firstcont = make_para_data(para_UnnumberedChapter, 0, 0, 0,
217 NULL, NULL, contents_title, conf);
218 lastcont = firstcont;
219 lastcont->next = NULL;
220 firstcontline = firstcont->first;
221 lastcontline = lastcont->last;
222 for (p = sourceform; p; p = p->next) {
223 word *words;
224 int indent;
225
226 switch (p->type) {
227 case para_Chapter:
228 case para_Appendix:
229 case para_UnnumberedChapter:
230 case para_Heading:
231 case para_Subsect:
232 switch (p->type) {
233 case para_Chapter:
234 case para_Appendix:
235 words = prepare_contents_title(p->kwtext, L": ", p->words);
236 indent = 0;
237 break;
238 case para_UnnumberedChapter:
239 words = prepare_contents_title(NULL, NULL, p->words);
240 indent = 0;
241 break;
242 case para_Heading:
243 case para_Subsect:
244 words = prepare_contents_title(p->kwtext2, L" ", p->words);
245 indent = (p->aux + 1) * conf->contents_indent_step;
246 break;
247 }
248 pdata = make_para_data(para_Normal, p->aux, indent,
249 conf->contents_margin,
250 NULL, NULL, words, conf);
251 pdata->next = NULL;
252 pdata->contents_entry = p;
253 lastcont->next = pdata;
254 lastcont = pdata;
255
256 /*
257 * Link all contents line structures together into
258 * a big list.
259 */
260 if (pdata->first) {
261 if (lastcontline) {
262 lastcontline->next = pdata->first;
263 pdata->first->prev = lastcontline;
264 } else {
265 firstcontline = pdata->first;
266 pdata->first->prev = NULL;
267 }
268 lastcontline = pdata->last;
269 lastcontline->next = NULL;
270 }
271
272 break;
273 }
274 }
c6536773 275
276 /*
277 * And one extra one, for the index.
278 */
279 if (has_index) {
280 pdata = make_para_data(para_Normal, 0, 0,
281 conf->contents_margin,
282 NULL, NULL, fake_word(L"Index"), conf);
283 pdata->next = NULL;
284 pdata->contents_entry = &index_placeholder_para;
285 lastcont->next = pdata;
286 lastcont = pdata;
287
288 if (pdata->first) {
289 if (lastcontline) {
290 lastcontline->next = pdata->first;
291 pdata->first->prev = lastcontline;
292 } else {
293 firstcontline = pdata->first;
294 pdata->first->prev = NULL;
295 }
296 lastcontline = pdata->last;
297 lastcontline->next = NULL;
298 }
299 }
2bfd1b76 300 }
301
302 /*
303 * Do the main paragraph formatting.
43341922 304 */
305 indent = 0;
2bfd1b76 306 used_contents = FALSE;
43341922 307 firstline = lastline = NULL;
308 for (p = sourceform; p; p = p->next) {
309 p->private_data = NULL;
310
311 switch (p->type) {
312 /*
313 * These paragraph types are either invisible or don't
314 * define text in the normal sense. Either way, they
315 * don't require wrapping.
316 */
317 case para_IM:
318 case para_BR:
43341922 319 case para_Biblio:
320 case para_NotParaType:
321 case para_Config:
322 case para_VersionID:
323 case para_NoCite:
324 break;
325
326 /*
327 * These paragraph types don't require wrapping, but
328 * they do affect the line width to which we wrap the
329 * rest of the paragraphs, so we need to pay attention.
330 */
331 case para_LcontPush:
be76d597 332 indent += conf->indent_list; break;
43341922 333 case para_LcontPop:
be76d597 334 indent -= conf->indent_list; assert(indent >= 0); break;
43341922 335 case para_QuotePush:
be76d597 336 indent += conf->indent_quote; break;
43341922 337 case para_QuotePop:
be76d597 338 indent -= conf->indent_quote; assert(indent >= 0); break;
43341922 339
340 /*
341 * This paragraph type is special. Process it
342 * specially.
343 */
344 case para_Code:
be76d597 345 pdata = code_paragraph(indent, p->words, conf);
515d216b 346 p->private_data = pdata;
39a0cfb9 347 if (pdata->first != pdata->last) {
348 pdata->first->penalty_after += 100000;
349 pdata->last->penalty_before += 100000;
350 }
43341922 351 break;
352
353 /*
87bd6353 354 * This paragraph is also special.
355 */
356 case para_Rule:
be76d597 357 pdata = rule_paragraph(indent, conf);
87bd6353 358 p->private_data = pdata;
359 break;
360
361 /*
43341922 362 * All of these paragraph types require wrapping in the
363 * ordinary way. So we must supply a set of fonts, a
364 * line width and auxiliary information (e.g. bullet
365 * text) for each one.
366 */
367 case para_Chapter:
368 case para_Appendix:
369 case para_UnnumberedChapter:
370 case para_Heading:
371 case para_Subsect:
372 case para_Normal:
373 case para_BiblioCited:
374 case para_Bullet:
375 case para_NumberedList:
376 case para_DescribedThing:
377 case para_Description:
378 case para_Copyright:
379 case para_Title:
2bfd1b76 380 pdata = make_para_data(p->type, p->aux, indent, 0,
be76d597 381 p->kwtext, p->kwtext2, p->words, conf);
43341922 382
43341922 383 p->private_data = pdata;
384
515d216b 385 break;
386 }
387
388 if (p->private_data) {
389 pdata = (para_data *)p->private_data;
390
43341922 391 /*
2bfd1b76 392 * If this is the first non-title heading, we link the
393 * contents section in before it.
394 */
395 if (!used_contents && pdata->outline_level > 0) {
396 used_contents = TRUE;
397 if (lastpara)
398 lastpara->next = firstcont;
399 else
400 firstpara = firstcont;
401 lastpara = lastcont;
402 assert(lastpara->next == NULL);
403
404 if (lastline) {
405 lastline->next = firstcontline;
406 firstcontline->prev = lastline;
407 } else {
408 firstline = firstcontline;
409 firstcontline->prev = NULL;
410 }
411 assert(lastcontline != NULL);
412 lastline = lastcontline;
413 lastline->next = NULL;
414 }
415
416 /*
515d216b 417 * Link all line structures together into a big list.
418 */
43341922 419 if (pdata->first) {
420 if (lastline) {
421 lastline->next = pdata->first;
422 pdata->first->prev = lastline;
423 } else {
424 firstline = pdata->first;
425 pdata->first->prev = NULL;
426 }
427 lastline = pdata->last;
2bfd1b76 428 lastline->next = NULL;
43341922 429 }
be76d597 430
431 /*
432 * Link all paragraph structures together similarly.
433 */
434 pdata->next = NULL;
435 if (lastpara)
436 lastpara->next = pdata;
437 else
438 firstpara = pdata;
439 lastpara = pdata;
43341922 440 }
441 }
442
443 /*
444 * Now we have an enormous linked list of every line of text in
445 * the document. Break it up into pages.
446 */
c6536773 447 pages = page_breaks(firstline, lastline, conf->page_height, 0, 0);
43341922 448
449 /*
2bfd1b76 450 * Number the pages.
451 */
452 {
c6536773 453 char buf[40];
2bfd1b76 454 page_data *page;
c6536773 455
456 pagenum = 0;
457
2bfd1b76 458 for (page = pages; page; page = page->next) {
c6536773 459 sprintf(buf, "%d", ++pagenum);
2bfd1b76 460 page->number = ufroma_dup(buf);
461 }
c6536773 462
463 if (has_index) {
464 first_index_page = mknew(page_data);
465 first_index_page->next = first_index_page->prev = NULL;
466 first_index_page->first_line = NULL;
467 first_index_page->last_line = NULL;
468 first_index_page->first_text = first_index_page->last_text = NULL;
469 first_index_page->first_xref = first_index_page->last_xref = NULL;
470 first_index_page->first_rect = first_index_page->last_rect = NULL;
471
472 /* And don't forget the as-yet-uncreated index. */
473 sprintf(buf, "%d", ++pagenum);
474 first_index_page->number = ufroma_dup(buf);
475 }
2bfd1b76 476 }
477
478 /*
43341922 479 * Now we're ready to actually lay out the pages. We do this by
480 * looping over _paragraphs_, since we may need to track cross-
481 * references between lines and even across pages.
482 */
c6536773 483 for (pdata = firstpara; pdata; pdata = pdata->next)
484 render_para(pdata, conf, keywords, idx,
485 &index_placeholder_para, first_index_page);
486
487 /*
488 * Now we've laid out the main body pages, we should have
489 * acquired a full set of page numbers for the index.
490 */
491 if (has_index) {
492 int i;
493 indexentry *entry;
494 word *index_title;
495 para_data *firstidx, *lastidx;
496 line_data *firstidxline, *lastidxline, *ldata;
497 page_data *ipages, *ipages2, *page;
a0768d17 498
c6536773 499 /*
500 * Create a set of paragraphs for the index.
501 */
502 index_title = fake_word(L"Index");
503
504 firstidx = make_para_data(para_UnnumberedChapter, 0, 0, 0,
505 NULL, NULL, index_title, conf);
506 lastidx = firstidx;
507 lastidx->next = NULL;
508 firstidxline = firstidx->first;
509 lastidxline = lastidx->last;
510 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
511 paper_idx *pi = (paper_idx *)entry->backend_data;
512 para_data *text, *pages;
513
514 text = make_para_data(para_Normal, 0, 0,
515 conf->base_width - conf->index_colwidth,
516 NULL, NULL, entry->text, conf);
517
518 pages = make_para_data(para_Normal, 0, 0,
519 conf->base_width - conf->index_colwidth,
520 NULL, NULL, pi->words, conf);
521
522 text->justification = LEFT;
523 pages->justification = RIGHT;
524 text->last->space_after = pages->first->space_before =
525 conf->base_leading / 2;
526
527 pages->last->space_after = text->first->space_before =
528 conf->base_leading;
529
530 assert(text->first);
531 assert(pages->first);
532 assert(lastidxline);
533 assert(lastidx);
a0768d17 534
a0768d17 535 /*
c6536773 536 * If feasible, fold the two halves of the index entry
537 * together.
a0768d17 538 */
c6536773 539 if (text->last->real_shortfall + pages->first->real_shortfall >
540 conf->index_colwidth + conf->index_minsep) {
541 text->last->space_after = -1;
542 pages->first->space_before = -pages->first->line_height+1;
a0768d17 543 }
544
c6536773 545 lastidx->next = text;
546 text->next = pages;
547 pages->next = NULL;
548 lastidx = pages;
549
550 /*
551 * Link all index line structures together into
552 * a big list.
553 */
554 text->last->next = pages->first;
555 pages->first->prev = text->last;
556
557 lastidxline->next = text->first;
558 text->first->prev = lastidxline;
559
560 lastidxline = pages->last;
561
562 /*
563 * Breaking an index entry anywhere is so bad that I
564 * think I'm going to forbid it totally.
565 */
566 for (ldata = text->first; ldata && ldata->next;
567 ldata = ldata->next) {
568 ldata->next->space_before += ldata->space_after + 1;
569 ldata->space_after = -1;
570 }
be76d597 571 }
87bd6353 572
be76d597 573 /*
c6536773 574 * Now break the index into pages.
2bfd1b76 575 */
c6536773 576 ipages = page_breaks(firstidxline, firstidxline, conf->page_height,
577 0, 0);
578 ipages2 = page_breaks(firstidxline->next, lastidxline,
579 conf->page_height,
580 conf->index_cols,
581 firstidxline->space_before +
582 firstidxline->line_height +
583 firstidxline->space_after);
2bfd1b76 584
c6536773 585 /*
586 * This will have put each _column_ of the index on a
587 * separate page, which isn't what we want. Fold the pages
588 * back together.
589 */
590 page = ipages2;
591 while (page) {
592 int i;
593
594 for (i = 1; i < conf->index_cols; i++)
595 if (page->next) {
596 page_data *tpage;
597
598 fold_into_page(page, page->next,
599 i * (conf->index_colwidth +
600 conf->index_gutter));
601 tpage = page->next;
602 page->next = page->next->next;
603 if (page->next)
604 page->next->prev = page;
605 sfree(tpage);
606 }
2bfd1b76 607
c6536773 608 page = page->next;
2bfd1b76 609 }
c6536773 610 /* Also fold the heading on to the same page as the index items. */
611 fold_into_page(ipages, ipages2, 0);
612 ipages->next = ipages2->next;
613 if (ipages->next)
614 ipages->next->prev = ipages;
615 sfree(ipages2);
616 fold_into_page(first_index_page, ipages, 0);
617 first_index_page->next = ipages->next;
618 if (first_index_page->next)
619 first_index_page->next->prev = first_index_page;
620 sfree(ipages);
621 ipages = first_index_page;
2bfd1b76 622
623 /*
c6536773 624 * Number the index pages, except the already-numbered
625 * first one.
be76d597 626 */
c6536773 627 for (page = ipages->next; page; page = page->next) {
628 char buf[40];
629 sprintf(buf, "%d", ++pagenum);
630 page->number = ufroma_dup(buf);
43341922 631 }
c6536773 632
633 /*
634 * Render the index pages.
635 */
636 for (pdata = firstidx; pdata; pdata = pdata->next)
637 render_para(pdata, conf, keywords, idx,
638 &index_placeholder_para, first_index_page);
639
640 /*
641 * Link the index page list on to the end of the main page
642 * list.
643 */
644 if (!pages)
645 pages = ipages;
646 else {
647 for (page = pages; page->next; page = page->next);
648 page->next = ipages;
649 }
650
651 /*
652 * Same with the paragraph list, which will cause the index
653 * to be mentioned in the document outline.
654 */
655 if (!firstpara)
656 firstpara = firstidx;
657 else
658 lastpara->next = firstidx;
659 lastpara = lastidx;
43341922 660 }
661
f0e51ce1 662 /*
663 * Start putting together the overall document structure we're
664 * going to return.
665 */
43341922 666 doc = mknew(document);
667 doc->fonts = fontlist;
668 doc->pages = pages;
be76d597 669 doc->paper_width = conf->paper_width;
670 doc->paper_height = conf->paper_height;
f0e51ce1 671
672 /*
673 * Collect the section heading paragraphs into a document
674 * outline. This is slightly fiddly because the Title paragraph
675 * isn't required to be at the start, although all the others
676 * must be in order.
677 */
678 {
679 int osize = 20;
680
681 doc->outline_elements = mknewa(outline_element, osize);
682 doc->n_outline_elements = 0;
683
684 /* First find the title. */
be76d597 685 for (pdata = firstpara; pdata; pdata = pdata->next) {
686 if (pdata->outline_level == 0) {
f0e51ce1 687 doc->outline_elements[0].level = 0;
be76d597 688 doc->outline_elements[0].pdata = pdata;
f0e51ce1 689 doc->n_outline_elements++;
690 break;
691 }
692 }
693
694 /* Then collect the rest. */
be76d597 695 for (pdata = firstpara; pdata; pdata = pdata->next) {
696 if (pdata->outline_level > 0) {
f0e51ce1 697 if (doc->n_outline_elements >= osize) {
698 osize += 20;
699 doc->outline_elements =
700 resize(doc->outline_elements, osize);
701 }
702
be76d597 703 doc->outline_elements[doc->n_outline_elements].level =
704 pdata->outline_level;
705 doc->outline_elements[doc->n_outline_elements].pdata = pdata;
f0e51ce1 706 doc->n_outline_elements++;
f0e51ce1 707 }
708 }
709 }
710
be76d597 711 sfree(conf);
712
43341922 713 return doc;
714}
715
2bfd1b76 716static para_data *make_para_data(int ptype, int paux, int indent, int rmargin,
be76d597 717 word *pkwtext, word *pkwtext2, word *pwords,
718 paper_conf *conf)
719{
720 para_data *pdata;
721 line_data *ldata;
722 int extra_indent, firstline_indent, aux_indent;
723 word *aux, *aux2;
724
725 pdata = mknew(para_data);
726 pdata->outline_level = -1;
727 pdata->outline_title = NULL;
728 pdata->rect_type = RECT_NONE;
2bfd1b76 729 pdata->contents_entry = NULL;
c6536773 730 pdata->justification = JUST;
be76d597 731
732 /*
733 * Choose fonts for this paragraph.
734 *
735 * FIXME: All of this ought to be completely
736 * user-configurable.
737 */
738 switch (ptype) {
739 case para_Title:
740 pdata->fonts[FONT_NORMAL] = conf->hr;
741 pdata->sizes[FONT_NORMAL] = 24;
742 pdata->fonts[FONT_EMPH] = conf->hi;
743 pdata->sizes[FONT_EMPH] = 24;
744 pdata->fonts[FONT_CODE] = conf->cb;
745 pdata->sizes[FONT_CODE] = 24;
746 pdata->outline_level = 0;
747 break;
748
749 case para_Chapter:
750 case para_Appendix:
751 case para_UnnumberedChapter:
752 pdata->fonts[FONT_NORMAL] = conf->hr;
753 pdata->sizes[FONT_NORMAL] = 20;
754 pdata->fonts[FONT_EMPH] = conf->hi;
755 pdata->sizes[FONT_EMPH] = 20;
756 pdata->fonts[FONT_CODE] = conf->cb;
757 pdata->sizes[FONT_CODE] = 20;
758 pdata->outline_level = 1;
759 break;
760
761 case para_Heading:
762 case para_Subsect:
763 pdata->fonts[FONT_NORMAL] = conf->hr;
764 pdata->fonts[FONT_EMPH] = conf->hi;
765 pdata->fonts[FONT_CODE] = conf->cb;
766 pdata->sizes[FONT_NORMAL] =
767 pdata->sizes[FONT_EMPH] =
768 pdata->sizes[FONT_CODE] =
769 (paux == 0 ? 16 : paux == 1 ? 14 : 13);
770 pdata->outline_level = 2 + paux;
771 break;
772
773 case para_Normal:
774 case para_BiblioCited:
775 case para_Bullet:
776 case para_NumberedList:
777 case para_DescribedThing:
778 case para_Description:
779 case para_Copyright:
780 pdata->fonts[FONT_NORMAL] = conf->tr;
781 pdata->sizes[FONT_NORMAL] = 12;
782 pdata->fonts[FONT_EMPH] = conf->ti;
783 pdata->sizes[FONT_EMPH] = 12;
784 pdata->fonts[FONT_CODE] = conf->cr;
785 pdata->sizes[FONT_CODE] = 12;
786 break;
787 }
788
789 /*
790 * Also select an indentation level depending on the
791 * paragraph type (list paragraphs other than
792 * para_DescribedThing need extra indent).
793 *
794 * (FIXME: Perhaps at some point we might even arrange
795 * for the user to be able to request indented first
796 * lines in paragraphs.)
797 */
798 if (ptype == para_Bullet ||
799 ptype == para_NumberedList ||
800 ptype == para_Description) {
801 extra_indent = firstline_indent = conf->indent_list;
802 } else {
803 extra_indent = firstline_indent = 0;
804 }
805
806 /*
807 * Find the auxiliary text for this paragraph.
808 */
809 aux = aux2 = NULL;
810 aux_indent = 0;
811
812 switch (ptype) {
813 case para_Chapter:
814 case para_Appendix:
815 case para_Heading:
816 case para_Subsect:
817 /*
818 * For some heading styles (FIXME: be able to
819 * configure which), the auxiliary text contains
820 * the chapter number and is arranged to be
821 * right-aligned a few points left of the primary
822 * margin. For other styles, the auxiliary text is
823 * the full chapter _name_ and takes up space
824 * within the (wrapped) chapter title, meaning that
825 * we must move the first line indent over to make
826 * space for it.
827 */
828 if (ptype == para_Heading || ptype == para_Subsect) {
829 int len;
830
831 aux = pkwtext2;
832 len = paper_width_simple(pdata, pkwtext2);
833 aux_indent = -len - conf->sect_num_left_space;
834
835 pdata->outline_title =
836 prepare_outline_title(pkwtext2, L" ", pwords);
837 } else {
838 aux = pkwtext;
2bfd1b76 839 aux2 = fake_word(L": ");
be76d597 840 aux_indent = 0;
841
842 firstline_indent += paper_width_simple(pdata, aux);
843 firstline_indent += paper_width_simple(pdata, aux2);
844
845 pdata->outline_title =
846 prepare_outline_title(pkwtext, L": ", pwords);
847 }
848 break;
849
850 case para_Bullet:
851 /*
852 * Auxiliary text consisting of a bullet. (FIXME:
853 * configurable bullet.)
854 */
2bfd1b76 855 aux = fake_word(L"\x2022");
be76d597 856 aux_indent = indent + conf->indent_list_bullet;
857 break;
858
859 case para_NumberedList:
860 /*
861 * Auxiliary text consisting of the number followed
862 * by a (FIXME: configurable) full stop.
863 */
864 aux = pkwtext;
2bfd1b76 865 aux2 = fake_word(L".");
be76d597 866 aux_indent = indent + conf->indent_list_bullet;
867 break;
868
869 case para_BiblioCited:
870 /*
871 * Auxiliary text consisting of the bibliography
872 * reference text, and a trailing space.
873 */
874 aux = pkwtext;
2bfd1b76 875 aux2 = fake_word(L" ");
be76d597 876 aux_indent = indent;
877 firstline_indent += paper_width_simple(pdata, aux);
878 firstline_indent += paper_width_simple(pdata, aux2);
879 break;
880 }
881
882 if (pdata->outline_level >= 0 && !pdata->outline_title) {
883 pdata->outline_title =
884 prepare_outline_title(NULL, NULL, pwords);
885 }
886
2bfd1b76 887 wrap_paragraph(pdata, pwords, conf->base_width - rmargin,
be76d597 888 indent + firstline_indent,
889 indent + extra_indent);
890
891 pdata->first->aux_text = aux;
892 pdata->first->aux_text_2 = aux2;
893 pdata->first->aux_left_indent = aux_indent;
894
895 /*
896 * Line breaking penalties.
897 */
898 switch (ptype) {
899 case para_Chapter:
900 case para_Appendix:
901 case para_Heading:
902 case para_Subsect:
903 case para_UnnumberedChapter:
904 /*
905 * Fixed and large penalty for breaking straight
906 * after a heading; corresponding bonus for
907 * breaking straight before.
908 */
909 pdata->first->penalty_before = -500000;
910 pdata->last->penalty_after = 500000;
911 for (ldata = pdata->first; ldata; ldata = ldata->next)
912 ldata->penalty_after = 500000;
913 break;
914
915 case para_DescribedThing:
916 /*
917 * This is treated a bit like a small heading:
918 * there's a penalty for breaking after it (i.e.
919 * between it and its description), and a bonus for
920 * breaking before it (actually _between_ list
921 * items).
922 */
923 pdata->first->penalty_before = -200000;
924 pdata->last->penalty_after = 200000;
925 break;
926
927 default:
928 /*
929 * Most paragraph types: widow/orphan control by
930 * discouraging breaking one line from the end of
931 * any paragraph.
932 */
933 if (pdata->first != pdata->last) {
934 pdata->first->penalty_after = 100000;
935 pdata->last->penalty_before = 100000;
936 }
937 break;
938 }
939
940 standard_line_spacing(pdata, conf);
941
942 /*
943 * Some kinds of section heading require a page break before
944 * them and an underline after.
945 */
946 if (ptype == para_Title ||
947 ptype == para_Chapter ||
948 ptype == para_Appendix ||
949 ptype == para_UnnumberedChapter) {
950 pdata->first->page_break = TRUE;
951 pdata->first->space_before = conf->chapter_top_space;
952 pdata->last->space_after +=
953 (conf->chapter_underline_depth +
954 conf->chapter_underline_thickness);
955 pdata->rect_type = RECT_CHAPTER_UNDERLINE;
956 }
957
958 return pdata;
959}
960
961static void standard_line_spacing(para_data *pdata, paper_conf *conf)
962{
963 line_data *ldata;
964
965 /*
966 * Set the line spacing for each line in this paragraph.
967 */
968 for (ldata = pdata->first; ldata; ldata = ldata->next) {
969 if (ldata == pdata->first)
970 ldata->space_before = conf->base_para_spacing / 2;
971 else
972 ldata->space_before = conf->base_leading / 2;
973 if (ldata == pdata->last)
974 ldata->space_after = conf->base_para_spacing / 2;
975 else
976 ldata->space_after = conf->base_leading / 2;
977 ldata->page_break = FALSE;
978 }
979}
980
43341922 981static font_encoding *new_font_encoding(font_data *font)
982{
983 font_encoding *fe;
984 int i;
985
986 fe = mknew(font_encoding);
987 fe->next = NULL;
988
989 if (font->list->tail)
990 font->list->tail->next = fe;
991 else
992 font->list->head = fe;
993 font->list->tail = fe;
994
995 fe->font = font;
996 fe->free_pos = 0x21;
997
998 for (i = 0; i < 256; i++) {
999 fe->vector[i] = NULL;
1000 fe->indices[i] = -1;
1001 fe->to_unicode[i] = 0xFFFF;
1002 }
1003
1004 return fe;
1005}
1006
1007static font_data *make_std_font(font_list *fontlist, char const *name)
1008{
1009 const int *widths;
1010 int nglyphs;
1011 font_data *f;
1012 font_encoding *fe;
1013 int i;
1014
1015 widths = ps_std_font_widths(name);
1016 if (!widths)
1017 return NULL;
1018
1019 for (nglyphs = 0; ps_std_glyphs[nglyphs] != NULL; nglyphs++);
1020
1021 f = mknew(font_data);
1022
1023 f->list = fontlist;
1024 f->name = name;
1025 f->nglyphs = nglyphs;
1026 f->glyphs = ps_std_glyphs;
1027 f->widths = widths;
1028 f->subfont_map = mknewa(subfont_map_entry, nglyphs);
1029
1030 /*
1031 * Our first subfont will contain all of US-ASCII. This isn't
1032 * really necessary - we could just create custom subfonts
1033 * precisely as the whim of render_string dictated - but
1034 * instinct suggests that it might be nice to have the text in
1035 * the output files look _marginally_ recognisable.
1036 */
1037 fe = new_font_encoding(f);
1038 fe->free_pos = 0xA1; /* only the top half is free */
1039 f->latest_subfont = fe;
1040
1041 for (i = 0; i < (int)lenof(f->bmp); i++)
1042 f->bmp[i] = 0xFFFF;
1043
1044 for (i = 0; i < nglyphs; i++) {
1045 wchar_t ucs;
1046 ucs = ps_glyph_to_unicode(f->glyphs[i]);
1047 assert(ucs != 0xFFFF);
1048 f->bmp[ucs] = i;
1049 if (ucs >= 0x20 && ucs <= 0x7E) {
1050 fe->vector[ucs] = f->glyphs[i];
1051 fe->indices[ucs] = i;
1052 fe->to_unicode[ucs] = ucs;
1053 f->subfont_map[i].subfont = fe;
1054 f->subfont_map[i].position = ucs;
1055 } else {
1056 /*
1057 * This character is not yet assigned to a subfont.
1058 */
1059 f->subfont_map[i].subfont = NULL;
1060 f->subfont_map[i].position = 0;
1061 }
1062 }
1063
1064 return f;
1065}
1066
1067static int string_width(font_data *font, wchar_t const *string, int *errs)
1068{
1069 int width = 0;
1070
1071 if (errs)
1072 *errs = 0;
1073
1074 for (; *string; string++) {
1075 int index;
1076
1077 index = font->bmp[(unsigned short)*string];
1078 if (index == 0xFFFF) {
1079 if (errs)
1080 *errs = 1;
1081 } else {
1082 width += font->widths[index];
1083 }
1084 }
1085
1086 return width;
1087}
1088
faad4952 1089static int paper_width_internal(void *vctx, word *word, int *nspaces);
43341922 1090
1091struct paper_width_ctx {
1092 int minspacewidth;
1093 para_data *pdata;
1094};
1095
faad4952 1096static int paper_width_list(void *vctx, word *text, word *end, int *nspaces) {
43341922 1097 int w = 0;
faad4952 1098 while (text && text != end) {
1099 w += paper_width_internal(vctx, text, nspaces);
43341922 1100 text = text->next;
1101 }
1102 return w;
1103}
1104
faad4952 1105static int paper_width_internal(void *vctx, word *word, int *nspaces)
43341922 1106{
1107 struct paper_width_ctx *ctx = (struct paper_width_ctx *)vctx;
1108 int style, type, findex, width, errs;
1109 wchar_t *str;
1110
1111 switch (word->type) {
1112 case word_HyperLink:
1113 case word_HyperEnd:
1114 case word_UpperXref:
1115 case word_LowerXref:
1116 case word_XrefEnd:
1117 case word_IndexRef:
1118 return 0;
1119 }
1120
1121 style = towordstyle(word->type);
1122 type = removeattr(word->type);
1123
1124 findex = (style == word_Normal ? FONT_NORMAL :
1125 style == word_Emph ? FONT_EMPH :
1126 FONT_CODE);
1127
1128 if (type == word_Normal) {
1129 str = word->text;
1130 } else if (type == word_WhiteSpace) {
faad4952 1131 if (findex != FONT_CODE) {
1132 if (nspaces)
1133 (*nspaces)++;
43341922 1134 return ctx->minspacewidth;
faad4952 1135 } else
43341922 1136 str = L" ";
1137 } else /* if (type == word_Quote) */ {
1138 if (word->aux == quote_Open)
1139 str = L"\x2018"; /* FIXME: configurability! */
1140 else
1141 str = L"\x2019"; /* FIXME: configurability! */
1142 }
1143
1144 width = string_width(ctx->pdata->fonts[findex], str, &errs);
1145
1146 if (errs && word->alt)
faad4952 1147 return paper_width_list(vctx, word->alt, NULL, nspaces);
43341922 1148 else
1149 return ctx->pdata->sizes[findex] * width;
1150}
1151
faad4952 1152static int paper_width(void *vctx, word *word)
1153{
1154 return paper_width_internal(vctx, word, NULL);
1155}
1156
515d216b 1157static int paper_width_simple(para_data *pdata, word *text)
1158{
1159 struct paper_width_ctx ctx;
1160
1161 ctx.pdata = pdata;
1162 ctx.minspacewidth =
1163 (pdata->sizes[FONT_NORMAL] *
1164 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
1165
1166 return paper_width_list(&ctx, text, NULL, NULL);
1167}
1168
43341922 1169static void wrap_paragraph(para_data *pdata, word *words,
1170 int w, int i1, int i2)
1171{
1172 wrappedline *wrapping, *p;
1173 int spacewidth;
1174 struct paper_width_ctx ctx;
1175 int line_height;
1176
1177 /*
1178 * We're going to need to store the line height in every line
1179 * structure we generate.
1180 */
1181 {
1182 int i;
1183 line_height = 0;
1184 for (i = 0; i < NFONTS; i++)
1185 if (line_height < pdata->sizes[i])
1186 line_height = pdata->sizes[i];
1187 line_height *= 4096;
1188 }
1189
1190 spacewidth = (pdata->sizes[FONT_NORMAL] *
1191 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
1192 if (spacewidth == 0) {
1193 /*
1194 * A font without a space?! Disturbing. I hope this never
1195 * comes up, but I'll make a random guess anyway and set my
1196 * space width to half the point size.
1197 */
1198 spacewidth = pdata->sizes[FONT_NORMAL] * 4096 / 2;
1199 }
1200
1201 /*
1202 * I'm going to set the _minimum_ space width to 3/5 of the
1203 * standard one, and use the standard one as the optimum.
1204 */
1205 ctx.minspacewidth = spacewidth * 3 / 5;
1206 ctx.pdata = pdata;
1207
1208 wrapping = wrap_para(words, w - i1, w - i2, paper_width, &ctx, spacewidth);
1209
1210 /*
1211 * Having done the wrapping, we now concoct a set of line_data
1212 * structures.
1213 */
1214 pdata->first = pdata->last = NULL;
1215
1216 for (p = wrapping; p; p = p->next) {
1217 line_data *ldata;
1218 word *wd;
1219 int len, wid, spaces;
1220
1221 ldata = mknew(line_data);
1222
1223 ldata->pdata = pdata;
1224 ldata->first = p->begin;
faad4952 1225 ldata->end = p->end;
43341922 1226 ldata->line_height = line_height;
1227
1228 ldata->xpos = (p == wrapping ? i1 : i2);
1229
1230 if (pdata->last) {
1231 pdata->last->next = ldata;
1232 ldata->prev = pdata->last;
1233 } else {
1234 pdata->first = ldata;
1235 ldata->prev = NULL;
1236 }
1237 ldata->next = NULL;
1238 pdata->last = ldata;
1239
43341922 1240 spaces = 0;
faad4952 1241 len = paper_width_list(&ctx, ldata->first, ldata->end, &spaces);
1242 wid = (p == wrapping ? w - i1 : w - i2);
43341922 1243 wd = ldata->first;
43341922 1244
faad4952 1245 ldata->hshortfall = wid - len;
1246 ldata->nspaces = spaces;
1247 /*
1248 * This tells us how much the space width needs to
1249 * change from _min_spacewidth. But we want to store
1250 * its difference from the _natural_ space width, to
1251 * make the text rendering easier.
1252 */
1253 ldata->hshortfall += ctx.minspacewidth * spaces;
1254 ldata->hshortfall -= spacewidth * spaces;
c6536773 1255 ldata->real_shortfall = ldata->hshortfall;
faad4952 1256 /*
1257 * Special case: on the last line of a paragraph, we
1258 * never stretch spaces.
1259 */
1260 if (ldata->hshortfall > 0 && !p->next)
1261 ldata->hshortfall = 0;
43341922 1262
1263 ldata->aux_text = NULL;
515d216b 1264 ldata->aux_text_2 = NULL;
43341922 1265 ldata->aux_left_indent = 0;
39a0cfb9 1266 ldata->penalty_before = ldata->penalty_after = 0;
43341922 1267 }
1268
1269}
1270
1271static page_data *page_breaks(line_data *first, line_data *last,
c6536773 1272 int page_height, int ncols, int headspace)
43341922 1273{
1274 line_data *l, *m;
1275 page_data *ph, *pt;
c6536773 1276 int n, n1, this_height;
43341922 1277
1278 /*
1279 * Page breaking is done by a close analogue of the optimal
1280 * paragraph wrapping algorithm used by wrap_para(). We work
1281 * backwards from the end of the document line by line; for
1282 * each line, we contemplate every possible number of lines we
1283 * could put on a page starting with that line, determine a
1284 * cost function for each one, add it to the pre-computed cost
1285 * function for optimally page-breaking everything after that
1286 * page, and pick the best option.
1287 *
c6536773 1288 * This is made slightly more complex by the fact that we have
1289 * a multi-column index with a heading at the top of the
1290 * _first_ page, meaning that the first _ncols_ pages must have
1291 * a different length. Hence, we must do the wrapping ncols+1
1292 * times over, hypothetically trying to put every subsequence
1293 * on every possible page.
1294 *
43341922 1295 * Since my line_data structures are only used for this
1296 * purpose, I might as well just store the algorithm data
1297 * directly in them.
1298 */
1299
1300 for (l = last; l; l = l->prev) {
c6536773 1301 l->bestcost = mknewa(int, ncols+1);
1302 l->vshortfall = mknewa(int, ncols+1);
1303 l->text = mknewa(int, ncols+1);
1304 l->space = mknewa(int, ncols+1);
1305 l->page_last = mknewa(line_data *, ncols+1);
1306
1307 for (n = 0; n <= ncols; n++) {
1308 int minheight, text = 0, space = 0;
1309 int cost;
1310
1311 n1 = (n < ncols ? n+1 : ncols);
1312 if (n < ncols)
1313 this_height = page_height - headspace;
1314 else
1315 this_height = page_height;
1316
1317 l->bestcost[n] = -1;
1318 for (m = l; m; m = m->next) {
1319 if (m != l && m->page_break)
1320 break; /* we've gone as far as we can */
1321
1322 if (m != l) {
1323 if (m->prev->space_after > 0)
1324 space += m->prev->space_after;
1325 else
1326 text += m->prev->space_after;
1327 }
1328 if (m != l || m->page_break) {
1329 if (m->space_before > 0)
1330 space += m->space_before;
1331 else
1332 text += m->space_before;
1333 }
1334 text += m->line_height;
1335 minheight = text + space;
43341922 1336
c6536773 1337 if (m != l && minheight > this_height)
1338 break;
43341922 1339
c6536773 1340 /*
1341 * If the space after this paragraph is _negative_
1342 * (which means the next line is folded on to this
1343 * one, which happens in the index), we absolutely
1344 * cannot break here.
1345 */
1346 if (m->space_after >= 0) {
43341922 1347
c6536773 1348 /*
1349 * Compute the cost of this arrangement, as the
1350 * square of the amount of wasted space on the
1351 * page. Exception: if this is the last page
1352 * before a mandatory break or the document
1353 * end, we don't penalise a large blank area.
1354 */
1355 if (m != last && m->next && !m->next->page_break)
1356 {
1357 int x = this_height - minheight;
1358 int xf;
1359
1360 xf = x & 0xFF;
1361 x >>= 8;
1362
1363 cost = x*x;
1364 cost += (x * xf) >> 8;
1365 } else
1366 cost = 0;
1367
1368 if (m != last && m->next && !m->next->page_break) {
1369 cost += m->penalty_after;
1370 cost += m->next->penalty_before;
1371 }
43341922 1372
c6536773 1373 if (m != last && m->next && !m->next->page_break)
1374 cost += m->next->bestcost[n1];
1375 if (l->bestcost[n] == -1 || l->bestcost[n] > cost) {
1376 /*
1377 * This is the best option yet for this
1378 * starting point.
1379 */
1380 l->bestcost[n] = cost;
1381 if (m != last && m->next && !m->next->page_break)
1382 l->vshortfall[n] = this_height - minheight;
1383 else
1384 l->vshortfall[n] = 0;
1385 l->text[n] = text;
1386 l->space[n] = space;
1387 l->page_last[n] = m;
1388 }
1389 }
43341922 1390
c6536773 1391 if (m == last)
1392 break;
43341922 1393 }
1394 }
1395 }
1396
1397 /*
1398 * Now go through the line list forwards and assemble the
1399 * actual pages.
1400 */
1401 ph = pt = NULL;
1402
1403 l = first;
c6536773 1404 n = 0;
43341922 1405 while (l) {
1406 page_data *page;
c6536773 1407 int text, space, head;
43341922 1408
1409 page = mknew(page_data);
1410 page->next = NULL;
1411 page->prev = pt;
1412 if (pt)
1413 pt->next = page;
1414 else
1415 ph = page;
1416 pt = page;
1417
1418 page->first_line = l;
c6536773 1419 page->last_line = l->page_last[n];
43341922 1420
1421 page->first_text = page->last_text = NULL;
138d7ffb 1422 page->first_xref = page->last_xref = NULL;
23765aeb 1423 page->first_rect = page->last_rect = NULL;
138d7ffb 1424
43341922 1425 /*
1426 * Now assign a y-coordinate to each line on the page.
1427 */
1428 text = space = 0;
c6536773 1429 head = (n < ncols ? headspace : 0);
43341922 1430 for (l = page->first_line; l; l = l->next) {
c6536773 1431 if (l != page->first_line) {
1432 if (l->prev->space_after > 0)
1433 space += l->prev->space_after;
1434 else
1435 text += l->prev->space_after;
1436 }
1437 if (l != page->first_line || l->page_break) {
1438 if (l->space_before > 0)
1439 space += l->space_before;
1440 else
1441 text += l->space_before;
1442 }
43341922 1443 text += l->line_height;
1444
1445 l->page = page;
c6536773 1446 l->ypos = text + space + head +
1447 space * (float)page->first_line->vshortfall[n] /
1448 page->first_line->space[n];
43341922 1449
1450 if (l == page->last_line)
1451 break;
1452 }
1453
c6536773 1454 l = page->last_line;
1455 if (l == last)
1456 break;
1457 l = l->next;
1458
1459 n = (n < ncols ? n+1 : ncols);
43341922 1460 }
1461
1462 return ph;
1463}
1464
23765aeb 1465static void add_rect_to_page(page_data *page, int x, int y, int w, int h)
1466{
1467 rect *r = mknew(rect);
1468
1469 r->next = NULL;
1470 if (page->last_rect)
1471 page->last_rect->next = r;
1472 else
1473 page->first_rect = r;
1474 page->last_rect = r;
1475
1476 r->x = x;
1477 r->y = y;
1478 r->w = w;
1479 r->h = h;
1480}
1481
43341922 1482static void add_string_to_page(page_data *page, int x, int y,
1483 font_encoding *fe, int size, char *text)
1484{
1485 text_fragment *frag;
1486
1487 frag = mknew(text_fragment);
1488 frag->next = NULL;
1489
1490 if (page->last_text)
1491 page->last_text->next = frag;
1492 else
1493 page->first_text = frag;
1494 page->last_text = frag;
1495
1496 frag->x = x;
1497 frag->y = y;
1498 frag->fe = fe;
1499 frag->fontsize = size;
1500 frag->text = dupstr(text);
1501}
1502
1503/*
1504 * Returns the updated x coordinate.
1505 */
1506static int render_string(page_data *page, font_data *font, int fontsize,
1507 int x, int y, wchar_t *str)
1508{
1509 char *text;
1510 int textpos, textwid, glyph;
1511 font_encoding *subfont = NULL, *sf;
1512
1513 text = mknewa(char, 1 + ustrlen(str));
1514 textpos = textwid = 0;
1515
1516 while (*str) {
1517 glyph = font->bmp[*str];
1518
1519 if (glyph == 0xFFFF)
1520 continue; /* nothing more we can do here */
1521
1522 /*
1523 * Find which subfont this character is going in.
1524 */
1525 sf = font->subfont_map[glyph].subfont;
1526
1527 if (!sf) {
1528 int c;
1529
1530 /*
1531 * This character is not yet in a subfont. Assign one.
1532 */
1533 if (font->latest_subfont->free_pos >= 0x100)
1534 font->latest_subfont = new_font_encoding(font);
1535
1536 c = font->latest_subfont->free_pos++;
1537 if (font->latest_subfont->free_pos == 0x7F)
1538 font->latest_subfont->free_pos = 0xA1;
1539
1540 font->subfont_map[glyph].subfont = font->latest_subfont;
1541 font->subfont_map[glyph].position = c;
1542 font->latest_subfont->vector[c] = font->glyphs[glyph];
1543 font->latest_subfont->indices[c] = glyph;
1544 font->latest_subfont->to_unicode[c] = *str;
1545
1546 sf = font->latest_subfont;
1547 }
1548
1549 if (!subfont || sf != subfont) {
1550 if (subfont) {
1551 text[textpos] = '\0';
1552 add_string_to_page(page, x, y, subfont, fontsize, text);
1553 x += textwid;
1554 } else {
1555 assert(textpos == 0);
1556 }
1557 textpos = 0;
1558 subfont = sf;
1559 }
1560
1561 text[textpos++] = font->subfont_map[glyph].position;
1562 textwid += font->widths[glyph] * fontsize;
1563
1564 str++;
1565 }
1566
1567 if (textpos > 0) {
1568 text[textpos] = '\0';
1569 add_string_to_page(page, x, y, subfont, fontsize, text);
1570 x += textwid;
1571 }
1572
1573 return x;
1574}
1575
1576/*
1577 * Returns the updated x coordinate.
1578 */
138d7ffb 1579static int render_text(page_data *page, para_data *pdata, line_data *ldata,
1580 int x, int y, word *text, word *text_end, xref **xr,
1581 int shortfall, int nspaces, int *nspace,
c6536773 1582 keywordlist *keywords, indexdata *idx)
43341922 1583{
faad4952 1584 while (text && text != text_end) {
43341922 1585 int style, type, findex, errs;
1586 wchar_t *str;
138d7ffb 1587 xref_dest dest;
43341922 1588
1589 switch (text->type) {
138d7ffb 1590 /*
1591 * Start a cross-reference.
1592 */
43341922 1593 case word_HyperLink:
43341922 1594 case word_UpperXref:
1595 case word_LowerXref:
138d7ffb 1596
1597 if (text->type == word_HyperLink) {
1598 dest.type = URL;
1599 dest.url = utoa_dup(text->text);
1600 dest.page = NULL;
1601 } else {
1602 keyword *kwl = kw_lookup(keywords, text->text);
1603 para_data *pdata;
1604
1605 if (kwl) {
1606 assert(kwl->para->private_data);
1607 pdata = (para_data *) kwl->para->private_data;
1608 dest.type = PAGE;
1609 dest.page = pdata->first->page;
1610 dest.url = NULL;
1611 } else {
1612 /*
1613 * Shouldn't happen, but *shrug*
1614 */
1615 dest.type = NONE;
1616 dest.page = NULL;
1617 dest.url = NULL;
1618 }
1619 }
1620 if (dest.type != NONE) {
1621 *xr = mknew(xref);
1622 (*xr)->dest = dest; /* structure copy */
1623 if (page->last_xref)
1624 page->last_xref->next = *xr;
1625 else
1626 page->first_xref = *xr;
1627 page->last_xref = *xr;
23765aeb 1628 (*xr)->next = NULL;
138d7ffb 1629
1630 /*
1631 * FIXME: Ideally we should have, and use, some
1632 * vertical font metric information here so that
1633 * our cross-ref rectangle can take account of
1634 * descenders and the font's cap height. This will
1635 * do for the moment, but it isn't ideal.
1636 */
1637 (*xr)->lx = (*xr)->rx = x;
1638 (*xr)->by = y;
1639 (*xr)->ty = y + ldata->line_height;
1640 }
1641 goto nextword;
1642
1643 /*
1644 * Finish extending a cross-reference box.
1645 */
1646 case word_HyperEnd:
43341922 1647 case word_XrefEnd:
138d7ffb 1648 *xr = NULL;
1649 goto nextword;
1650
43341922 1651 /*
c6536773 1652 * Add the current page number to the list of pages
1653 * referenced by an index entry.
43341922 1654 */
c6536773 1655 case word_IndexRef:
1656 {
1657 indextag *tag;
1658 int i;
1659
1660 tag = index_findtag(idx, text->text);
1661 if (!tag)
1662 goto nextword;
1663
1664 for (i = 0; i < tag->nrefs; i++) {
1665 indexentry *entry = tag->refs[i];
1666 paper_idx *pi = (paper_idx *)entry->backend_data;
1667
1668 /*
1669 * If the same index term is indexed twice
1670 * within the same section, we only want to
1671 * mention it once in the index.
1672 */
1673 if (pi->lastpage != page) {
1674 if (pi->lastword) {
1675 pi->lastword = pi->lastword->next =
1676 fake_word(L",");
1677 pi->lastword = pi->lastword->next =
1678 fake_space_word();
1679 pi->lastword = pi->lastword->next =
1680 fake_word(page->number);
1681 } else {
1682 pi->lastword = pi->words =
1683 fake_word(page->number);
1684 }
1685 }
1686
1687 pi->lastpage = page;
1688 }
1689 }
1690 goto nextword;
43341922 1691 }
1692
1693 style = towordstyle(text->type);
1694 type = removeattr(text->type);
1695
1696 findex = (style == word_Normal ? FONT_NORMAL :
1697 style == word_Emph ? FONT_EMPH :
1698 FONT_CODE);
1699
1700 if (type == word_Normal) {
1701 str = text->text;
1702 } else if (type == word_WhiteSpace) {
1703 x += pdata->sizes[findex] *
1704 string_width(pdata->fonts[findex], L" ", NULL);
faad4952 1705 if (nspaces && findex != FONT_CODE) {
1706 x += (*nspace+1) * shortfall / nspaces;
1707 x -= *nspace * shortfall / nspaces;
1708 (*nspace)++;
1709 }
43341922 1710 goto nextword;
1711 } else /* if (type == word_Quote) */ {
1712 if (text->aux == quote_Open)
1713 str = L"\x2018"; /* FIXME: configurability! */
1714 else
1715 str = L"\x2019"; /* FIXME: configurability! */
1716 }
1717
1718 (void) string_width(pdata->fonts[findex], str, &errs);
1719
1720 if (errs && text->alt)
138d7ffb 1721 x = render_text(page, pdata, ldata, x, y, text->alt, NULL,
c6536773 1722 xr, shortfall, nspaces, nspace, keywords, idx);
43341922 1723 else
1724 x = render_string(page, pdata->fonts[findex],
1725 pdata->sizes[findex], x, y, str);
1726
138d7ffb 1727 if (*xr)
1728 (*xr)->rx = x;
1729
43341922 1730 nextword:
43341922 1731 text = text->next;
1732 }
1733
1734 return x;
1735}
1736
2bfd1b76 1737/*
1738 * Returns the last x position used on the line.
1739 */
1740static int render_line(line_data *ldata, int left_x, int top_y,
c6536773 1741 xref_dest *dest, keywordlist *keywords, indexdata *idx)
43341922 1742{
faad4952 1743 int nspace;
138d7ffb 1744 xref *xr;
2bfd1b76 1745 int ret = 0;
138d7ffb 1746
faad4952 1747 if (ldata->aux_text) {
515d216b 1748 int x;
138d7ffb 1749 xr = NULL;
faad4952 1750 nspace = 0;
515d216b 1751 x = render_text(ldata->page, ldata->pdata, ldata,
1752 left_x + ldata->aux_left_indent,
1753 top_y - ldata->ypos,
c6536773 1754 ldata->aux_text, NULL, &xr, 0, 0, &nspace,
1755 keywords, idx);
515d216b 1756 if (ldata->aux_text_2)
1757 render_text(ldata->page, ldata->pdata, ldata,
1758 x, top_y - ldata->ypos,
c6536773 1759 ldata->aux_text_2, NULL, &xr, 0, 0, &nspace,
1760 keywords, idx);
faad4952 1761 }
1762 nspace = 0;
138d7ffb 1763
87bd6353 1764 if (ldata->first) {
138d7ffb 1765 /*
87bd6353 1766 * There might be a cross-reference carried over from a
1767 * previous line.
138d7ffb 1768 */
87bd6353 1769 if (dest->type != NONE) {
1770 xr = mknew(xref);
1771 xr->next = NULL;
1772 xr->dest = *dest; /* structure copy */
1773 if (ldata->page->last_xref)
1774 ldata->page->last_xref->next = xr;
1775 else
1776 ldata->page->first_xref = xr;
1777 ldata->page->last_xref = xr;
1778 xr->lx = xr->rx = left_x + ldata->xpos;
1779 xr->by = top_y - ldata->ypos;
1780 xr->ty = top_y - ldata->ypos + ldata->line_height;
1781 } else
1782 xr = NULL;
1783
c6536773 1784 {
1785 int extra_indent, shortfall, spaces;
1786 int just = ldata->pdata->justification;
1787
1788 /*
1789 * All forms of justification become JUST when we have
1790 * to squeeze the paragraph.
1791 */
1792 if (ldata->hshortfall < 0)
1793 just = JUST;
1794
1795 switch (just) {
1796 case JUST:
1797 shortfall = ldata->hshortfall;
1798 spaces = ldata->nspaces;
1799 extra_indent = 0;
1800 break;
1801 case LEFT:
1802 shortfall = spaces = extra_indent = 0;
1803 break;
1804 case RIGHT:
1805 shortfall = spaces = 0;
1806 extra_indent = ldata->real_shortfall;
1807 break;
1808 }
1809
1810 ret = render_text(ldata->page, ldata->pdata, ldata,
1811 left_x + ldata->xpos + extra_indent,
1812 top_y - ldata->ypos, ldata->first, ldata->end,
1813 &xr, shortfall, spaces, &nspace,
1814 keywords, idx);
1815 }
87bd6353 1816
1817 if (xr) {
1818 /*
1819 * There's a cross-reference continued on to the next line.
1820 */
1821 *dest = xr->dest;
1822 } else
1823 dest->type = NONE;
1824 }
2bfd1b76 1825
1826 return ret;
43341922 1827}
515d216b 1828
c6536773 1829static void render_para(para_data *pdata, paper_conf *conf,
1830 keywordlist *keywords, indexdata *idx,
1831 paragraph *index_placeholder, page_data *index_page)
1832{
1833 int last_x;
1834 xref *cxref;
1835 page_data *cxref_page;
1836 xref_dest dest;
1837 para_data *target;
1838 line_data *ldata;
1839
1840 dest.type = NONE;
1841 cxref = NULL;
1842 cxref_page = NULL;
1843
1844 for (ldata = pdata->first; ldata; ldata = ldata->next) {
1845 /*
1846 * If this is a contents entry, we expect to have a single
1847 * enormous cross-reference rectangle covering the whole
1848 * thing. (Unless, of course, it spans multiple pages.)
1849 */
1850 if (pdata->contents_entry && ldata->page != cxref_page) {
1851 cxref_page = ldata->page;
1852 cxref = mknew(xref);
1853 cxref->next = NULL;
1854 cxref->dest.type = PAGE;
1855 if (pdata->contents_entry == index_placeholder) {
1856 cxref->dest.page = index_page;
1857 } else {
1858 assert(pdata->contents_entry->private_data);
1859 target = (para_data *)pdata->contents_entry->private_data;
1860 cxref->dest.page = target->first->page;
1861 }
1862 cxref->dest.url = NULL;
1863 if (ldata->page->last_xref)
1864 ldata->page->last_xref->next = cxref;
1865 else
1866 ldata->page->first_xref = cxref;
1867 ldata->page->last_xref = cxref;
1868 cxref->lx = conf->left_margin;
1869 cxref->rx = conf->paper_width - conf->right_margin;
1870 cxref->ty = conf->paper_height - conf->top_margin
1871 - ldata->ypos + ldata->line_height;
1872 }
1873 if (pdata->contents_entry) {
1874 assert(cxref != NULL);
1875 cxref->by = conf->paper_height - conf->top_margin
1876 - ldata->ypos;
1877 }
1878
1879 last_x = render_line(ldata, conf->left_margin,
1880 conf->paper_height - conf->top_margin,
1881 &dest, keywords, idx);
1882 if (ldata == pdata->last)
1883 break;
1884 }
1885
1886 /*
1887 * If this is a contents entry, add leaders and a page
1888 * number.
1889 */
1890 if (pdata->contents_entry) {
1891 word *w;
1892 wchar_t *num;
1893 int wid;
1894 int x;
1895
1896 if (pdata->contents_entry == index_placeholder) {
1897 num = index_page->number;
1898 } else {
1899 assert(pdata->contents_entry->private_data);
1900 target = (para_data *)pdata->contents_entry->private_data;
1901 num = target->first->page->number;
1902 }
1903
1904 w = fake_word(num);
1905 wid = paper_width_simple(pdata, w);
1906 sfree(w);
1907
1908 render_string(pdata->last->page,
1909 pdata->fonts[FONT_NORMAL],
1910 pdata->sizes[FONT_NORMAL],
1911 conf->paper_width - conf->right_margin - wid,
1912 (conf->paper_height - conf->top_margin -
1913 pdata->last->ypos), num);
1914
1915 for (x = 0; x < conf->base_width; x += conf->leader_separation)
1916 if (x - conf->leader_separation > last_x - conf->left_margin &&
1917 x + conf->leader_separation < conf->base_width - wid)
1918 render_string(pdata->last->page,
1919 pdata->fonts[FONT_NORMAL],
1920 pdata->sizes[FONT_NORMAL],
1921 conf->left_margin + x,
1922 (conf->paper_height - conf->top_margin -
1923 pdata->last->ypos), L".");
1924 }
1925
1926 /*
1927 * Render any rectangle (chapter title underline or rule)
1928 * that goes with this paragraph.
1929 */
1930 switch (pdata->rect_type) {
1931 case RECT_CHAPTER_UNDERLINE:
1932 add_rect_to_page(pdata->last->page,
1933 conf->left_margin,
1934 (conf->paper_height - conf->top_margin -
1935 pdata->last->ypos -
1936 conf->chapter_underline_depth),
1937 conf->base_width,
1938 conf->chapter_underline_thickness);
1939 break;
1940 case RECT_RULE:
1941 add_rect_to_page(pdata->first->page,
1942 conf->left_margin + pdata->first->xpos,
1943 (conf->paper_height - conf->top_margin -
1944 pdata->last->ypos -
1945 pdata->last->line_height),
1946 conf->base_width - pdata->first->xpos,
1947 pdata->last->line_height);
1948 break;
1949 default: /* placate gcc */
1950 break;
1951 }
1952}
1953
be76d597 1954static para_data *code_paragraph(int indent, word *words, paper_conf *conf)
515d216b 1955{
be76d597 1956 para_data *pdata = mknew(para_data);
1957
515d216b 1958 /*
1959 * For code paragraphs, I'm going to hack grievously and
1960 * pretend the three normal fonts are the three code paragraph
1961 * fonts.
1962 */
be76d597 1963 pdata->fonts[FONT_NORMAL] = conf->cb;
1964 pdata->fonts[FONT_EMPH] = conf->co;
36e64ed4 1965 pdata->fonts[FONT_CODE] = conf->cr;
515d216b 1966 pdata->sizes[FONT_NORMAL] =
1967 pdata->sizes[FONT_EMPH] =
be76d597 1968 pdata->sizes[FONT_CODE] = 12;
515d216b 1969
1970 pdata->first = pdata->last = NULL;
be76d597 1971 pdata->outline_level = -1;
1972 pdata->rect_type = RECT_NONE;
2bfd1b76 1973 pdata->contents_entry = NULL;
c6536773 1974 pdata->justification = LEFT;
515d216b 1975
1976 for (; words; words = words->next) {
1977 wchar_t *t, *e, *start;
1978 word *lhead = NULL, *ltail = NULL, *w;
1979 line_data *ldata;
1980 int prev = -1, curr;
1981
1982 t = words->text;
1983 if (words->next && words->next->type == word_Emph) {
1984 e = words->next->text;
1985 words = words->next;
1986 } else
1987 e = NULL;
1988
1989 start = t;
1990
1991 while (*start) {
1992 while (*t) {
1993 if (!e || !*e)
1994 curr = 0;
1995 else if (*e == L'i')
1996 curr = 1;
1997 else if (*e == L'b')
1998 curr = 2;
1999 else
2000 curr = 0;
2001
2002 if (prev < 0)
2003 prev = curr;
2004
2005 if (curr != prev)
2006 break;
2007
2008 t++;
2009 if (e && *e)
2010 e++;
2011 }
2012
2013 /*
2014 * We've isolated a maximal subsequence of the line
2015 * which has the same emphasis. Form it into a word
2016 * structure.
2017 */
2018 w = mknew(word);
2019 w->next = NULL;
2020 w->alt = NULL;
2021 w->type = (prev == 0 ? word_WeakCode :
2022 prev == 1 ? word_Emph : word_Normal);
2023 w->text = mknewa(wchar_t, t-start+1);
2024 memcpy(w->text, start, (t-start) * sizeof(wchar_t));
2025 w->text[t-start] = '\0';
2026 w->breaks = FALSE;
2027
2028 if (ltail)
2029 ltail->next = w;
2030 else
2031 lhead = w;
2032 ltail = w;
2033
2034 start = t;
2035 prev = -1;
2036 }
2037
2038 ldata = mknew(line_data);
2039
2040 ldata->pdata = pdata;
2041 ldata->first = lhead;
2042 ldata->end = NULL;
be76d597 2043 ldata->line_height = conf->base_font_size * 4096;
515d216b 2044
2045 ldata->xpos = indent;
2046
2047 if (pdata->last) {
2048 pdata->last->next = ldata;
2049 ldata->prev = pdata->last;
2050 } else {
2051 pdata->first = ldata;
2052 ldata->prev = NULL;
2053 }
2054 ldata->next = NULL;
2055 pdata->last = ldata;
2056
2057 ldata->hshortfall = 0;
2058 ldata->nspaces = 0;
2059 ldata->aux_text = NULL;
2060 ldata->aux_text_2 = NULL;
2061 ldata->aux_left_indent = 0;
39a0cfb9 2062 /* General opprobrium for breaking in a code paragraph. */
2063 ldata->penalty_before = ldata->penalty_after = 50000;
515d216b 2064 }
be76d597 2065
2066 standard_line_spacing(pdata, conf);
2067
2068 return pdata;
515d216b 2069}
87bd6353 2070
be76d597 2071static para_data *rule_paragraph(int indent, paper_conf *conf)
87bd6353 2072{
be76d597 2073 para_data *pdata = mknew(para_data);
87bd6353 2074 line_data *ldata;
2075
2076 ldata = mknew(line_data);
2077
2078 ldata->pdata = pdata;
2079 ldata->first = NULL;
2080 ldata->end = NULL;
be76d597 2081 ldata->line_height = conf->rule_thickness;
87bd6353 2082
2083 ldata->xpos = indent;
2084
2085 ldata->prev = NULL;
2086 ldata->next = NULL;
2087
2088 ldata->hshortfall = 0;
2089 ldata->nspaces = 0;
2090 ldata->aux_text = NULL;
2091 ldata->aux_text_2 = NULL;
2092 ldata->aux_left_indent = 0;
2093
2094 /*
2095 * Better to break after a rule than before it
2096 */
2097 ldata->penalty_after += 100000;
2098 ldata->penalty_before += -100000;
2099
2100 pdata->first = pdata->last = ldata;
be76d597 2101 pdata->outline_level = -1;
2102 pdata->rect_type = RECT_RULE;
2bfd1b76 2103 pdata->contents_entry = NULL;
c6536773 2104 pdata->justification = LEFT;
be76d597 2105
2106 standard_line_spacing(pdata, conf);
2107
2108 return pdata;
2109}
2110
2111/*
2112 * Plain-text-like formatting for outline titles.
2113 */
2114static void paper_rdaddw(rdstring *rs, word *text) {
2115 for (; text; text = text->next) switch (text->type) {
2116 case word_HyperLink:
2117 case word_HyperEnd:
2118 case word_UpperXref:
2119 case word_LowerXref:
2120 case word_XrefEnd:
2121 case word_IndexRef:
2122 break;
2123
2124 case word_Normal:
2125 case word_Emph:
2126 case word_Code:
2127 case word_WeakCode:
2128 case word_WhiteSpace:
2129 case word_EmphSpace:
2130 case word_CodeSpace:
2131 case word_WkCodeSpace:
2132 case word_Quote:
2133 case word_EmphQuote:
2134 case word_CodeQuote:
2135 case word_WkCodeQuote:
2136 assert(text->type != word_CodeQuote &&
2137 text->type != word_WkCodeQuote);
2138 if (towordstyle(text->type) == word_Emph &&
2139 (attraux(text->aux) == attr_First ||
2140 attraux(text->aux) == attr_Only))
2141 rdadd(rs, L'_'); /* FIXME: configurability */
2142 else if (towordstyle(text->type) == word_Code &&
2143 (attraux(text->aux) == attr_First ||
2144 attraux(text->aux) == attr_Only))
2145 rdadd(rs, L'\''); /* FIXME: configurability */
2146 if (removeattr(text->type) == word_Normal) {
2147 rdadds(rs, text->text);
2148 } else if (removeattr(text->type) == word_WhiteSpace) {
2149 rdadd(rs, L' ');
2150 } else if (removeattr(text->type) == word_Quote) {
2151 rdadd(rs, L'\''); /* fixme: configurability */
2152 }
2153 if (towordstyle(text->type) == word_Emph &&
2154 (attraux(text->aux) == attr_Last ||
2155 attraux(text->aux) == attr_Only))
2156 rdadd(rs, L'_'); /* FIXME: configurability */
2157 else if (towordstyle(text->type) == word_Code &&
2158 (attraux(text->aux) == attr_Last ||
2159 attraux(text->aux) == attr_Only))
2160 rdadd(rs, L'\''); /* FIXME: configurability */
2161 break;
2162 }
2163}
2164
2165static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
2166 word *second)
2167{
2168 rdstring rs = {0, 0, NULL};
2169
2170 if (first)
2171 paper_rdaddw(&rs, first);
2172 if (separator)
2173 rdadds(&rs, separator);
2174 if (second)
2175 paper_rdaddw(&rs, second);
2176
2177 return rs.text;
87bd6353 2178}
2bfd1b76 2179
2180static word *fake_word(wchar_t *text)
2181{
2182 word *ret = mknew(word);
2183 ret->next = NULL;
2184 ret->alt = NULL;
2185 ret->type = word_Normal;
2186 ret->text = ustrdup(text);
2187 ret->breaks = FALSE;
2188 ret->aux = 0;
2189 return ret;
2190}
2191
c6536773 2192static word *fake_space_word(void)
2193{
2194 word *ret = mknew(word);
2195 ret->next = NULL;
2196 ret->alt = NULL;
2197 ret->type = word_WhiteSpace;
2198 ret->text = NULL;
2199 ret->breaks = TRUE;
2200 ret->aux = 0;
2201 return ret;
2202}
2203
2bfd1b76 2204static word *prepare_contents_title(word *first, wchar_t *separator,
2205 word *second)
2206{
2207 word *ret;
2208 word **wptr, *w;
2209
2210 wptr = &ret;
2211
2212 if (first) {
2213 w = dup_word_list(first);
2214 *wptr = w;
2215 while (w->next)
2216 w = w->next;
2217 wptr = &w->next;
2218 }
2219
2220 if (separator) {
2221 w = fake_word(separator);
2222 *wptr = w;
2223 wptr = &w->next;
2224 }
2225
2226 if (second) {
2227 *wptr = dup_word_list(second);
2228 }
2229
2230 return ret;
2231}
c6536773 2232
2233static void fold_into_page(page_data *dest, page_data *src, int right_shift)
2234{
2235 line_data *ldata;
2236
2237 if (!src->first_line)
2238 return;
2239
2240 if (dest->last_line) {
2241 dest->last_line->next = src->first_line;
2242 src->first_line->prev = dest->last_line;
2243 }
2244 dest->last_line = src->last_line;
2245
2246 for (ldata = src->first_line; ldata; ldata = ldata->next) {
2247 ldata->page = dest;
2248 ldata->xpos += right_shift;
2249
2250 if (ldata == src->last_line)
2251 break;
2252 }
2253}