TextOutputDev.cpp
1//========================================================================
2//
3// TextOutputDev.cpp
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 "GString.h"
21#include "gmem.h"
22#include "config.h"
23#include "Error.h"
24#include "GlobalParams.h"
25#include "UnicodeMap.h"
26#include "GfxState.h"
27#include "TextOutputDev.h"
28
29#ifdef MACOS
30// needed for setting type/creator of MacOS files
31#include "ICSupport.h"
32#endif
33
34//------------------------------------------------------------------------
35
36#define textOutSpace 0.2
37#define textOutColSpace 0.2
38
39//------------------------------------------------------------------------
40
41struct TextOutColumnEdge {
42 double x, y0, y1;
43};
44
45//------------------------------------------------------------------------
46// TextBlock
47//------------------------------------------------------------------------
48
49TextBlock::TextBlock() {
50 strings = NULL;
51 next = NULL;
52 xyNext = NULL;
53 text = NULL;
54 xRight = NULL;
55 col = NULL;
56}
57
58TextBlock::~TextBlock() {
59 TextString *p1, *p2;
60
61 for (p1 = strings; p1; p1 = p2) {
62 p2 = p1->next;
63 delete p1;
64 }
65 gfree(text);
66 gfree(xRight);
67 gfree(col);
68}
69
70//------------------------------------------------------------------------
71// TextLine
72//------------------------------------------------------------------------
73
74TextLine::TextLine() {
75 blocks = NULL;
76 next = NULL;
77}
78
79TextLine::~TextLine() {
80 TextBlock *p1, *p2;
81
82 for (p1 = blocks; p1; p1 = p2) {
83 p2 = p1->next;
84 delete p1;
85 }
86}
87
88//------------------------------------------------------------------------
89// TextString
90//------------------------------------------------------------------------
91
92TextString::TextString(GfxState *state, double x0, double y0,
93 double fontSize) {
94 GfxFont *font;
95 double x, y;
96
97 state->transform(x0, y0, &x, &y);
98 if ((font = state->getFont())) {
99 yMin = y - font->getAscent() * fontSize;
100 yMax = y - font->getDescent() * fontSize;
101 } else {
102 // this means that the PDF file draws text without a current font,
103 // which should never happen
104 yMin = y - 0.95 * fontSize;
105 yMax = y + 0.35 * fontSize;
106 }
107 if (yMin == yMax) {
108 // this is a sanity check for a case that shouldn't happen -- but
109 // if it does happen, we want to avoid dividing by zero later
110 yMin = y;
111 yMax = y + 1;
112 }
113 marked = gFalse;
114 text = NULL;
115 xRight = NULL;
116 len = size = 0;
117 next = NULL;
118}
119
120
121TextString::~TextString() {
122 gfree(text);
123 gfree(xRight);
124}
125
126void TextString::addChar(GfxState */*state*/, double x, double /*y*/,
127 double dx, double /*dy*/, Unicode u) {
128 if (len == size) {
129 size += 16;
130 text = (Unicode *)grealloc(text, size * sizeof(Unicode));
131 xRight = (double *)grealloc(xRight, size * sizeof(double));
132 }
133 text[len] = u;
134 if (len == 0) {
135 xMin = x;
136 }
137 xMax = xRight[len] = x + dx;
138 ++len;
139}
140
141//------------------------------------------------------------------------
142// TextPage
143//------------------------------------------------------------------------
144
145TextPage::TextPage(GBool rawOrderA) {
146 rawOrder = rawOrderA;
147 curStr = NULL;
148 fontSize = 0;
149 xyStrings = NULL;
150 xyCur1 = xyCur2 = NULL;
151 lines = NULL;
152 nest = 0;
153 nTinyChars = 0;
154}
155
156TextPage::~TextPage() {
157 clear();
158}
159
160void TextPage::updateFont(GfxState *state) {
161 GfxFont *font;
162 double *fm;
163 char *name;
164 int code, mCode, letterCode, anyCode;
165 double w;
166
167 // adjust the font size
168 fontSize = state->getTransformedFontSize();
169 if ((font = state->getFont()) && font->getType() == fontType3) {
170 // This is a hack which makes it possible to deal with some Type 3
171 // fonts. The problem is that it's impossible to know what the
172 // base coordinate system used in the font is without actually
173 // rendering the font. This code tries to guess by looking at the
174 // width of the character 'm' (which breaks if the font is a
175 // subset that doesn't contain 'm').
176 mCode = letterCode = anyCode = -1;
177 for (code = 0; code < 256; ++code) {
178 name = ((Gfx8BitFont *)font)->getCharName(code);
179 if (name && name[0] == 'm' && name[1] == '\0') {
180 mCode = code;
181 }
182 if (letterCode < 0 && name && name[1] == '\0' &&
183 ((name[0] >= 'A' && name[0] <= 'Z') ||
184 (name[0] >= 'a' && name[0] <= 'z'))) {
185 letterCode = code;
186 }
187 if (anyCode < 0 && name && ((Gfx8BitFont *)font)->getWidth(code) > 0) {
188 anyCode = code;
189 }
190 }
191 if (mCode >= 0 &&
192 (w = ((Gfx8BitFont *)font)->getWidth(mCode)) > 0) {
193 // 0.6 is a generic average 'm' width -- yes, this is a hack
194 fontSize *= w / 0.6;
195 } else if (letterCode >= 0 &&
196 (w = ((Gfx8BitFont *)font)->getWidth(letterCode)) > 0) {
197 // even more of a hack: 0.5 is a generic letter width
198 fontSize *= w / 0.5;
199 } else if (anyCode >= 0 &&
200 (w = ((Gfx8BitFont *)font)->getWidth(anyCode)) > 0) {
201 // better than nothing: 0.5 is a generic character width
202 fontSize *= w / 0.5;
203 }
204 fm = font->getFontMatrix();
205 if (fm[0] != 0) {
206 fontSize *= fabs(fm[3] / fm[0]);
207 }
208 }
209}
210
211void TextPage::beginString(GfxState *state, double x0, double y0) {
212 // This check is needed because Type 3 characters can contain
213 // text-drawing operations.
214 if (curStr) {
215 ++nest;
216 return;
217 }
218
219 curStr = new TextString(state, x0, y0, fontSize);
220}
221
222void TextPage::addChar(GfxState *state, double x, double y,
223 double dx, double dy, Unicode *u, int uLen) {
224 double x1, y1, w1, h1, dx2, dy2;
225 int n, i;
226
227 state->transform(x, y, &x1, &y1);
228 if (x1 < 0 || x1 > state->getPageWidth() ||
229 y1 < 0 || y1 > state->getPageHeight()) {
230 return;
231 }
232 state->textTransformDelta(state->getCharSpace() * state->getHorizScaling(),
233 0, &dx2, &dy2);
234 dx -= dx2;
235 dy -= dy2;
236 state->transformDelta(dx, dy, &w1, &h1);
237 if (!globalParams->getTextKeepTinyChars() &&
238 fabs(w1) < 3 && fabs(h1) < 3) {
239 if (++nTinyChars > 20000) {
240 return;
241 }
242 }
243 n = curStr->len;
244 if (n > 0 && x1 - curStr->xRight[n-1] >
245 0.1 * (curStr->yMax - curStr->yMin)) {
246 // large char spacing is sometimes used to move text around
247 endString();
248 beginString(state, x, y);
249 }
250 if (uLen == 1 && u[0] == (Unicode)0x20 &&
251 w1 > 0.5 * (curStr->yMax - curStr->yMin)) {
252 // large word spacing is sometimes used to move text around
253 return;
254 }
255 if (uLen != 0) {
256 w1 /= uLen;
257 h1 /= uLen;
258 }
259 for (i = 0; i < uLen; ++i) {
260 curStr->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, u[i]);
261 }
262}
263
264void TextPage::endString() {
265 // This check is needed because Type 3 characters can contain
266 // text-drawing operations.
267 if (nest > 0) {
268 --nest;
269 return;
270 }
271
272 addString(curStr);
273 curStr = NULL;
274}
275
276void TextPage::addString(TextString *str) {
277 TextString *p1, *p2;
278
279 // throw away zero-length strings -- they don't have valid xMin/xMax
280 // values, and they're useless anyway
281 if (str->len == 0) {
282 delete str;
283 return;
284 }
285
286 // insert string in xy list
287 if (rawOrder) {
288 p1 = xyCur1;
289 p2 = NULL;
290 } else if ((!xyCur1 || xyBefore(xyCur1, str)) &&
291 (!xyCur2 || xyBefore(str, xyCur2))) {
292 p1 = xyCur1;
293 p2 = xyCur2;
294 } else if (xyCur1 && xyBefore(xyCur1, str)) {
295 for (p1 = xyCur1, p2 = xyCur2; p2; p1 = p2, p2 = p2->next) {
296 if (xyBefore(str, p2)) {
297 break;
298 }
299 }
300 xyCur2 = p2;
301 } else {
302 for (p1 = NULL, p2 = xyStrings; p2; p1 = p2, p2 = p2->next) {
303 if (xyBefore(str, p2)) {
304 break;
305 }
306 }
307 xyCur2 = p2;
308 }
309 xyCur1 = str;
310 if (p1) {
311 p1->next = str;
312 } else {
313 xyStrings = str;
314 }
315 str->next = p2;
316}
317
318void TextPage::coalesce() {
319 TextLine *line, *line0;
320 TextBlock *yxBlocks, *xyBlocks, *blk, *blk0, *blk1, *blk2;
321 TextString *str0, *str1, *str2, *str3, *str4;
322 TextString *str1prev, *str2prev, *str3prev;
323 TextOutColumnEdge *edges;
324 UnicodeMap *uMap;
325 GBool isUnicode;
326 char buf[8];
327 int edgesLength, edgesSize;
328 double x, yMin, yMax;
329 double space, fit1, fit2, h;
330 int col1, col2, d;
331 int i, j;
332
333#if 0 //~ for debugging
334 for (str1 = xyStrings; str1; str1 = str1->next) {
335 printf("x=%.2f..%.2f y=%.2f..%.2f size=%.2f '",
336 str1->xMin, str1->xMax, str1->yMin, str1->yMax,
337 (str1->yMax - str1->yMin));
338 for (i = 0; i < str1->len; ++i) {
339 fputc(str1->text[i] & 0xff, stdout);
340 }
341 printf("'\n");
342 }
343 printf("\n------------------------------------------------------------\n\n");
344#endif
345
346 // build the list of column edges
347 edges = NULL;
348 edgesLength = edgesSize = 0;
349 if (!rawOrder) {
350 for (str1prev = NULL, str1 = xyStrings;
351 str1;
352 str1prev = str1, str1 = str1->next) {
353 if (str1->marked) {
354 continue;
355 }
356 h = str1->yMax - str1->yMin;
357 if (str1prev && (str1->xMin - str1prev->xMax) / h < textOutColSpace) {
358 continue;
359 }
360 x = str1->xMin;
361 yMin = str1->yMin;
362 yMax = str1->yMax;
363 for (str2prev = str1, str2 = str1->next;
364 str2;
365 str2prev = str2, str2 = str2->next) {
366 h = str2->yMax - str2->yMin;
367 if (!str2->marked &&
368 (str2->xMin - str2prev->xMax) / h > textOutColSpace &&
369 fabs(str2->xMin - x) < 0.5 &&
370 str2->yMin - yMax < 0.3 * h &&
371 yMin - str2->yMax < 0.3 * h) {
372 break;
373 }
374 }
375 if (str2) {
376 if (str2->yMin < yMin) {
377 yMin = str2->yMin;
378 }
379 if (str2->yMax > yMax) {
380 yMax = str2->yMax;
381 }
382 str2->marked = gTrue;
383 for (str3prev = str1, str3 = str1->next;
384 str3;
385 str3prev = str3, str3 = str3->next) {
386 h = str3->yMax - str3->yMin;
387 if (!str3->marked &&
388 (str3->xMin - str3prev->xMax) / h > textOutColSpace &&
389 fabs(str3->xMin - x) < 0.5 &&
390 str3->yMin - yMax < 0.3 * h &&
391 yMin - str3->yMax < 0.3 * h) {
392 break;
393 }
394 }
395 if (str3) {
396 if (str3->yMin < yMin) {
397 yMin = str3->yMin;
398 }
399 if (str3->yMax > yMax) {
400 yMax = str3->yMax;
401 }
402 str3->marked = gTrue;
403 do {
404 for (str2prev = str1, str2 = str1->next;
405 str2;
406 str2prev = str2, str2 = str2->next) {
407 h = str2->yMax - str2->yMin;
408 if (!str2->marked &&
409 (str2->xMin - str2prev->xMax) / h > textOutColSpace &&
410 fabs(str2->xMin - x) < 0.5 &&
411 str2->yMin - yMax < 0.3 * h &&
412 yMin - str2->yMax < 0.3 * h) {
413 if (str2->yMin < yMin) {
414 yMin = str2->yMin;
415 }
416 if (str2->yMax > yMax) {
417 yMax = str2->yMax;
418 }
419 str2->marked = gTrue;
420 break;
421 }
422 }
423 } while (str2);
424 if (edgesLength == edgesSize) {
425 edgesSize = edgesSize ? 2 * edgesSize : 16;
426 edges = (TextOutColumnEdge *)
427 grealloc(edges, edgesSize * sizeof(TextOutColumnEdge));
428 }
429 edges[edgesLength].x = x;
430 edges[edgesLength].y0 = yMin;
431 edges[edgesLength].y1 = yMax;
432 ++edgesLength;
433 } else {
434 str2->marked = gFalse;
435 }
436 }
437 str1->marked = gTrue;
438 }
439 }
440
441#if 0 //~ for debugging
442 printf("column edges:\n");
443 for (i = 0; i < edgesLength; ++i) {
444 printf("%d: x=%.2f y0=%.2f y1=%.2f\n",
445 i, edges[i].x, edges[i].y0, edges[i].y1);
446 }
447 printf("\n------------------------------------------------------------\n\n");
448#endif
449
450 // build the blocks
451 yxBlocks = NULL;
452 blk1 = blk2 = NULL;
453 while (xyStrings) {
454
455 // build the block
456 str0 = xyStrings;
457 xyStrings = xyStrings->next;
458 str0->next = NULL;
459 blk = new TextBlock();
460 blk->strings = str0;
461 blk->xMin = str0->xMin;
462 blk->xMax = str0->xMax;
463 blk->yMin = str0->yMin;
464 blk->yMax = str0->yMax;
465 while (xyStrings) {
466 str1 = NULL;
467 str2 = xyStrings;
468 fit1 = coalesceFit(str0, str2);
469 if (!rawOrder) {
470 // look for best-fitting string
471 space = str0->yMax - str0->yMin;
472 for (str3 = xyStrings, str4 = xyStrings->next;
473 str4 && str4->xMin - str0->xMax <= space;
474 str3 = str4, str4 = str4->next) {
475 fit2 = coalesceFit(str0, str4);
476 if (fit2 < fit1) {
477 str1 = str3;
478 str2 = str4;
479 fit1 = fit2;
480 }
481 }
482 }
483 if (fit1 > 1) {
484 // no fit - we're done with this block
485 break;
486 }
487
488 // if we've hit a column edge we're done with this block
489 if (fit1 > 0.2) {
490 for (i = 0; i < edgesLength; ++i) {
491 if (str0->xMax < edges[i].x + 0.5 && edges[i].x - 0.5 < str2->xMin &&
492 str0->yMin < edges[i].y1 && str0->yMax > edges[i].y0 &&
493 str2->yMin < edges[i].y1 && str2->yMax > edges[i].y0) {
494 break;
495 }
496 }
497 if (i < edgesLength) {
498 break;
499 }
500 }
501
502 if (str1) {
503 str1->next = str2->next;
504 } else {
505 xyStrings = str2->next;
506 }
507 str0->next = str2;
508 str2->next = NULL;
509 if (str2->xMax > blk->xMax) {
510 blk->xMax = str2->xMax;
511 }
512 if (str2->yMin < blk->yMin) {
513 blk->yMin = str2->yMin;
514 }
515 if (str2->yMax > blk->yMax) {
516 blk->yMax = str2->yMax;
517 }
518 str0 = str2;
519 }
520
521 // insert block on list
522 if (!rawOrder) {
523 // insert block on list in yx order
524 for (blk1 = NULL, blk2 = yxBlocks;
525 blk2 && !yxBefore(blk, blk2);
526 blk1 = blk2, blk2 = blk2->next) ;
527 }
528 blk->next = blk2;
529 if (blk1) {
530 blk1->next = blk;
531 } else {
532 yxBlocks = blk;
533 }
534 blk1 = blk;
535 }
536
537 gfree(edges);
538
539 // the strings are now owned by the lines/blocks tree
540 xyStrings = NULL;
541
542 // build the block text
543 uMap = globalParams->getTextEncoding();
544 isUnicode = uMap ? uMap->isUnicode() : gFalse;
545 for (blk = yxBlocks; blk; blk = blk->next) {
546 blk->len = 0;
547 for (str1 = blk->strings; str1; str1 = str1->next) {
548 blk->len += str1->len;
549 if (str1->next && str1->next->xMin - str1->xMax >
550 textOutSpace * (str1->yMax - str1->yMin)) {
551 str1->spaceAfter = gTrue;
552 ++blk->len;
553 } else {
554 str1->spaceAfter = gFalse;
555 }
556 }
557 blk->text = (Unicode *)gmalloc(blk->len * sizeof(Unicode));
558 blk->xRight = (double *)gmalloc(blk->len * sizeof(double));
559 blk->col = (int *)gmalloc(blk->len * sizeof(int));
560 i = 0;
561 for (str1 = blk->strings; str1; str1 = str1->next) {
562 for (j = 0; j < str1->len; ++j) {
563 blk->text[i] = str1->text[j];
564 blk->xRight[i] = str1->xRight[j];
565 ++i;
566 }
567 if (str1->spaceAfter) {
568 blk->text[i] = (Unicode)0x0020;
569 blk->xRight[i] = str1->next->xMin;
570 ++i;
571 }
572 }
573 blk->convertedLen = 0;
574 for (j = 0; j < blk->len; ++j) {
575 blk->col[j] = blk->convertedLen;
576 if (isUnicode) {
577 ++blk->convertedLen;
578 } else if (uMap) {
579 blk->convertedLen += uMap->mapUnicode(blk->text[j], buf, sizeof(buf));
580 }
581 }
582 }
583 if (uMap) {
584 uMap->decRefCnt();
585 }
586
587#if 0 //~ for debugging
588 for (blk = yxBlocks; blk; blk = blk->next) {
589 printf("[block: x=%.2f..%.2f y=%.2f..%.2f len=%d]\n",
590 blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->len);
591 TextString *str;
592 for (str = blk->strings; str; str = str->next) {
593 printf(" x=%.2f..%.2f y=%.2f..%.2f size=%.2f'",
594 str->xMin, str->xMax, str->yMin, str->yMax,
595 (str->yMax - str->yMin));
596 for (i = 0; i < str->len; ++i) {
597 fputc(str->text[i] & 0xff, stdout);
598 }
599 if (str->spaceAfter) {
600 fputc(' ', stdout);
601 }
602 printf("'\n");
603 }
604 }
605 printf("\n------------------------------------------------------------\n\n");
606#endif
607
608 // build the lines
609 lines = NULL;
610 line0 = NULL;
611 while (yxBlocks) {
612 blk0 = yxBlocks;
613 yxBlocks = yxBlocks->next;
614 blk0->next = NULL;
615 line = new TextLine();
616 line->blocks = blk0;
617 line->yMin = blk0->yMin;
618 line->yMax = blk0->yMax;
619 while (yxBlocks) {
620
621 // remove duplicated text (fake boldface, shadowed text)
622 h = blk0->yMax - blk0->yMin;
623 if (yxBlocks->len == blk0->len &&
624 !memcmp(yxBlocks->text, blk0->text,
625 yxBlocks->len * sizeof(Unicode)) &&
626 fabs(yxBlocks->yMin - blk0->yMin) / h < 0.2 &&
627 fabs(yxBlocks->yMax - blk0->yMax) / h < 0.2 &&
628 fabs(yxBlocks->xMin - blk0->xMin) / h < 0.2 &&
629 fabs(yxBlocks->xMax - blk0->xMax) / h < 0.2) {
630 blk1 = yxBlocks;
631 yxBlocks = yxBlocks->next;
632 delete blk1;
633 continue;
634 }
635
636 if (rawOrder && yxBlocks->yMax < blk0->yMin) {
637 break;
638 }
639 if (yxBlocks->yMin > 0.2*blk0->yMin + 0.8*blk0->yMax ||
640 yxBlocks->xMin < blk0->xMax) {
641 break;
642 }
643 blk1 = yxBlocks;
644 yxBlocks = yxBlocks->next;
645 blk0->next = blk1;
646 blk1->next = NULL;
647 if (blk1->yMin < line->yMin) {
648 line->yMin = blk1->yMin;
649 }
650 if (blk1->yMax > line->yMax) {
651 line->yMax = blk1->yMax;
652 }
653 blk0 = blk1;
654 }
655 if (line0) {
656 line0->next = line;
657 } else {
658 lines = line;
659 }
660 line->next = NULL;
661 line0 = line;
662 }
663
664
665 // sort the blocks into xy order
666 xyBlocks = NULL;
667 for (line = lines; line; line = line->next) {
668 for (blk = line->blocks; blk; blk = blk->next) {
669 for (blk1 = NULL, blk2 = xyBlocks;
670 blk2 && !xyBefore(blk, blk2);
671 blk1 = blk2, blk2 = blk2->xyNext) ;
672 blk->xyNext = blk2;
673 if (blk1) {
674 blk1->xyNext = blk;
675 } else {
676 xyBlocks = blk;
677 }
678 }
679 }
680
681#if 0 //~ for debugging
682 for (blk = xyBlocks; blk; blk = blk->xyNext) {
683 printf("[block: x=%.2f..%.2f y=%.2f..%.2f len=%d]\n",
684 blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->len);
685 TextString *str;
686 for (str = blk->strings; str; str = str->next) {
687 printf(" x=%.2f..%.2f y=%.2f..%.2f size=%.2f '",
688 str->xMin, str->xMax, str->yMin, str->yMax,
689 (str->yMax - str->yMin));
690 for (i = 0; i < str->len; ++i) {
691 fputc(str->text[i] & 0xff, stdout);
692 }
693 printf("'\n");
694 }
695 }
696 printf("\n------------------------------------------------------------\n\n");
697#endif
698
699 // do column assignment
700 for (blk1 = xyBlocks; blk1; blk1 = blk1->xyNext) {
701 col1 = 0;
702 for (blk2 = xyBlocks; blk2 != blk1; blk2 = blk2->xyNext) {
703 if (blk1->xMin >= blk2->xMax) {
704 d = (int)((blk1->xMin - blk2->xMax) /
705 (0.4 * (blk1->yMax - blk1->yMin)));
706 if (d > 4) {
707 d = 4;
708 }
709 col2 = blk2->col[0] + blk2->convertedLen + d;
710 if (col2 > col1) {
711 col1 = col2;
712 }
713 } else if (blk1->xMin > blk2->xMin) {
714 for (i = 0; i < blk2->len && blk1->xMin >= blk2->xRight[i]; ++i) ;
715 col2 = blk2->col[i];
716 if (col2 > col1) {
717 col1 = col2;
718 }
719 }
720 }
721 for (j = 0; j < blk1->len; ++j) {
722 blk1->col[j] += col1;
723 }
724 }
725
726#if 0 //~ for debugging
727 for (line = lines; line; line = line->next) {
728 printf("[line]\n");
729 for (blk = line->blocks; blk; blk = blk->next) {
730 printf("[block: col=%d, len=%d]\n", blk->col[0], blk->len);
731 TextString *str;
732 for (str = blk->strings; str; str = str->next) {
733 printf(" x=%.2f..%.2f y=%.2f..%.2f size=%.2f '",
734 str->xMin, str->xMax, str->yMin, str->yMax,
735 (str->yMax - str->yMin));
736 for (i = 0; i < str->len; ++i) {
737 fputc(str->text[i] & 0xff, stdout);
738 }
739 if (str->spaceAfter) {
740 printf(" [space]\n");
741 }
742 printf("'\n");
743 }
744 }
745 }
746 printf("\n------------------------------------------------------------\n\n");
747#endif
748}
749
750
751GBool TextPage::findText(Unicode *s, int len,
752 GBool top, GBool bottom,
753 double *xMin, double *yMin,
754 double *xMax, double *yMax) {
755 TextLine *line;
756 TextBlock *blk;
757 Unicode *p;
758 Unicode u1, u2;
759 int m, i, j;
760 double x0, x1, x;
761
762 // scan all blocks on page
763 for (line = lines; line; line = line->next) {
764 for (blk = line->blocks; blk; blk = blk->next) {
765
766 // check: above top limit?
767 if (!top && (blk->yMax < *yMin ||
768 (blk->yMin < *yMin && blk->xMax <= *xMin))) {
769 continue;
770 }
771
772 // check: below bottom limit?
773 if (!bottom && (blk->yMin > *yMax ||
774 (blk->yMax > *yMax && blk->xMin >= *xMax))) {
775 return gFalse;
776 }
777
778 // search each position in this block
779 m = blk->len;
780 for (i = 0, p = blk->text; i <= m - len; ++i, ++p) {
781
782 x0 = (i == 0) ? blk->xMin : blk->xRight[i-1];
783 x1 = blk->xRight[i];
784 x = 0.5 * (x0 + x1);
785
786 // check: above top limit?
787 if (!top && blk->yMin < *yMin) {
788 if (x < *xMin) {
789 continue;
790 }
791 }
792
793 // check: below bottom limit?
794 if (!bottom && blk->yMax > *yMax) {
795 if (x > *xMax) {
796 return gFalse;
797 }
798 }
799
800 // compare the strings
801 for (j = 0; j < len; ++j) {
802#if 1 //~ this lowercases Latin A-Z only -- this will eventually be
803 //~ extended to handle other character sets
804 if (p[j] >= 0x41 && p[j] <= 0x5a) {
805 u1 = p[j] + 0x20;
806 } else {
807 u1 = p[j];
808 }
809 if (s[j] >= 0x41 && s[j] <= 0x5a) {
810 u2 = s[j] + 0x20;
811 } else {
812 u2 = s[j];
813 }
814#endif
815 if (u1 != u2) {
816 break;
817 }
818 }
819
820 // found it
821 if (j == len) {
822 *xMin = x0;
823 *xMax = blk->xRight[i + len - 1];
824 *yMin = blk->yMin;
825 *yMax = blk->yMax;
826 return gTrue;
827 }
828 }
829 }
830 }
831
832 return gFalse;
833}
834
835GString *TextPage::getText(double xMin, double yMin,
836 double xMax, double yMax) {
837 GString *s;
838 UnicodeMap *uMap;
839 GBool isUnicode;
840 char space[8], eol[16], buf[8];
841 int spaceLen, eolLen, len;
842 TextLine *line;
843 TextBlock *blk;
844 double x0, x1, y;
845 int firstCol, col, i;
846 GBool multiLine;
847
848 s = new GString();
849
850 // get the output encoding
851 if (!(uMap = globalParams->getTextEncoding())) {
852 return s;
853 }
854 isUnicode = uMap->isUnicode();
855 spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
856 eolLen = 0; // make gcc happy
857 switch (globalParams->getTextEOL()) {
858 case eolUnix:
859 eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
860 break;
861 case eolDOS:
862 eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
863 eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
864 break;
865 case eolMac:
866 eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
867 break;
868 }
869
870 // find the leftmost column
871 multiLine = gFalse;
872 firstCol = -1;
873 for (line = lines; line; line = line->next) {
874 if (line->yMin > yMax) {
875 break;
876 }
877 if (line->yMax < yMin) {
878 continue;
879 }
880
881 for (blk = line->blocks; blk && blk->xMax < xMin; blk = blk->next) ;
882 if (!blk || blk->xMin > xMax) {
883 continue;
884 }
885
886 y = 0.5 * (blk->yMin + blk->yMax);
887 if (y < yMin || y > yMax) {
888 continue;
889 }
890
891 if (firstCol >= 0) {
892 multiLine = gTrue;
893 }
894
895 i = 0;
896 while (1) {
897 x0 = (i==0) ? blk->xMin : blk->xRight[i-1];
898 x1 = blk->xRight[i];
899 if (0.5 * (x0 + x1) > xMin) {
900 break;
901 }
902 ++i;
903 }
904 col = blk->col[i];
905
906 if (firstCol < 0 || col < firstCol) {
907 firstCol = col;
908 }
909 }
910
911 // extract the text
912 for (line = lines; line; line = line->next) {
913 if (line->yMin > yMax) {
914 break;
915 }
916 if (line->yMax < yMin) {
917 continue;
918 }
919
920 for (blk = line->blocks; blk && blk->xMax < xMin; blk = blk->next) ;
921 if (!blk || blk->xMin > xMax) {
922 continue;
923 }
924
925 y = 0.5 * (blk->yMin + blk->yMax);
926 if (y < yMin || y > yMax) {
927 continue;
928 }
929
930 i = 0;
931 while (1) {
932 x0 = (i==0) ? blk->xMin : blk->xRight[i-1];
933 x1 = blk->xRight[i];
934 if (0.5 * (x0 + x1) > xMin) {
935 break;
936 }
937 ++i;
938 }
939
940 col = firstCol;
941
942 do {
943
944 // line this block up with the correct column
945 for (; col < blk->col[i]; ++col) {
946 s->append(space, spaceLen);
947 }
948
949 // print the block
950 for (; i < blk->len; ++i) {
951
952 x0 = (i==0) ? blk->xMin : blk->xRight[i-1];
953 x1 = blk->xRight[i];
954 if (0.5 * (x0 + x1) > xMax) {
955 break;
956 }
957
958 len = uMap->mapUnicode(blk->text[i], buf, sizeof(buf));
959 s->append(buf, len);
960 col += isUnicode ? 1 : len;
961 }
962 if (i < blk->len) {
963 break;
964 }
965
966 // next block
967 blk = blk->next;
968 i = 0;
969
970 } while (blk && blk->xMin < xMax);
971
972 if (multiLine) {
973 s->append(eol, eolLen);
974 }
975 }
976
977 uMap->decRefCnt();
978
979 return s;
980}
981
982void TextPage::dump(void *outputStream, TextOutputFunc outputFunc) {
983 UnicodeMap *uMap;
984 char space[8], eol[16], eop[8], buf[8];
985 int spaceLen, eolLen, eopLen, len;
986 TextLine *line;
987 TextBlock *blk;
988 int col, d, i;
989
990 // get the output encoding
991 if (!(uMap = globalParams->getTextEncoding())) {
992 return;
993 }
994 spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
995 eolLen = 0; // make gcc happy
996 switch (globalParams->getTextEOL()) {
997 case eolUnix:
998 eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
999 break;
1000 case eolDOS:
1001 eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
1002 eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
1003 break;
1004 case eolMac:
1005 eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
1006 break;
1007 }
1008 eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop));
1009
1010 // output
1011 for (line = lines; line; line = line->next) {
1012 col = 0;
1013 for (blk = line->blocks; blk; blk = blk->next) {
1014
1015 // line this block up with the correct column
1016 if (rawOrder && col == 0) {
1017 col = blk->col[0];
1018 } else {
1019 for (; col < blk->col[0]; ++col) {
1020 (*outputFunc)(outputStream, space, spaceLen);
1021 }
1022 }
1023
1024 // print the block
1025 for (i = 0; i < blk->len; ++i) {
1026 len = uMap->mapUnicode(blk->text[i], buf, sizeof(buf));
1027 (*outputFunc)(outputStream, buf, len);
1028 }
1029 col += blk->convertedLen;
1030 }
1031
1032 // print a return
1033 (*outputFunc)(outputStream, eol, eolLen);
1034
1035 // print extra vertical space if necessary
1036 if (line->next) {
1037 d = (int)((line->next->yMin - line->yMax) /
1038 (line->blocks->strings->yMax - lines->blocks->strings->yMin)
1039 + 0.5);
1040 // various things (weird font matrices) can result in bogus
1041 // values here, so do a sanity check
1042 if (rawOrder && d > 2) {
1043 d = 2;
1044 } else if (!rawOrder && d > 5) {
1045 d = 5;
1046 }
1047 for (; d > 0; --d) {
1048 (*outputFunc)(outputStream, eol, eolLen);
1049 }
1050 }
1051 }
1052
1053 // end of page
1054 (*outputFunc)(outputStream, eol, eolLen);
1055 (*outputFunc)(outputStream, eop, eopLen);
1056 (*outputFunc)(outputStream, eol, eolLen);
1057
1058 uMap->decRefCnt();
1059}
1060
1061// Returns true if <str1> should be inserted before <str2> in xy
1062// order.
1063GBool TextPage::xyBefore(TextString *str1, TextString *str2) {
1064 return str1->xMin < str2->xMin ||
1065 (str1->xMin == str2->xMin && str1->yMin < str2->yMin);
1066}
1067
1068// Returns true if <blk1> should be inserted before <blk2> in xy
1069// order.
1070GBool TextPage::xyBefore(TextBlock *blk1, TextBlock *blk2) {
1071 return blk1->xMin < blk2->xMin ||
1072 (blk1->xMin == blk2->xMin && blk1->yMin < blk2->yMin);
1073}
1074
1075// Returns true if <blk1> should be inserted before <blk2> in yx
1076// order, allowing a little slack for vertically overlapping text.
1077GBool TextPage::yxBefore(TextBlock *blk1, TextBlock *blk2) {
1078 double h1, h2, overlap;
1079
1080 h1 = blk1->yMax - blk1->yMin;
1081 h2 = blk2->yMax - blk2->yMin;
1082 overlap = ((blk1->yMax < blk2->yMax ? blk1->yMax : blk2->yMax) -
1083 (blk1->yMin > blk2->yMin ? blk1->yMin : blk2->yMin)) /
1084 (h1 < h2 ? h1 : h2);
1085 if (overlap > 0.6) {
1086 return blk1->xMin < blk2->xMin;
1087 }
1088 return blk1->yMin < blk2->yMin;
1089}
1090
1091double TextPage::coalesceFit(TextString *str1, TextString *str2) {
1092 double h1, h2, w1, w2, r, overlap, spacing;
1093
1094 h1 = str1->yMax - str1->yMin;
1095 h2 = str2->yMax - str2->yMin;
1096 w1 = str1->xMax - str1->xMin;
1097 w2 = str2->xMax - str2->xMin;
1098 r = h1 / h2;
1099 if (r < (1.0 / 3.0) || r > 3) {
1100 return 10;
1101 }
1102 overlap = ((str1->yMax < str2->yMax ? str1->yMax : str2->yMax) -
1103 (str1->yMin > str2->yMin ? str1->yMin : str2->yMin)) /
1104 (h1 < h2 ? h1 : h2);
1105 if (overlap < 0.5) {
1106 return 10;
1107 }
1108 spacing = (str2->xMin - str1->xMax) / (h1 > h2 ? h1 : h2);
1109 if (spacing < -0.5) {
1110 return 10;
1111 }
1112 // separate text that overlaps - duplicated text (so that fake
1113 // boldface and shadowed text can be cleanly removed)
1114 if ((str2->xMin - str1->xMax) / (w1 < w2 ? w1 : w2) < -0.7) {
1115 return 10;
1116 }
1117 return spacing;
1118}
1119
1120void TextPage::clear() {
1121 TextLine *p1, *p2;
1122 TextString *s1, *s2;
1123
1124 if (curStr) {
1125 delete curStr;
1126 curStr = NULL;
1127 }
1128 if (lines) {
1129 for (p1 = lines; p1; p1 = p2) {
1130 p2 = p1->next;
1131 delete p1;
1132 }
1133 } else if (xyStrings) {
1134 for (s1 = xyStrings; s1; s1 = s2) {
1135 s2 = s1->next;
1136 delete s1;
1137 }
1138 }
1139 xyStrings = NULL;
1140 xyCur1 = xyCur2 = NULL;
1141 lines = NULL;
1142 nest = 0;
1143 nTinyChars = 0;
1144}
1145
1146//------------------------------------------------------------------------
1147// TextOutputDev
1148//------------------------------------------------------------------------
1149
1150static void outputToFile(void *stream, char *text, int len) {
1151 fwrite(text, 1, len, (FILE *)stream);
1152}
1153
1154TextOutputDev::TextOutputDev(char *fileName, GBool rawOrderA, GBool append) {
1155 text = NULL;
1156 rawOrder = rawOrderA;
1157 ok = gTrue;
1158
1159 // open file
1160 needClose = gFalse;
1161 if (fileName) {
1162 if (!strcmp(fileName, "-")) {
1163 outputStream = stdout;
1164 } else if ((outputStream = fopen(fileName, append ? "ab" : "wb"))) {
1165 needClose = gTrue;
1166 } else {
1167 error(-1, "Couldn't open text file '%s'", fileName);
1168 ok = gFalse;
1169 return;
1170 }
1171 outputFunc = &outputToFile;
1172 } else {
1173 outputStream = NULL;
1174 }
1175
1176 // set up text object
1177 text = new TextPage(rawOrder);
1178}
1179
1180TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream,
1181 GBool rawOrderA) {
1182 outputFunc = func;
1183 outputStream = stream;
1184 needClose = gFalse;
1185 rawOrder = rawOrderA;
1186 text = new TextPage(rawOrder);
1187 ok = gTrue;
1188}
1189
1190TextOutputDev::~TextOutputDev() {
1191 if (needClose) {
1192#ifdef MACOS
1193 ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle);
1194#endif
1195 fclose((FILE *)outputStream);
1196 }
1197 if (text) {
1198 delete text;
1199 }
1200}
1201
1202void TextOutputDev::startPage(int /*pageNum*/, GfxState */*state*/) {
1203 text->clear();
1204}
1205
1206void TextOutputDev::endPage() {
1207 text->coalesce();
1208 if (outputStream) {
1209 text->dump(outputStream, outputFunc);
1210 }
1211}
1212
1213void TextOutputDev::updateFont(GfxState *state) {
1214 text->updateFont(state);
1215}
1216
1217void TextOutputDev::beginString(GfxState *state, GString */*s*/) {
1218 text->beginString(state, state->getCurX(), state->getCurY());
1219}
1220
1221void TextOutputDev::endString(GfxState */*state*/) {
1222 text->endString();
1223}
1224
1225void TextOutputDev::drawChar(GfxState *state, double x, double y,
1226 double dx, double dy,
1227 double /*originX*/, double /*originY*/,
1228 CharCode /*c*/, Unicode *u, int uLen) {
1229 text->addChar(state, x, y, dx, dy, u, uLen);
1230}
1231
1232GBool TextOutputDev::findText(Unicode *s, int len,
1233 GBool top, GBool bottom,
1234 double *xMin, double *yMin,
1235 double *xMax, double *yMax) {
1236 return text->findText(s, len, top, bottom, xMin, yMin, xMax, yMax);
1237}
1238
1239GString *TextOutputDev::getText(double xMin, double yMin,
1240 double xMax, double yMax) {
1241 return text->getText(xMin, yMin, xMax, yMax);
1242}
1243