]> www.fi.muni.cz Git - evince.git/blob - pdf/xpdf/TextOutputDev.cc
Import of Xpdf 2.02 for merge
[evince.git] / pdf / xpdf / TextOutputDev.cc
1 //========================================================================
2 //
3 // TextOutputDev.cc
4 //
5 // Copyright 1997-2002 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #include <aconf.h>
10
11 #ifdef USE_GCC_PRAGMAS
12 #pragma implementation
13 #endif
14
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <stddef.h>
18 #include <math.h>
19 #include <ctype.h>
20 #include "gmem.h"
21 #include "GString.h"
22 #include "GList.h"
23 #include "config.h"
24 #include "Error.h"
25 #include "GlobalParams.h"
26 #include "UnicodeMap.h"
27 #include "GfxState.h"
28 #include "TextOutputDev.h"
29
30 #ifdef MACOS
31 // needed for setting type/creator of MacOS files
32 #include "ICSupport.h"
33 #endif
34
35 //------------------------------------------------------------------------
36 // parameters
37 //------------------------------------------------------------------------
38
39 // Minium and maximum inter-word spacing (as a fraction of the average
40 // character width).
41 #define wordMinSpaceWidth 0.3
42 #define wordMaxSpaceWidth 2.0
43
44 // Default min and max inter-word spacing (when the average character
45 // width is unknown).
46 #define wordDefMinSpaceWidth 0.2
47 #define wordDefMaxSpaceWidth 1.5
48
49 // Max difference in x,y coordinates (as a fraction of the font size)
50 // allowed for duplicated text (fake boldface, drop shadows) which is
51 // to be discarded.
52 #define dupMaxDeltaX 0.2
53 #define dupMaxDeltaY 0.2
54
55 // Min overlap (as a fraction of the font size) required for two
56 // lines to be considered vertically overlapping.
57 #define lineOverlapSlack 0.5
58
59 // Max difference in baseline y coordinates (as a fraction of the font
60 // size) allowed for words which are to be grouped into a line, not
61 // including sub/superscripts.
62 #define lineMaxBaselineDelta 0.1
63
64 // Max ratio of font sizes allowed for words which are to be grouped
65 // into a line, not including sub/superscripts.
66 #define lineMaxFontSizeRatio 1.4
67
68 // Min spacing (as a fraction of the font size) allowed between words
69 // which are to be grouped into a line.
70 #define lineMinDeltaX -0.5
71
72 // Minimum vertical overlap (as a fraction of the font size) required
73 // for superscript and subscript words.
74 #define lineMinSuperscriptOverlap 0.3
75 #define lineMinSubscriptOverlap   0.3
76
77 // Min/max ratio of font sizes allowed for sub/superscripts compared to
78 // the base text.
79 #define lineMinSubscriptFontSizeRatio   0.4
80 #define lineMaxSubscriptFontSizeRatio   1.01
81 #define lineMinSuperscriptFontSizeRatio 0.4
82 #define lineMaxSuperscriptFontSizeRatio 1.01
83
84 // Max horizontal spacing (as a fraction of the font size) allowed
85 // before sub/superscripts.
86 #define lineMaxSubscriptDeltaX   0.2
87 #define lineMaxSuperscriptDeltaX 0.2
88
89 // Maximum vertical spacing (as a fraction of the font size) allowed
90 // for lines which are to be grouped into a block.
91 #define blkMaxSpacing 2.0
92
93 // Max ratio of primary font sizes allowed for lines which are to be
94 // grouped into a block.
95 #define blkMaxFontSizeRatio 1.3
96
97 // Min overlap (as a fraction of the font size) required for two
98 // blocks to be considered vertically overlapping.
99 #define blkOverlapSlack 0.5
100
101 // Max vertical spacing (as a fraction of the font size) allowed
102 // between blocks which are 'adjacent' when sorted by reading order.
103 #define blkMaxSortSpacing 2.0
104
105 // Max vertical offset (as a fraction of the font size) of the top and
106 // bottom edges allowed for blocks which are to be grouped into a
107 // flow.
108 #define flowMaxDeltaY 1.0
109
110 //------------------------------------------------------------------------
111 // TextFontInfo
112 //------------------------------------------------------------------------
113
114 TextFontInfo::TextFontInfo(GfxState *state) {
115   double *textMat;
116   double t1, t2, avgWidth, w;
117   int n, i;
118
119   gfxFont = state->getFont();
120   textMat = state->getTextMat();
121   horizScaling = state->getHorizScaling();
122   if ((t1 = fabs(textMat[0])) > 0.01 &&
123       (t2 = fabs(textMat[3])) > 0.01) {
124     horizScaling *= t1 / t2;
125   }
126
127   if (!gfxFont) {
128     minSpaceWidth = horizScaling * wordDefMinSpaceWidth;
129     maxSpaceWidth = horizScaling * wordDefMaxSpaceWidth;
130   } else if (gfxFont->isCIDFont()) {
131     //~ handle 16-bit fonts
132     minSpaceWidth = horizScaling * wordDefMinSpaceWidth;
133     maxSpaceWidth = horizScaling * wordDefMaxSpaceWidth;
134   } else {
135     avgWidth = 0;
136     n = 0;
137     for (i = 0; i < 256; ++i) {
138       w = ((Gfx8BitFont *)gfxFont)->getWidth(i);
139       if (w > 0) {
140         avgWidth += w;
141         ++n;
142       }
143     }
144     avgWidth /= n;
145     minSpaceWidth = horizScaling * wordMinSpaceWidth * avgWidth;
146     maxSpaceWidth = horizScaling * wordMaxSpaceWidth * avgWidth;
147   }
148
149 }
150
151 TextFontInfo::~TextFontInfo() {
152 }
153
154 GBool TextFontInfo::matches(GfxState *state) {
155   double *textMat;
156   double t1, t2, h;
157
158   textMat = state->getTextMat();
159   h = state->getHorizScaling();
160   if ((t1 = fabs(textMat[0])) > 0.01 &&
161       (t2 = fabs(textMat[3])) > 0.01) {
162     h *= t1 / t2;
163   }
164   return state->getFont() == gfxFont &&
165          fabs(h - horizScaling) < 0.01;
166 }
167
168 //------------------------------------------------------------------------
169 // TextWord
170 //------------------------------------------------------------------------
171
172 TextWord::TextWord(GfxState *state, double x0, double y0,
173                    TextFontInfo *fontA, double fontSizeA) {
174   GfxFont *gfxFont;
175   double x, y;
176
177   font = fontA;
178   fontSize = fontSizeA;
179   state->transform(x0, y0, &x, &y);
180   if ((gfxFont = font->gfxFont)) {
181     yMin = y - gfxFont->getAscent() * fontSize;
182     yMax = y - gfxFont->getDescent() * fontSize;
183   } else {
184     // this means that the PDF file draws text without a current font,
185     // which should never happen
186     yMin = y - 0.95 * fontSize;
187     yMax = y + 0.35 * fontSize;
188   }
189   if (yMin == yMax) {
190     // this is a sanity check for a case that shouldn't happen -- but
191     // if it does happen, we want to avoid dividing by zero later
192     yMin = y;
193     yMax = y + 1;
194   }
195   yBase = y;
196   text = NULL;
197   xRight = NULL;
198   len = size = 0;
199   spaceAfter = gFalse;
200   next = NULL;
201
202 }
203
204
205 TextWord::~TextWord() {
206   gfree(text);
207   gfree(xRight);
208 }
209
210 void TextWord::addChar(GfxState *state, double x, double y,
211                        double dx, double dy, Unicode u) {
212   if (len == size) {
213     size += 16;
214     text = (Unicode *)grealloc(text, size * sizeof(Unicode));
215     xRight = (double *)grealloc(xRight, size * sizeof(double));
216   }
217   text[len] = u;
218   if (len == 0) {
219     xMin = x;
220   }
221   xMax = xRight[len] = x + dx;
222   ++len;
223 }
224
225 // Returns true if <this> comes before <word2> in xy order.
226 GBool TextWord::xyBefore(TextWord *word2) {
227   return xMin < word2->xMin ||
228          (xMin == word2->xMin && yMin < word2->yMin);
229 }
230
231 // Merge another word onto the end of this one.
232 void TextWord::merge(TextWord *word2) {
233   int i;
234
235   xMax = word2->xMax;
236   if (word2->yMin < yMin) {
237     yMin = word2->yMin;
238   }
239   if (word2->yMax > yMax) {
240     yMax = word2->yMax;
241   }
242   if (len + word2->len > size) {
243     size = len + word2->len;
244     text = (Unicode *)grealloc(text, size * sizeof(Unicode));
245     xRight = (double *)grealloc(xRight, size * sizeof(double));
246   }
247   for (i = 0; i < word2->len; ++i) {
248     text[len + i] = word2->text[i];
249     xRight[len + i] = word2->xRight[i];
250   }
251   len += word2->len;
252 }
253
254 //------------------------------------------------------------------------
255 // TextLine
256 //------------------------------------------------------------------------
257
258 TextLine::TextLine() {
259   words = NULL;
260   text = NULL;
261   xRight = NULL;
262   col = NULL;
263   len = 0;
264   hyphenated = gFalse;
265   pageNext = NULL;
266   next = NULL;
267   flowNext = NULL;
268 }
269
270 TextLine::~TextLine() {
271   TextWord *w1, *w2;
272
273   for (w1 = words; w1; w1 = w2) {
274     w2 = w1->next;
275     delete w1;
276   }
277   gfree(text);
278   gfree(xRight);
279   gfree(col);
280 }
281
282 // Returns true if <this> comes before <line2> in yx order, allowing
283 // slack for vertically overlapping lines.
284 GBool TextLine::yxBefore(TextLine *line2) {
285   double dy;
286
287   dy = lineOverlapSlack * fontSize;
288
289   // non-overlapping case
290   if (line2->yMin > yMax - dy ||
291       line2->yMax < yMin + dy) {
292     return yMin < line2->yMin ||
293            (yMin == line2->yMin && xMin < line2->xMin);
294   }
295
296   // overlapping case
297   return xMin < line2->xMin;
298 }
299
300 // Merge another line's words onto the end of this line.
301 void TextLine::merge(TextLine *line2) {
302   TextWord *word;
303   int newLen, i;
304
305   xMax = line2->xMax;
306   if (line2->yMin < yMin) {
307     yMin = line2->yMin;
308   }
309   if (line2->yMax > yMax) {
310     yMax = line2->yMax;
311   }
312   xSpaceR = line2->xSpaceR;
313   for (word = words; word->next; word = word->next) ;
314   word->spaceAfter = gTrue;
315   word->next = line2->words;
316   line2->words = NULL;
317   newLen = len + 1 + line2->len;
318   text = (Unicode *)grealloc(text, newLen * sizeof(Unicode));
319   xRight = (double *)grealloc(xRight, newLen * sizeof(double));
320   text[len] = (Unicode)0x0020;
321   xRight[len] = line2->xMin;
322   for (i = 0; i < line2->len; ++i) {
323     text[len + 1 + i] = line2->text[i];
324     xRight[len + 1 + i] = line2->xRight[i];
325   }
326   len = newLen;
327   convertedLen += line2->convertedLen;
328   hyphenated = line2->hyphenated;
329 }
330
331 //------------------------------------------------------------------------
332 // TextBlock
333 //------------------------------------------------------------------------
334
335 TextBlock::TextBlock() {
336   lines = NULL;
337   next = NULL;
338 }
339
340 TextBlock::~TextBlock() {
341   TextLine *l1, *l2;
342
343   for (l1 = lines; l1; l1 = l2) {
344     l2 = l1->next;
345     delete l1;
346   }
347 }
348
349 // Returns true if <this> comes before <blk2> in xy order, allowing
350 // slack for vertically overlapping blocks.
351 GBool TextBlock::yxBefore(TextBlock *blk2) {
352   double dy;
353
354   dy = blkOverlapSlack * lines->fontSize;
355
356   // non-overlapping case
357   if (blk2->yMin > yMax - dy ||
358       blk2->yMax < yMin + dy) {
359     return yMin < blk2->yMin ||
360            (yMin == blk2->yMin && xMin < blk2->xMin);
361   }
362
363   // overlapping case
364   return xMin < blk2->xMin;
365 }
366
367 // Merge another block's line onto the right of this one.
368 void TextBlock::mergeRight(TextBlock *blk2) {
369   lines->merge(blk2->lines);
370   xMax = lines->xMax;
371   yMin = lines->yMin;
372   yMax = lines->yMax;
373   xSpaceR = lines->xSpaceR;
374 }
375
376 // Merge another block's lines onto the bottom of this block.
377 void TextBlock::mergeBelow(TextBlock *blk2) {
378   TextLine *line;
379
380   if (blk2->xMin < xMin) {
381     xMin = blk2->xMin;
382   }
383   if (blk2->xMax > xMax) {
384     xMax = blk2->xMax;
385   }
386   yMax = blk2->yMax;
387   if (blk2->xSpaceL > xSpaceL) {
388     xSpaceL = blk2->xSpaceL;
389   }
390   if (blk2->xSpaceR < xSpaceR) {
391     xSpaceR = blk2->xSpaceR;
392   }
393   if (blk2->maxFontSize > maxFontSize) {
394     maxFontSize = blk2->maxFontSize;
395   }
396   for (line = lines; line->next; line = line->next) ;
397   line->next = line->flowNext = blk2->lines;
398   blk2->lines = NULL;
399 }
400
401 //------------------------------------------------------------------------
402 // TextFlow
403 //------------------------------------------------------------------------
404
405 TextFlow::TextFlow() {
406   blocks = NULL;
407   next = NULL;
408 }
409
410 TextFlow::~TextFlow() {
411   TextBlock *b1, *b2;
412
413   for (b1 = blocks; b1; b1 = b2) {
414     b2 = b1->next;
415     delete b1;
416   }
417 }
418
419
420 //------------------------------------------------------------------------
421 // TextPage
422 //------------------------------------------------------------------------
423
424 TextPage::TextPage(GBool rawOrderA) {
425   rawOrder = rawOrderA;
426   curWord = NULL;
427   font = NULL;
428   fontSize = 0;
429   nest = 0;
430   nTinyChars = 0;
431   words = wordPtr = NULL;
432   lines = NULL;
433   flows = NULL;
434   fonts = new GList();
435 }
436
437 TextPage::~TextPage() {
438   clear();
439   delete fonts;
440 }
441
442 void TextPage::updateFont(GfxState *state) {
443   GfxFont *gfxFont;
444   double *fm;
445   char *name;
446   int code, mCode, letterCode, anyCode;
447   double w;
448   int i;
449
450   // get the font info object
451   font = NULL;
452   for (i = 0; i < fonts->getLength(); ++i) {
453     font = (TextFontInfo *)fonts->get(i);
454     if (font->matches(state)) {
455       break;
456     }
457     font = NULL;
458   }
459   if (!font) {
460     font = new TextFontInfo(state);
461     fonts->append(font);
462   }
463
464   // adjust the font size
465   gfxFont = state->getFont();
466   fontSize = state->getTransformedFontSize();
467   if (gfxFont && gfxFont->getType() == fontType3) {
468     // This is a hack which makes it possible to deal with some Type 3
469     // fonts.  The problem is that it's impossible to know what the
470     // base coordinate system used in the font is without actually
471     // rendering the font.  This code tries to guess by looking at the
472     // width of the character 'm' (which breaks if the font is a
473     // subset that doesn't contain 'm').
474     mCode = letterCode = anyCode = -1;
475     for (code = 0; code < 256; ++code) {
476       name = ((Gfx8BitFont *)gfxFont)->getCharName(code);
477       if (name && name[0] == 'm' && name[1] == '\0') {
478         mCode = code;
479       }
480       if (letterCode < 0 && name && name[1] == '\0' &&
481           ((name[0] >= 'A' && name[0] <= 'Z') ||
482            (name[0] >= 'a' && name[0] <= 'z'))) {
483         letterCode = code;
484       }
485       if (anyCode < 0 && name &&
486           ((Gfx8BitFont *)gfxFont)->getWidth(code) > 0) {
487         anyCode = code;
488       }
489     }
490     if (mCode >= 0 &&
491         (w = ((Gfx8BitFont *)gfxFont)->getWidth(mCode)) > 0) {
492       // 0.6 is a generic average 'm' width -- yes, this is a hack
493       fontSize *= w / 0.6;
494     } else if (letterCode >= 0 &&
495                (w = ((Gfx8BitFont *)gfxFont)->getWidth(letterCode)) > 0) {
496       // even more of a hack: 0.5 is a generic letter width
497       fontSize *= w / 0.5;
498     } else if (anyCode >= 0 &&
499                (w = ((Gfx8BitFont *)gfxFont)->getWidth(anyCode)) > 0) {
500       // better than nothing: 0.5 is a generic character width
501       fontSize *= w / 0.5;
502     }
503     fm = gfxFont->getFontMatrix();
504     if (fm[0] != 0) {
505       fontSize *= fabs(fm[3] / fm[0]);
506     }
507   }
508 }
509
510 void TextPage::beginWord(GfxState *state, double x0, double y0) {
511   // This check is needed because Type 3 characters can contain
512   // text-drawing operations (when TextPage is being used via
513   // XOutputDev rather than TextOutputDev).
514   if (curWord) {
515     ++nest;
516     return;
517   }
518
519   curWord = new TextWord(state, x0, y0, font, fontSize);
520 }
521
522 void TextPage::addChar(GfxState *state, double x, double y,
523                        double dx, double dy,
524                        CharCode c, Unicode *u, int uLen) {
525   double x1, y1, w1, h1, dx2, dy2, sp;
526   int n, i;
527
528   // if the previous char was a space, addChar will have called
529   // endWord, so we need to start a new word
530   if (!curWord) {
531     beginWord(state, x, y);
532   }
533
534   // throw away chars that aren't inside the page bounds
535   state->transform(x, y, &x1, &y1);
536   if (x1 < 0 || x1 > pageWidth ||
537       y1 < 0 || y1 > pageHeight) {
538     return;
539   }
540
541   // subtract char and word spacing from the dx,dy values
542   sp = state->getCharSpace();
543   if (c == (CharCode)0x20) {
544     sp += state->getWordSpace();
545   }
546   state->textTransformDelta(sp * state->getHorizScaling(), 0, &dx2, &dy2);
547   dx -= dx2;
548   dy -= dy2;
549   state->transformDelta(dx, dy, &w1, &h1);
550
551   // check the tiny chars limit
552   if (!globalParams->getTextKeepTinyChars() &&
553       fabs(w1) < 3 && fabs(h1) < 3) {
554     if (++nTinyChars > 20000) {
555       return;
556     }
557   }
558
559   // break words at space character
560   if (uLen == 1 && u[0] == (Unicode)0x20) {
561     endWord();
562     return;
563   }
564
565   // large char spacing is sometimes used to move text around -- in
566   // this case, break text into individual chars and let the coalesce
567   // function deal with it later
568   n = curWord->len;
569   if (n > 0 && x1 - curWord->xRight[n-1] >
570                     curWord->font->minSpaceWidth * curWord->fontSize) {
571     // large char spacing is sometimes used to move text around
572     endWord();
573     beginWord(state, x, y);
574   }
575
576   // add the characters to the current word
577   if (uLen != 0) {
578     w1 /= uLen;
579     h1 /= uLen;
580   }
581   for (i = 0; i < uLen; ++i) {
582     curWord->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, u[i]);
583   }
584 }
585
586 void TextPage::endWord() {
587   // This check is needed because Type 3 characters can contain
588   // text-drawing operations (when TextPage is being used via
589   // XOutputDev rather than TextOutputDev).
590   if (nest > 0) {
591     --nest;
592     return;
593   }
594
595   if (curWord) {
596     addWord(curWord);
597     curWord = NULL;
598   }
599 }
600
601 void TextPage::addWord(TextWord *word) {
602   TextWord *p1, *p2;
603
604   // throw away zero-length words -- they don't have valid xMin/xMax
605   // values, and they're useless anyway
606   if (word->len == 0) {
607     delete word;
608     return;
609   }
610
611   // insert word in xy list
612   if (rawOrder) {
613     p1 = wordPtr;
614     p2 = NULL;
615   } else {
616     if (wordPtr && wordPtr->xyBefore(word)) {
617       p1 = wordPtr;
618       p2 = wordPtr->next;
619     } else {
620       p1 = NULL;
621       p2 = words;
622     }
623     for (; p2; p1 = p2, p2 = p2->next) {
624       if (word->xyBefore(p2)) {
625         break;
626       }
627     }
628   }
629   if (p1) {
630     p1->next = word;
631   } else {
632     words = word;
633   }
634   word->next = p2;
635   wordPtr = word;
636 }
637
638 void TextPage::coalesce() {
639   TextWord *word0, *word1, *word2, *word3, *word4;
640   TextLine *line0, *line1, *line2, *line3, *line4, *lineList;
641   TextBlock *blk0, *blk1, *blk2, *blk3, *blk4, *blk5, *blk6;
642   TextBlock *yxBlocks, *blocks, *blkStack;
643   TextFlow *flow0, *flow1;
644   double sz, xLimit, minSpace, maxSpace, yLimit;
645   double fit1, fit2;
646   GBool found;
647   UnicodeMap *uMap;
648   GBool isUnicode;
649   char buf[8];
650   int col1, col2, d, i, j;
651
652 #if 0 // for debugging
653   printf("*** initial word list ***\n");
654   for (word0 = words; word0; word0 = word0->next) {
655     printf("word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f: '",
656            word0->xMin, word0->xMax, word0->yMin, word0->yMax, word0->yBase);
657     for (i = 0; i < word0->len; ++i) {
658       fputc(word0->text[i] & 0xff, stdout);
659     }
660     printf("'\n");
661   }
662   printf("\n");
663   fflush(stdout);
664 #endif
665
666   //----- discard duplicated text (fake boldface, drop shadows)
667
668   word0 = words;
669   while (word0) {
670     sz = word0->fontSize;
671     xLimit = word0->xMin + sz * dupMaxDeltaX;
672     found = gFalse;
673     for (word1 = word0, word2 = word0->next;
674          word2 && word2->xMin < xLimit;
675          word1 = word2, word2 = word2->next) {
676       if (word2->len == word0->len &&
677           !memcmp(word2->text, word0->text, word0->len * sizeof(Unicode)) &&
678           fabs(word2->yMin - word0->yMin) < sz * dupMaxDeltaY &&
679           fabs(word2->yMax - word0->yMax) < sz * dupMaxDeltaY &&
680           fabs(word2->xMax - word0->xMax) < sz * dupMaxDeltaX) {
681         found = gTrue;
682         break;
683       }
684     }
685     if (found) {
686       word1->next = word2->next;
687       delete word2;
688     } else {
689       word0 = word0->next;
690     }
691   }
692
693 #if 0 // for debugging
694   printf("*** words after removing duplicate text ***\n");
695   for (word0 = words; word0; word0 = word0->next) {
696     printf("word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f: '",
697            word0->xMin, word0->xMax, word0->yMin, word0->yMax, word0->yBase);
698     for (i = 0; i < word0->len; ++i) {
699       fputc(word0->text[i] & 0xff, stdout);
700     }
701     printf("'\n");
702   }
703   printf("\n");
704   fflush(stdout);
705 #endif
706
707   //----- merge words
708
709   word0 = words;
710   while (word0) {
711     sz = word0->fontSize;
712
713     // look for adjacent text which is part of the same word, and
714     // merge it into this word
715     xLimit = word0->xMax + sz * word0->font->minSpaceWidth;
716     if (rawOrder) {
717       word1 = word0;
718       word2 = word0->next;
719       found = word2 &&
720               word2->xMin < xLimit &&
721               word2->font == word0->font &&
722               fabs(word2->fontSize - sz) < 0.05 &&
723               fabs(word2->yBase - word0->yBase) < 0.05;
724     } else {
725       found = gFalse;
726       for (word1 = word0, word2 = word0->next;
727            word2 && word2->xMin < xLimit;
728            word1 = word2, word2 = word2->next) {
729         if (word2->font == word0->font &&
730             fabs(word2->fontSize - sz) < 0.05 &&
731             fabs(word2->yBase - word0->yBase) < 0.05) {
732           found = gTrue;
733           break;
734         }
735       }
736     }
737     if (found) {
738       word0->merge(word2);
739       word1->next = word2->next;
740       delete word2;
741       continue;
742     }
743
744     word0 = word0->next;
745   }
746
747 #if 0 // for debugging
748   printf("*** after merging words ***\n");
749   for (word0 = words; word0; word0 = word0->next) {
750     printf("word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f: '",
751            word0->xMin, word0->xMax, word0->yMin, word0->yMax, word0->yBase);
752     for (i = 0; i < word0->len; ++i) {
753       fputc(word0->text[i] & 0xff, stdout);
754     }
755     printf("'\n");
756   }
757   printf("\n");
758   fflush(stdout);
759 #endif
760
761   //----- assemble words into lines
762
763   uMap = globalParams->getTextEncoding();
764   isUnicode = uMap ? uMap->isUnicode() : gFalse;
765
766   lineList = NULL;
767   line0 = NULL;
768   while (words) {
769
770     // build a new line object
771     word0 = words;
772     words = words->next;
773     word0->next = NULL;
774     line1 = new TextLine();
775     line1->words = word0;
776     line1->xMin = word0->xMin;
777     line1->xMax = word0->xMax;
778     line1->yMin = word0->yMin;
779     line1->yMax = word0->yMax;
780     line1->yBase = word0->yBase;
781     line1->font = word0->font;
782     line1->fontSize = word0->fontSize;
783     line1->len = word0->len;
784     minSpace = line1->fontSize * word0->font->minSpaceWidth;
785     maxSpace = line1->fontSize * word0->font->maxSpaceWidth;
786
787     // find subsequent words in the line
788     while (words) {
789       xLimit = line1->xMax + maxSpace;
790       fit1 = fit2 = 0;
791       word3 = word4 = NULL;
792       if (rawOrder) {
793         if (words &&
794             words->xMin < xLimit &&
795             ((fit1 = lineFit(line1, word0, words)) >= 0)) {
796           word3 = NULL;
797           word4 = words;
798         }
799       } else {
800         for (word1 = NULL, word2 = words;
801              word2 && word2->xMin < xLimit;
802              word1 = word2, word2 = word2->next) {
803           fit2 = lineFit(line1, word0, word2);
804           if (fit2 >= 0 && (!word4 ||
805                             (word4 && fit2 < fit1))) {
806             fit1 = fit2;
807             word3 = word1;
808             word4 = word2;
809           }
810         }
811       }
812       if (word4) {
813         if (word3) {
814           word3->next = word4->next;
815         } else {
816           words = word4->next;
817         }
818         word0->next = word4;
819         word4->next = NULL;
820         if (word4->xMax > line1->xMax) {
821           line1->xMax = word4->xMax;
822         }
823         if (word4->yMin < line1->yMin) {
824           line1->yMin = word4->yMin;
825         }
826         if (word4->yMax > line1->yMax) {
827           line1->yMax = word4->yMax;
828         }
829         line1->len += word4->len;
830         if (fit1 > minSpace) {
831           word0->spaceAfter = gTrue;
832           ++line1->len;
833         }
834         word0 = word4;
835       } else {
836         break;
837       }
838     }
839
840     // build the line text
841     line1->text = (Unicode *)gmalloc(line1->len * sizeof(Unicode));
842     line1->xRight = (double *)gmalloc(line1->len * sizeof(double));
843     line1->col = (int *)gmalloc(line1->len * sizeof(int));
844     i = 0;
845     for (word1 = line1->words; word1; word1 = word1->next) {
846       for (j = 0; j < word1->len; ++j) {
847         line1->text[i] = word1->text[j];
848         line1->xRight[i] = word1->xRight[j];
849         ++i;
850       }
851       if (word1->spaceAfter && word1->next) {
852         line1->text[i] = (Unicode)0x0020;
853         line1->xRight[i] = word1->next->xMin;
854         ++i;
855       }
856     }
857     line1->convertedLen = 0;
858     for (j = 0; j < line1->len; ++j) {
859       line1->col[j] = line1->convertedLen;
860       if (isUnicode) {
861         ++line1->convertedLen;
862       } else if (uMap) {
863         line1->convertedLen +=
864           uMap->mapUnicode(line1->text[j], buf, sizeof(buf));
865       }
866     }
867
868     // check for hyphen at end of line
869     //~ need to check for other chars used as hyphens
870     if (line1->text[line1->len - 1] == (Unicode)'-') {
871       line1->hyphenated = gTrue;
872     }
873
874     // insert line on list
875     if (line0) {
876       line0->next = line1;
877     } else {
878       lineList = line1;
879     }
880     line0 = line1;
881   }
882
883   if (uMap) {
884     uMap->decRefCnt();
885   }
886
887 #if 0 // for debugging
888   printf("*** lines in xy order ***\n");
889   for (line0 = lineList; line0; line0 = line0->next) {
890     printf("[line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f len=%d]\n",
891            line0->xMin, line0->xMax, line0->yMin, line0->yMax,
892            line0->yBase, line0->len);
893     for (word0 = line0->words; word0; word0 = word0->next) {
894       printf("  word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSz=%.2f space=%d: '",
895              word0->xMin, word0->xMax, word0->yMin, word0->yMax,
896              word0->yBase, word0->fontSize, word0->spaceAfter);
897       for (i = 0; i < word0->len; ++i) {
898         fputc(word0->text[i] & 0xff, stdout);
899       }
900       printf("'\n");
901     }
902   }
903   printf("\n");
904   fflush(stdout);
905 #endif
906
907   //----- column assignment
908
909   for (line1 = lineList; line1; line1 = line1->next) {
910     col1 = 0;
911     for (line2 = lineList; line2 != line1; line2 = line2->next) {
912       if (line1->xMin >= line2->xMax) {
913         d = (int)((line1->xMin - line2->xMax) /
914                   (line1->font->maxSpaceWidth * line1->fontSize));
915         if (d > 4) {
916           d = 4;
917         }
918         col2 = line2->col[0] + line2->convertedLen + d;
919         if (col2 > col1) {
920           col1 = col2;
921         }
922       } else if (line1->xMin > line2->xMin) {
923         for (i = 0; i < line2->len && line1->xMin >= line2->xRight[i]; ++i) ;
924         col2 = line2->col[i];
925         if (col2 > col1) {
926           col1 = col2;
927         }
928       }
929     }
930     for (j = 0; j < line1->len; ++j) {
931       line1->col[j] += col1;
932     }
933   }
934
935   //----- assemble lines into blocks
936
937   if (rawOrder) {
938
939     lines = lineList;
940     for (line1 = lines; line1; line1 = line1->next) {
941       line1->xSpaceL = 0;
942       line1->xSpaceR = pageWidth;
943     }
944
945   } else {
946
947     // sort lines into yx order
948     lines = NULL;
949     while (lineList) {
950       line0 = lineList;
951       lineList = lineList->next;
952       for (line1 = NULL, line2 = lines;
953            line2 && !line0->yxBefore(line2);
954            line1 = line2, line2 = line2->next) ;
955       if (line1) {
956         line1->next = line0;
957       } else {
958         lines = line0;
959       }
960       line0->next = line2;
961     }
962
963     // compute whitespace to left and right of each line
964     line0 = lines;
965     for (line1 = lines; line1; line1 = line1->next) {
966
967       // find the first vertically overlapping line
968       for (; line0 && line0->yMax < line1->yMin; line0 = line0->next) ;
969
970       // check each vertically overlapping line -- look for the nearest
971       // on each side
972       line1->xSpaceL = 0;
973       line1->xSpaceR = pageWidth;
974       for (line2 = line0;
975            line2 && line2->yMin < line1->yMax;
976            line2 = line2->next) {
977         if (line2->yMax > line1->yMin) {
978           if (line2->xMax < line1->xMin) {
979             if (line2->xMax > line1->xSpaceL) {
980               line1->xSpaceL = line2->xMax;
981             }
982           } else if (line2->xMin > line1->xMax) {
983             if (line2->xMin < line1->xSpaceR) {
984               line1->xSpaceR = line2->xMin;
985             }
986           }
987         }
988       }
989     }
990   } // (!rawOrder)
991
992 #if 0 // for debugging
993   printf("*** lines in yx order ***\n");
994   for (line0 = lines; line0; line0 = line0->next) {
995     printf("[line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f xSpaceL=%.2f xSpaceR=%.2f len=%d]\n",
996            line0->xMin, line0->xMax, line0->yMin, line0->yMax,
997            line0->yBase, line0->xSpaceL, line0->xSpaceR, line0->len);
998     for (word0 = line0->words; word0; word0 = word0->next) {
999       printf("  word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSz=%.2f space=%d: '",
1000              word0->xMin, word0->xMax, word0->yMin, word0->yMax,
1001              word0->yBase, word0->fontSize, word0->spaceAfter);
1002       for (i = 0; i < word0->len; ++i) {
1003         fputc(word0->text[i] & 0xff, stdout);
1004       }
1005       printf("'\n");
1006     }
1007   }
1008   printf("\n");
1009   fflush(stdout);
1010 #endif
1011
1012   lineList = lines;
1013   yxBlocks = NULL;
1014   blk0 = NULL;
1015   while (lineList) {
1016
1017     // build a new block object
1018     line0 = lineList;
1019     lineList = lineList->next;
1020     line0->next = NULL;
1021     blk1 = new TextBlock();
1022     blk1->lines = line0;
1023     blk1->xMin = line0->xMin;
1024     blk1->xMax = line0->xMax;
1025     blk1->yMin = line0->yMin;
1026     blk1->yMax = line0->yMax;
1027     blk1->xSpaceL = line0->xSpaceL;
1028     blk1->xSpaceR = line0->xSpaceR;
1029     blk1->maxFontSize = line0->fontSize;
1030
1031     // find subsequent lines in the block
1032     while (lineList) {
1033
1034       // look for the first horizontally overlapping line below this
1035       // one
1036       yLimit = line0->yMax + blkMaxSpacing * line0->fontSize;
1037       line3 = line4 = NULL;
1038       if (rawOrder) {
1039         if (lineList->yMin < yLimit &&
1040             lineList->xMax > blk1->xMin &&
1041             lineList->xMin < blk1->xMax) {
1042           line3 = NULL;
1043           line4 = lineList;
1044         }
1045       } else {
1046         for (line1 = NULL, line2 = lineList;
1047              line2 && line2->yMin < yLimit;
1048              line1 = line2, line2 = line2->next) {
1049           if (line2->xMax > blk1->xMin &&
1050               line2->xMin < blk1->xMax) {
1051             line3 = line1;
1052             line4 = line2;
1053             break;
1054           }
1055         }
1056       }
1057
1058       // if there is an overlapping line and it fits in the block, add
1059       // it to the block
1060       if (line4 && blockFit(blk1, line4)) {
1061         if (line3) {
1062           line3->next = line4->next;
1063         } else {
1064           lineList = line4->next;
1065         }
1066         line0->next = line0->flowNext = line4;
1067         line4->next = NULL;
1068         if (line4->xMin < blk1->xMin) {
1069           blk1->xMin = line4->xMin;
1070         } else if (line4->xMax > blk1->xMax) {
1071           blk1->xMax = line4->xMax;
1072         }
1073         if (line4->yMax > blk1->yMax) {
1074           blk1->yMax = line4->yMax;
1075         }
1076         if (line4->xSpaceL > blk1->xSpaceL) {
1077           blk1->xSpaceL = line4->xSpaceL;
1078         }
1079         if (line4->xSpaceR < blk1->xSpaceR) {
1080           blk1->xSpaceR = line4->xSpaceR;
1081         }
1082         if (line4->fontSize > blk1->maxFontSize) {
1083           blk1->maxFontSize = line4->fontSize;
1084         }
1085         line0 = line4;
1086
1087       // otherwise, we're done with this block
1088       } else {
1089         break;
1090       }
1091     }
1092
1093     // insert block on list, in yx order
1094     if (rawOrder) {
1095       blk2 = blk0;
1096       blk3 = NULL;
1097       blk0 = blk1;
1098     } else {
1099       for (blk2 = NULL, blk3 = yxBlocks;
1100            blk3 && !blk1->yxBefore(blk3);
1101            blk2 = blk3, blk3 = blk3->next) ;
1102     }
1103     blk1->next = blk3;
1104     if (blk2) {
1105       blk2->next = blk1;
1106     } else {
1107       yxBlocks = blk1;
1108     }
1109   }
1110
1111 #if 0 // for debugging
1112   printf("*** blocks in yx order ***\n");
1113   for (blk0 = yxBlocks; blk0; blk0 = blk0->next) {
1114     printf("[block: x=%.2f..%.2f y=%.2f..%.2f]\n",
1115            blk0->xMin, blk0->xMax, blk0->yMin, blk0->yMax);
1116     for (line0 = blk0->lines; line0; line0 = line0->next) {
1117       printf("  [line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f len=%d]\n",
1118              line0->xMin, line0->xMax, line0->yMin, line0->yMax,
1119              line0->yBase, line0->len);
1120       for (word0 = line0->words; word0; word0 = word0->next) {
1121         printf("    word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f space=%d: '",
1122                word0->xMin, word0->xMax, word0->yMin, word0->yMax,
1123                word0->yBase, word0->spaceAfter);
1124         for (i = 0; i < word0->len; ++i) {
1125           fputc(word0->text[i] & 0xff, stdout);
1126         }
1127         printf("'\n");
1128       }
1129     }
1130   }
1131   printf("\n");
1132   fflush(stdout);
1133 #endif
1134
1135   //----- merge lines and blocks, sort blocks into reading order
1136
1137   if (rawOrder) {
1138     blocks = yxBlocks;
1139
1140   } else {
1141     blocks = NULL;
1142     blk0 = NULL;
1143     blkStack = NULL;
1144     while (yxBlocks) {
1145
1146       // find the next two blocks:
1147       // - if the depth-first traversal stack is empty, take the first
1148       //   (upper-left-most) two blocks on the yx-sorted block list
1149       // - otherwise, find the two upper-left-most blocks under the top
1150       //   block on the stack
1151       if (blkStack) {
1152         blk3 = blk4 = blk5 = blk6 = NULL;
1153         for (blk1 = NULL, blk2 = yxBlocks;
1154              blk2;
1155              blk1 = blk2, blk2 = blk2->next) {
1156           if (blk2->yMin > blkStack->yMin &&
1157               blk2->xMax > blkStack->xMin &&
1158               blk2->xMin < blkStack->xMax) {
1159             if (!blk4 || blk2->yxBefore(blk4)) {
1160               blk5 = blk3;
1161               blk6 = blk4;
1162               blk3 = blk1;
1163               blk4 = blk2;
1164             } else if (!blk6 || blk2->yxBefore(blk6)) {
1165               blk5 = blk1;
1166               blk6 = blk2;
1167             }
1168           }
1169         }
1170       } else {
1171         blk3 = NULL;
1172         blk4 = yxBlocks;
1173         blk5 = yxBlocks;
1174         blk6 = yxBlocks->next;
1175       }
1176
1177       // merge case 1:
1178       //   |                     |           |
1179       //   |   blkStack          |           |   blkStack
1180       //   +---------------------+    -->    +--------------
1181       //   +------+ +------+                 +-----------+
1182       //   | blk4 | | blk6 | ...             | blk4+blk6 |
1183       //   +------+ +------+                 +-----------+
1184       if (blkStack) {
1185         yLimit = blkStack->yMax + blkMaxSpacing * blkStack->lines->fontSize;
1186       }
1187       if (blkStack && blk4 && blk6 &&
1188           !blk4->lines->next && !blk6->lines->next &&
1189           lineFit2(blk4->lines, blk6->lines) &&
1190           blk4->yMin < yLimit &&
1191           blk4->xMin > blkStack->xSpaceL &&
1192           blkStack->xMin > blk4->xSpaceL &&
1193           blk6->xMax < blkStack->xSpaceR) {
1194         blk4->mergeRight(blk6);
1195         if (blk5) {
1196           blk5->next = blk6->next;
1197         } else {
1198           yxBlocks = blk6->next;
1199         }
1200         delete blk6;
1201
1202       // merge case 2:
1203       //   |                     |           |                   |
1204       //   |   blkStack          |           |                   |
1205       //   +---------------------+    -->    |   blkStack+blk2   |
1206       //   +---------------------+           |                   |
1207       //   |   blk4              |           |                   |
1208       //   |                     |           |                   |
1209       } else if (blkStack && blk4 &&
1210                  blk4->yMin < yLimit &&
1211                  blockFit2(blkStack, blk4)) {
1212         blkStack->mergeBelow(blk4);
1213         if (blk3) {
1214           blk3->next = blk4->next;
1215         } else {
1216           yxBlocks = blk4->next;
1217         }
1218         delete blk4;
1219
1220       // if any of:
1221       // 1. no block found
1222       // 2. non-fully overlapping block found
1223       // 3. large vertical gap above the overlapping block
1224       // then pop the stack and try again
1225       } else if (!blk4 ||
1226                  (blkStack && (blk4->xMin < blkStack->xSpaceL ||
1227                                blk4->xMax > blkStack->xSpaceR ||
1228                                blk4->yMin - blkStack->yMax >
1229                                  blkMaxSortSpacing * blkStack->maxFontSize))) {
1230         blkStack = blkStack->stackNext;
1231
1232       // add a block to the sorted list
1233       } else {
1234
1235         // remove the block from the yx-sorted list
1236         if (blk3) {
1237           blk3->next = blk4->next;
1238         } else {
1239           yxBlocks = blk4->next;
1240         }
1241         blk4->next = NULL;
1242
1243         // append the block to the reading-order list
1244         if (blk0) {
1245           blk0->next = blk4;
1246         } else {
1247           blocks = blk4;
1248         }
1249         blk0 = blk4;
1250
1251         // push the block on the traversal stack
1252         blk4->stackNext = blkStack;
1253         blkStack = blk4;
1254       }
1255     }
1256   } // (!rawOrder)
1257
1258 #if 0 // for debugging
1259   printf("*** blocks in reading order (after merging) ***\n");
1260   for (blk0 = blocks; blk0; blk0 = blk0->next) {
1261     printf("[block: x=%.2f..%.2f y=%.2f..%.2f]\n",
1262            blk0->xMin, blk0->xMax, blk0->yMin, blk0->yMax);
1263     for (line0 = blk0->lines; line0; line0 = line0->next) {
1264       printf("  [line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f len=%d]\n",
1265              line0->xMin, line0->xMax, line0->yMin, line0->yMax,
1266              line0->yBase, line0->len);
1267       for (word0 = line0->words; word0; word0 = word0->next) {
1268         printf("    word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f space=%d: '",
1269                word0->xMin, word0->xMax, word0->yMin, word0->yMax,
1270                word0->yBase, word0->spaceAfter);
1271         for (i = 0; i < word0->len; ++i) {
1272           fputc(word0->text[i] & 0xff, stdout);
1273         }
1274         printf("'\n");
1275       }
1276     }
1277   }
1278   printf("\n");
1279   fflush(stdout);
1280 #endif
1281
1282   //----- assemble blocks into flows
1283
1284   if (rawOrder) {
1285
1286     // one flow per block
1287     flow0 = NULL;
1288     while (blocks) {
1289       flow1 = new TextFlow();
1290       flow1->blocks = blocks;
1291       flow1->lines = blocks->lines;
1292       flow1->yMin = blocks->yMin;
1293       flow1->yMax = blocks->yMax;
1294       blocks = blocks->next;
1295       flow1->blocks->next = NULL;
1296       if (flow0) {
1297         flow0->next = flow1;
1298       } else {
1299         flows = flow1;
1300       }
1301       flow0 = flow1;
1302     }
1303
1304   } else {
1305
1306     // compute whitespace above and below each block
1307     for (blk0 = blocks; blk0; blk0 = blk0->next) {
1308       blk0->ySpaceT = 0;
1309       blk0->ySpaceB = pageHeight;
1310
1311       // check each horizontally overlapping block
1312       for (blk1 = blocks; blk1; blk1 = blk1->next) {
1313         if (blk1 != blk0 &&
1314             blk1->xMin < blk0->xMax &&
1315             blk1->xMax > blk0->xMin) {
1316           if (blk1->yMax < blk0->yMin) {
1317             if (blk1->yMax > blk0->ySpaceT) {
1318               blk0->ySpaceT = blk1->yMax;
1319             }
1320           } else if (blk1->yMin > blk0->yMax) {
1321             if (blk1->yMin < blk0->ySpaceB) {
1322               blk0->ySpaceB = blk1->yMin;
1323             }
1324           }
1325         }
1326       }
1327     }
1328
1329     flow0 = NULL;
1330     while (blocks) {
1331
1332       // build a new flow object
1333       flow1 = new TextFlow();
1334       flow1->blocks = blocks;
1335       flow1->lines = blocks->lines;
1336       flow1->yMin = blocks->yMin;
1337       flow1->yMax = blocks->yMax;
1338       flow1->ySpaceT = blocks->ySpaceT;
1339       flow1->ySpaceB = blocks->ySpaceB;
1340
1341       // find subsequent blocks in the flow
1342       for (blk1 = blocks, blk2 = blocks->next;
1343            blk2 && flowFit(flow1, blk2);
1344            blk1 = blk2, blk2 = blk2->next) {
1345         if (blk2->yMin < flow1->yMin) {
1346           flow1->yMin = blk2->yMin;
1347         }
1348         if (blk2->yMax > flow1->yMax) {
1349           flow1->yMax = blk2->yMax;
1350         }
1351         if (blk2->ySpaceT > flow1->ySpaceT) {
1352           flow1->ySpaceT = blk2->ySpaceT;
1353         }
1354         if (blk2->ySpaceB < flow1->ySpaceB) {
1355           flow1->ySpaceB = blk2->ySpaceB;
1356         }
1357         for (line1 = blk1->lines; line1->next; line1 = line1->next) ;
1358         line1->flowNext = blk2->lines;
1359       }
1360
1361       // chop the block list
1362       blocks = blk1->next;
1363       blk1->next = NULL;
1364
1365       // append the flow to the list
1366       if (flow0) {
1367         flow0->next = flow1;
1368       } else {
1369         flows = flow1;
1370       }
1371       flow0 = flow1;
1372     }
1373   }
1374
1375 #if 0 // for debugging
1376   printf("*** flows ***\n");
1377   for (flow0 = flows; flow0; flow0 = flow0->next) {
1378     printf("[flow]\n");
1379     for (blk0 = flow0->blocks; blk0; blk0 = blk0->next) {
1380       printf("  [block: x=%.2f..%.2f y=%.2f..%.2f ySpaceT=%.2f ySpaceB=%.2f]\n",
1381              blk0->xMin, blk0->xMax, blk0->yMin, blk0->yMax,
1382              blk0->ySpaceT, blk0->ySpaceB);
1383       for (line0 = blk0->lines; line0; line0 = line0->next) {
1384         printf("    [line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f len=%d]\n",
1385                line0->xMin, line0->xMax, line0->yMin, line0->yMax,
1386                line0->yBase, line0->len);
1387         for (word0 = line0->words; word0; word0 = word0->next) {
1388           printf("      word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f space=%d: '",
1389                  word0->xMin, word0->xMax, word0->yMin, word0->yMax,
1390                  word0->yBase, word0->spaceAfter);
1391           for (i = 0; i < word0->len; ++i) {
1392             fputc(word0->text[i] & 0xff, stdout);
1393           }
1394           printf("'\n");
1395         }
1396       }
1397     }
1398   }
1399   printf("\n");
1400   fflush(stdout);
1401 #endif
1402
1403   //----- sort lines into yx order
1404
1405   // (the block/line merging process doesn't maintain the full-page
1406   // linked list of lines)
1407
1408   lines = NULL;
1409   if (rawOrder) {
1410     line0 = NULL;
1411     for (flow0 = flows; flow0; flow0 = flow0->next) {
1412       for (line1 = flow0->lines; line1; line1 = line1->flowNext) {
1413         if (line0) {
1414           line0->pageNext = line1;
1415         } else {
1416           lines = line1;
1417         }
1418         line0 = line1;
1419       }
1420     }
1421   } else {
1422     for (flow0 = flows; flow0; flow0 = flow0->next) {
1423       for (line0 = flow0->lines; line0; line0 = line0->flowNext) {
1424         for (line1 = NULL, line2 = lines;
1425              line2 && !line0->yxBefore(line2);
1426              line1 = line2, line2 = line2->pageNext) ;
1427         if (line1) {
1428           line1->pageNext = line0;
1429         } else {
1430           lines = line0;
1431         }
1432         line0->pageNext = line2;
1433       }
1434     }
1435   }
1436
1437 #if 0 // for debugging
1438   printf("*** lines in yx order ***\n");
1439   for (line0 = lines; line0; line0 = line0->pageNext) {
1440     printf("[line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f xSpaceL=%.2f xSpaceR=%.2f col=%d len=%d]\n",
1441            line0->xMin, line0->xMax, line0->yMin, line0->yMax,
1442            line0->yBase, line0->xSpaceL, line0->xSpaceR, line0->col[0],
1443            line0->len);
1444     for (word0 = line0->words; word0; word0 = word0->next) {
1445       printf("  word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f space=%d: '",
1446              word0->xMin, word0->xMax, word0->yMin, word0->yMax,
1447              word0->yBase, word0->spaceAfter);
1448       for (i = 0; i < word0->len; ++i) {
1449         fputc(word0->text[i] & 0xff, stdout);
1450       }
1451       printf("'\n");
1452     }
1453   }
1454   printf("\n");
1455   fflush(stdout);
1456 #endif
1457 }
1458
1459 // Returns a non-negative number if <word> can be added to <line>
1460 // (whose last word is <lastWord>).  A smaller return value indicates
1461 // a better fit.  If <word> cannot be added to <line> at all, returns
1462 // a negative number.
1463 double TextPage::lineFit(TextLine *line, TextWord *lastWord, TextWord *word) {
1464   double fontSize0, fontSize1;
1465   double dx, dxLimit;
1466
1467   fontSize0 = line->fontSize;
1468   fontSize1 = word->fontSize;
1469   dx = word->xMin - lastWord->xMax;
1470   dxLimit = fontSize0 * line->font->maxSpaceWidth;
1471
1472   // check inter-word spacing
1473   if (dx < fontSize0 * lineMinDeltaX ||
1474       dx > dxLimit) {
1475     return -1;
1476   }
1477
1478   // ensure a non-negative return value
1479   if (dx < 0) {
1480     dx = 0;
1481   }
1482
1483   // look for adjacent words with close baselines and close font sizes
1484   if (fabs(line->yBase - word->yBase) < lineMaxBaselineDelta * fontSize0 &&
1485       fontSize0 < lineMaxFontSizeRatio * fontSize1 &&
1486       fontSize1 < lineMaxFontSizeRatio * fontSize0) {
1487     return dx;
1488   }
1489
1490   // look for a superscript
1491   if (fontSize1 > lineMinSuperscriptFontSizeRatio * fontSize0 &&
1492       fontSize1 < lineMaxSuperscriptFontSizeRatio * fontSize0 &&
1493       (word->yMax < lastWord->yMax ||
1494        word->yBase < lastWord->yBase) &&
1495       word->yMax - lastWord->yMin > lineMinSuperscriptOverlap * fontSize0 &&
1496       dx < fontSize0 * lineMaxSuperscriptDeltaX) {
1497     return dx;
1498   }
1499
1500   // look for a subscript
1501   if (fontSize1 > lineMinSubscriptFontSizeRatio * fontSize0 &&
1502       fontSize1 < lineMaxSubscriptFontSizeRatio * fontSize0 &&
1503       (word->yMin > lastWord->yMin ||
1504        word->yBase > lastWord->yBase) &&
1505       line->yMax - word->yMin > lineMinSubscriptOverlap * fontSize0 &&
1506       dx < fontSize0 * lineMaxSubscriptDeltaX) {
1507     return dx;
1508   }
1509
1510   return -1;
1511 }
1512
1513 // Returns true if <line0> and <line1> can be merged into a single
1514 // line, ignoring max word spacing.
1515 GBool TextPage::lineFit2(TextLine *line0, TextLine *line1) {
1516   double fontSize0, fontSize1;
1517   double dx;
1518
1519   fontSize0 = line0->fontSize;
1520   fontSize1 = line1->fontSize;
1521   dx = line1->xMin - line0->xMax;
1522
1523   // check inter-word spacing
1524   if (dx < fontSize0 * lineMinDeltaX) {
1525     return gFalse;
1526   }
1527
1528   // look for close baselines and close font sizes
1529   if (fabs(line0->yBase - line1->yBase) < lineMaxBaselineDelta * fontSize0 &&
1530       fontSize0 < lineMaxFontSizeRatio * fontSize1 &&
1531       fontSize1 < lineMaxFontSizeRatio * fontSize0) {
1532     return gTrue;
1533   }
1534
1535   return gFalse;
1536 }
1537
1538 // Returns true if <line> can be added to <blk>.  Assumes the y
1539 // coordinates are within range.
1540 GBool TextPage::blockFit(TextBlock *blk, TextLine *line) {
1541   double fontSize0, fontSize1;
1542
1543   // check edges
1544   if (line->xMin < blk->xSpaceL ||
1545       line->xMax > blk->xSpaceR ||
1546       blk->xMin < line->xSpaceL ||
1547       blk->xMax > line->xSpaceR) {
1548     return gFalse;
1549   }
1550
1551   // check font sizes
1552   fontSize0 = blk->lines->fontSize;
1553   fontSize1 = line->fontSize;
1554   if (fontSize0 > blkMaxFontSizeRatio * fontSize1 ||
1555       fontSize1 > blkMaxFontSizeRatio * fontSize0) {
1556     return gFalse;
1557   }
1558
1559   return gTrue;
1560 }
1561
1562 // Returns true if <blk0> and <blk1> can be merged into a single
1563 // block.  Assumes the y coordinates are within range.
1564 GBool TextPage::blockFit2(TextBlock *blk0, TextBlock *blk1) {
1565   double fontSize0, fontSize1;
1566
1567   // check edges
1568   if (blk1->xMin < blk0->xSpaceL ||
1569       blk1->xMax > blk0->xSpaceR ||
1570       blk0->xMin < blk1->xSpaceL ||
1571       blk0->xMax > blk1->xSpaceR) {
1572     return gFalse;
1573   }
1574
1575   // check font sizes
1576   fontSize0 = blk0->lines->fontSize;
1577   fontSize1 = blk1->lines->fontSize;
1578   if (fontSize0 > blkMaxFontSizeRatio * fontSize1 ||
1579       fontSize1 > blkMaxFontSizeRatio * fontSize0) {
1580     return gFalse;
1581   }
1582
1583   return gTrue;
1584 }
1585
1586 // Returns true if <blk> can be added to <flow>.
1587 GBool TextPage::flowFit(TextFlow *flow, TextBlock *blk) {
1588   double dy;
1589
1590   // check whitespace above and below
1591   if (blk->yMin < flow->ySpaceT ||
1592       blk->yMax > flow->ySpaceB ||
1593       flow->yMin < blk->ySpaceT ||
1594       flow->yMax > blk->ySpaceB) {
1595     return gFalse;
1596   }
1597
1598   // check that block top edge is within +/- dy of flow top edge,
1599   // and that block bottom edge is above flow bottom edge + dy
1600   dy = flowMaxDeltaY * flow->blocks->maxFontSize;
1601   return blk->yMin > flow->yMin - dy &&
1602          blk->yMin < flow->yMin + dy &&
1603          blk->yMax < flow->yMax + dy;
1604 }
1605
1606
1607 GBool TextPage::findText(Unicode *s, int len,
1608                          GBool top, GBool bottom,
1609                          double *xMin, double *yMin,
1610                          double *xMax, double *yMax) {
1611   TextLine *line;
1612   Unicode *p;
1613   Unicode u1, u2;
1614   int m, i, j;
1615   double x0, x1, x;
1616
1617   // scan all text on the page
1618   for (line = lines; line; line = line->pageNext) {
1619
1620     // check: above top limit?
1621     if (!top && (line->yMax < *yMin ||
1622                  (line->yMin < *yMin && line->xMax <= *xMin))) {
1623       continue;
1624     }
1625
1626     // check: below bottom limit?
1627     if (!bottom && (line->yMin > *yMax ||
1628                     (line->yMax > *yMax && line->xMin >= *xMax))) {
1629       return gFalse;
1630     }
1631
1632     // search each position in this line
1633     m = line->len;
1634     for (i = 0, p = line->text; i <= m - len; ++i, ++p) {
1635
1636       x0 = (i == 0) ? line->xMin : line->xRight[i-1];
1637       x1 = line->xRight[i];
1638       x = 0.5 * (x0 + x1);
1639
1640       // check: above top limit?
1641       if (!top && line->yMin < *yMin) {
1642         if (x < *xMin) {
1643           continue;
1644         }
1645       }
1646
1647       // check: below bottom limit?
1648       if (!bottom && line->yMax > *yMax) {
1649         if (x > *xMax) {
1650           return gFalse;
1651         }
1652       }
1653
1654       // compare the strings
1655       for (j = 0; j < len; ++j) {
1656 #if 1 //~ this lowercases Latin A-Z only -- this will eventually be
1657       //~ extended to handle other character sets
1658         if (p[j] >= 0x41 && p[j] <= 0x5a) {
1659           u1 = p[j] + 0x20;
1660         } else {
1661           u1 = p[j];
1662         }
1663         if (s[j] >= 0x41 && s[j] <= 0x5a) {
1664           u2 = s[j] + 0x20;
1665         } else {
1666           u2 = s[j];
1667         }
1668 #endif
1669         if (u1 != u2) {
1670           break;
1671         }
1672       }
1673
1674       // found it
1675       if (j == len) {
1676         *xMin = x0;
1677         *xMax = line->xRight[i + len - 1];
1678         *yMin = line->yMin;
1679         *yMax = line->yMax;
1680         return gTrue;
1681       }
1682     }
1683   }
1684
1685   return gFalse;
1686 }
1687
1688 GString *TextPage::getText(double xMin, double yMin,
1689                            double xMax, double yMax) {
1690   GString *s;
1691   UnicodeMap *uMap;
1692   GBool isUnicode;
1693   char space[8], eol[16], buf[8];
1694   int spaceLen, eolLen, len;
1695   TextLine *line, *prevLine;
1696   double x0, x1, y;
1697   int firstCol, col, i;
1698   GBool multiLine;
1699
1700   s = new GString();
1701
1702   // get the output encoding
1703   if (!(uMap = globalParams->getTextEncoding())) {
1704     return s;
1705   }
1706   isUnicode = uMap->isUnicode();
1707   spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
1708   eolLen = 0; // make gcc happy
1709   switch (globalParams->getTextEOL()) {
1710   case eolUnix:
1711     eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
1712     break;
1713   case eolDOS:
1714     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
1715     eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
1716     break;
1717   case eolMac:
1718     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
1719     break;
1720   }
1721
1722   // find the leftmost column
1723   firstCol = -1;
1724   for (line = lines; line; line = line->pageNext) {
1725     if (line->yMin > yMax) {
1726       break;
1727     }
1728     if (line->yMax < yMin || 
1729         line->xMax < xMin ||
1730         line->xMin > xMax) {
1731       continue;
1732     }
1733
1734     y = 0.5 * (line->yMin + line->yMax);
1735     if (y < yMin || y > yMax) {
1736       continue;
1737     }
1738
1739     i = 0;
1740     while (1) {
1741       x0 = (i==0) ? line->xMin : line->xRight[i-1];
1742       x1 = line->xRight[i];
1743       if (0.5 * (x0 + x1) > xMin) {
1744         break;
1745       }
1746       ++i;
1747     }
1748     col = line->col[i];
1749
1750     if (firstCol < 0 || col < firstCol) {
1751       firstCol = col;
1752     }
1753   }
1754
1755   // extract the text
1756   col = firstCol;
1757   multiLine = gFalse;
1758   for (prevLine = NULL, line = lines;
1759        line;
1760        prevLine = line, line = line->pageNext) {
1761     if (line->yMin > yMax) {
1762       break;
1763     }
1764     if (line->yMax < yMin || 
1765         line->xMax < xMin ||
1766         line->xMin > xMax) {
1767       continue;
1768     }
1769
1770     y = 0.5 * (line->yMin + line->yMax);
1771     if (y < yMin || y > yMax) {
1772       continue;
1773     }
1774
1775     i = 0;
1776     while (1) {
1777       x0 = (i==0) ? line->xMin : line->xRight[i-1];
1778       x1 = line->xRight[i];
1779       if (0.5 * (x0 + x1) > xMin) {
1780         break;
1781       }
1782       ++i;
1783     }
1784
1785     // insert a return
1786     if (col > line->col[i] ||
1787         (prevLine &&
1788          line->yMin >
1789            prevLine->yMax - lineOverlapSlack * prevLine->fontSize)) {
1790       s->append(eol, eolLen);
1791       col = firstCol;
1792       multiLine = gTrue;
1793     }
1794
1795     // line this block up with the correct column
1796     for (; col < line->col[i]; ++col) {
1797       s->append(space, spaceLen);
1798     }
1799
1800     // print the portion of the line
1801     for (; i < line->len; ++i) {
1802
1803       x0 = (i==0) ? line->xMin : line->xRight[i-1];
1804       x1 = line->xRight[i];
1805       if (0.5 * (x0 + x1) > xMax) {
1806         break;
1807       }
1808
1809       len = uMap->mapUnicode(line->text[i], buf, sizeof(buf));
1810       s->append(buf, len);
1811       col += isUnicode ? 1 : len;
1812     }
1813   }
1814
1815   if (multiLine) {
1816     s->append(eol, eolLen);
1817   }
1818
1819   uMap->decRefCnt();
1820
1821   return s;
1822 }
1823
1824 void TextPage::dump(void *outputStream, TextOutputFunc outputFunc,
1825                     GBool physLayout) {
1826   UnicodeMap *uMap;
1827   char space[8], eol[16], eop[8], buf[8];
1828   int spaceLen, eolLen, eopLen, len;
1829   TextFlow *flow;
1830   TextLine *line;
1831   int col, d, n, i;
1832
1833   // get the output encoding
1834   if (!(uMap = globalParams->getTextEncoding())) {
1835     return;
1836   }
1837   spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
1838   eolLen = 0; // make gcc happy
1839   switch (globalParams->getTextEOL()) {
1840   case eolUnix:
1841     eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
1842     break;
1843   case eolDOS:
1844     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
1845     eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
1846     break;
1847   case eolMac:
1848     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
1849     break;
1850   }
1851   eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop));
1852
1853   // output the page, maintaining the original physical layout
1854   if (physLayout || rawOrder) {
1855     col = 0;
1856     for (line = lines; line; line = line->pageNext) {
1857
1858       // line this block up with the correct column
1859       if (!rawOrder) {
1860         for (; col < line->col[0]; ++col) {
1861           (*outputFunc)(outputStream, space, spaceLen);
1862         }
1863       }
1864
1865       // print the line
1866       for (i = 0; i < line->len; ++i) {
1867         len = uMap->mapUnicode(line->text[i], buf, sizeof(buf));
1868         (*outputFunc)(outputStream, buf, len);
1869       }
1870       col += line->convertedLen;
1871
1872       // print one or more returns if necessary
1873       if (!line->pageNext ||
1874           line->pageNext->col[0] < col ||
1875           line->pageNext->yMin >
1876             line->yMax - lineOverlapSlack * line->fontSize) {
1877
1878         // compute number of returns
1879         d = 1;
1880         if (line->pageNext) {
1881           d += (int)((line->pageNext->yMin - line->yMax) /
1882                      line->fontSize + 0.5);
1883         }
1884
1885         // various things (weird font matrices) can result in bogus
1886         // values here, so do a sanity check
1887         if (d < 1) {
1888           d = 1;
1889         } else if (d > 5) {
1890           d = 5;
1891         }
1892         for (; d > 0; --d) {
1893           (*outputFunc)(outputStream, eol, eolLen);
1894         }
1895
1896         col = 0;
1897       }
1898     }
1899
1900   // output the page, "undoing" the layout
1901   } else {
1902     for (flow = flows; flow; flow = flow->next) {
1903       for (line = flow->lines; line; line = line->flowNext) {
1904         n = line->len;
1905         if (line->flowNext && line->hyphenated) {
1906           --n;
1907         }
1908         for (i = 0; i < n; ++i) {
1909           len = uMap->mapUnicode(line->text[i], buf, sizeof(buf));
1910           (*outputFunc)(outputStream, buf, len);
1911         }
1912         if (line->flowNext && !line->hyphenated) {
1913           (*outputFunc)(outputStream, space, spaceLen);
1914         }
1915       }
1916       (*outputFunc)(outputStream, eol, eolLen);
1917       (*outputFunc)(outputStream, eol, eolLen);
1918     }
1919   }
1920
1921   // end of page
1922   (*outputFunc)(outputStream, eop, eopLen);
1923   (*outputFunc)(outputStream, eol, eolLen);
1924
1925   uMap->decRefCnt();
1926 }
1927
1928 void TextPage::startPage(GfxState *state) {
1929   clear();
1930   pageWidth = state->getPageWidth();
1931   pageHeight = state->getPageHeight();
1932 }
1933
1934 void TextPage::clear() {
1935   TextWord *w1, *w2;
1936   TextFlow *f1, *f2;
1937
1938   if (curWord) {
1939     delete curWord;
1940     curWord = NULL;
1941   }
1942   if (words) {
1943     for (w1 = words; w1; w1 = w2) {
1944       w2 = w1->next;
1945       delete w1;
1946     }
1947   } else if (flows) {
1948     for (f1 = flows; f1; f1 = f2) {
1949       f2 = f1->next;
1950       delete f1;
1951     }
1952   }
1953   deleteGList(fonts, TextFontInfo);
1954
1955   curWord = NULL;
1956   font = NULL;
1957   fontSize = 0;
1958   nest = 0;
1959   nTinyChars = 0;
1960   words = wordPtr = NULL;
1961   lines = NULL;
1962   flows = NULL;
1963   fonts = new GList();
1964
1965 }
1966
1967
1968 //------------------------------------------------------------------------
1969 // TextOutputDev
1970 //------------------------------------------------------------------------
1971
1972 static void outputToFile(void *stream, char *text, int len) {
1973   fwrite(text, 1, len, (FILE *)stream);
1974 }
1975
1976 TextOutputDev::TextOutputDev(char *fileName, GBool physLayoutA,
1977                              GBool rawOrderA, GBool append) {
1978   text = NULL;
1979   physLayout = physLayoutA;
1980   rawOrder = rawOrderA;
1981   ok = gTrue;
1982
1983   // open file
1984   needClose = gFalse;
1985   if (fileName) {
1986     if (!strcmp(fileName, "-")) {
1987       outputStream = stdout;
1988     } else if ((outputStream = fopen(fileName, append ? "ab" : "wb"))) {
1989       needClose = gTrue;
1990     } else {
1991       error(-1, "Couldn't open text file '%s'", fileName);
1992       ok = gFalse;
1993       return;
1994     }
1995     outputFunc = &outputToFile;
1996   } else {
1997     outputStream = NULL;
1998   }
1999
2000   // set up text object
2001   text = new TextPage(rawOrderA);
2002 }
2003
2004 TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream,
2005                              GBool physLayoutA, GBool rawOrderA) {
2006   outputFunc = func;
2007   outputStream = stream;
2008   needClose = gFalse;
2009   physLayout = physLayoutA;
2010   rawOrder = rawOrderA;
2011   text = new TextPage(rawOrderA);
2012   ok = gTrue;
2013 }
2014
2015 TextOutputDev::~TextOutputDev() {
2016   if (needClose) {
2017 #ifdef MACOS
2018     ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle);
2019 #endif
2020     fclose((FILE *)outputStream);
2021   }
2022   if (text) {
2023     delete text;
2024   }
2025 }
2026
2027 void TextOutputDev::startPage(int pageNum, GfxState *state) {
2028   text->startPage(state);
2029 }
2030
2031 void TextOutputDev::endPage() {
2032   text->coalesce();
2033   if (outputStream) {
2034     text->dump(outputStream, outputFunc, physLayout);
2035   }
2036 }
2037
2038 void TextOutputDev::updateFont(GfxState *state) {
2039   text->updateFont(state);
2040 }
2041
2042 void TextOutputDev::beginString(GfxState *state, GString *s) {
2043   text->beginWord(state, state->getCurX(), state->getCurY());
2044 }
2045
2046 void TextOutputDev::endString(GfxState *state) {
2047   text->endWord();
2048 }
2049
2050 void TextOutputDev::drawChar(GfxState *state, double x, double y,
2051                              double dx, double dy,
2052                              double originX, double originY,
2053                              CharCode c, Unicode *u, int uLen) {
2054   text->addChar(state, x, y, dx, dy, c, u, uLen);
2055 }
2056
2057 GBool TextOutputDev::findText(Unicode *s, int len,
2058                               GBool top, GBool bottom,
2059                               double *xMin, double *yMin,
2060                               double *xMax, double *yMax) {
2061   return text->findText(s, len, top, bottom, xMin, yMin, xMax, yMax);
2062 }
2063
2064 GString *TextOutputDev::getText(double xMin, double yMin,
2065                                 double xMax, double yMax) {
2066   return text->getText(xMin, yMin, xMax, yMax);
2067 }
2068
2069
2070