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