]> www.fi.muni.cz Git - evince.git/blob - pdf/xpdf/TextOutputDev.cc
5e5761fe04c4076e571ae2e2e32cbeb1662603f4
[evince.git] / pdf / xpdf / TextOutputDev.cc
1 //========================================================================
2 //
3 // TextOutputDev.cc
4 //
5 // Copyright 1997-2002 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #ifdef __GNUC__
10 #pragma implementation
11 #endif
12
13 #include <aconf.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <stddef.h>
17 #include <math.h>
18 #include <ctype.h>
19 #include "GString.h"
20 #include "gmem.h"
21 #include "config.h"
22 #include "Error.h"
23 #include "GlobalParams.h"
24 #include "UnicodeMap.h"
25 #include "GfxState.h"
26 #include "TextOutputDev.h"
27
28 #ifdef MACOS
29 // needed for setting type/creator of MacOS files
30 #include "ICSupport.h"
31 #endif
32
33 //------------------------------------------------------------------------
34 // TextString
35 //------------------------------------------------------------------------
36
37 TextString::TextString(GfxState *state, double fontSize) {
38   GfxFont *font;
39   double x, y;
40
41   state->transform(state->getCurX(), state->getCurY(), &x, &y);
42   if ((font = state->getFont())) {
43     yMin = y - font->getAscent() * fontSize;
44     yMax = y - font->getDescent() * fontSize;
45   } else {
46     // this means that the PDF file draws text without a current font,
47     // which should never happen
48     yMin = y - 0.95 * fontSize;
49     yMax = y + 0.35 * fontSize;
50   }
51   if (yMin == yMax) {
52     // this is a sanity check for a case that shouldn't happen -- but
53     // if it does happen, we want to avoid dividing by zero later
54     yMin = y;
55     yMax = y + 1;
56   }
57   col = 0;
58   text = NULL;
59   xRight = NULL;
60   len = size = 0;
61   yxNext = NULL;
62   xyNext = NULL;
63 }
64
65 TextString::~TextString() {
66   gfree(text);
67   gfree(xRight);
68 }
69
70 void TextString::addChar(GfxState *state, double x, double y,
71                          double dx, double dy, Unicode u) {
72   if (len == size) {
73     size += 16;
74     text = (Unicode *)grealloc(text, size * sizeof(Unicode));
75     xRight = (double *)grealloc(xRight, size * sizeof(double));
76   }
77   text[len] = u;
78   if (len == 0) {
79     xMin = x;
80   }
81   xMax = xRight[len] = x + dx;
82   ++len;
83 }
84
85 //------------------------------------------------------------------------
86 // TextPage
87 //------------------------------------------------------------------------
88
89 TextPage::TextPage(GBool rawOrderA) {
90   rawOrder = rawOrderA;
91   curStr = NULL;
92   fontSize = 0;
93   yxStrings = NULL;
94   xyStrings = NULL;
95   yxCur1 = yxCur2 = NULL;
96   nest = 0;
97 }
98
99 TextPage::~TextPage() {
100   clear();
101 }
102
103 void TextPage::updateFont(GfxState *state) {
104   GfxFont *font;
105   double *fm;
106   char *name;
107   int code;
108   double w;
109
110   // adjust the font size
111   fontSize = state->getTransformedFontSize();
112   if ((font = state->getFont()) && font->getType() == fontType3) {
113     // This is a hack which makes it possible to deal with some Type 3
114     // fonts.  The problem is that it's impossible to know what the
115     // base coordinate system used in the font is without actually
116     // rendering the font.  This code tries to guess by looking at the
117     // width of the character 'm' (which breaks if the font is a
118     // subset that doesn't contain 'm').
119     for (code = 0; code < 256; ++code) {
120       if ((name = ((Gfx8BitFont *)font)->getCharName(code)) &&
121           name[0] == 'm' && name[1] == '\0') {
122         break;
123       }
124     }
125     if (code < 256) {
126       w = ((Gfx8BitFont *)font)->getWidth(code);
127       if (w != 0) {
128         // 600 is a generic average 'm' width -- yes, this is a hack
129         fontSize *= w / 0.6;
130       }
131     }
132     fm = font->getFontMatrix();
133     if (fm[0] != 0) {
134       fontSize *= fabs(fm[3] / fm[0]);
135     }
136   }
137 }
138
139 void TextPage::beginString(GfxState *state) {
140   // This check is needed because Type 3 characters can contain
141   // text-drawing operations.
142   if (curStr) {
143     ++nest;
144     return;
145   }
146
147   curStr = new TextString(state, fontSize);
148 }
149
150 void TextPage::addChar(GfxState *state, double x, double y,
151                        double dx, double dy, Unicode *u, int uLen) {
152   double x1, y1, w1, h1, dx2, dy2;
153   int n, i;
154
155   state->transform(x, y, &x1, &y1);
156   n = curStr->len;
157   if (n > 0 &&
158       x1 - curStr->xRight[n-1] > 0.1 * (curStr->yMax - curStr->yMin)) {
159     endString();
160     beginString(state);
161   }
162   state->textTransformDelta(state->getCharSpace() * state->getHorizScaling(),
163                             0, &dx2, &dy2);
164   dx -= dx2;
165   dy -= dy2;
166   state->transformDelta(dx, dy, &w1, &h1);
167   if (uLen != 0) {
168     w1 /= uLen;
169     h1 /= uLen;
170   }
171   for (i = 0; i < uLen; ++i) {
172     curStr->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, u[i]);
173   }
174 }
175
176 void TextPage::endString() {
177   TextString *p1, *p2;
178   double h, y1, y2;
179
180   // This check is needed because Type 3 characters can contain
181   // text-drawing operations.
182   if (nest > 0) {
183     --nest;
184     return;
185   }
186
187   // throw away zero-length strings -- they don't have valid xMin/xMax
188   // values, and they're useless anyway
189   if (curStr->len == 0) {
190     delete curStr;
191     curStr = NULL;
192     return;
193   }
194
195   // insert string in y-major list
196   h = curStr->yMax - curStr->yMin;
197   y1 = curStr->yMin + 0.5 * h;
198   y2 = curStr->yMin + 0.8 * h;
199   if (rawOrder) {
200     p1 = yxCur1;
201     p2 = NULL;
202   } else if ((!yxCur1 ||
203               (y1 >= yxCur1->yMin &&
204                (y2 >= yxCur1->yMax || curStr->xMax >= yxCur1->xMin))) &&
205              (!yxCur2 ||
206               (y1 < yxCur2->yMin ||
207                (y2 < yxCur2->yMax && curStr->xMax < yxCur2->xMin)))) {
208     p1 = yxCur1;
209     p2 = yxCur2;
210   } else {
211     for (p1 = NULL, p2 = yxStrings; p2; p1 = p2, p2 = p2->yxNext) {
212       if (y1 < p2->yMin || (y2 < p2->yMax && curStr->xMax < p2->xMin)) {
213         break;
214       }
215     }
216     yxCur2 = p2;
217   }
218   yxCur1 = curStr;
219   if (p1) {
220     p1->yxNext = curStr;
221   } else {
222     yxStrings = curStr;
223   }
224   curStr->yxNext = p2;
225   curStr = NULL;
226 }
227
228 void TextPage::coalesce() {
229   TextString *str1, *str2;
230   double space, d;
231   GBool addSpace;
232   int n, i;
233
234 #if 0 //~ for debugging
235   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
236     printf("x=%3d..%3d  y=%3d..%3d  size=%2d '",
237            (int)str1->xMin, (int)str1->xMax, (int)str1->yMin, (int)str1->yMax,
238            (int)(str1->yMax - str1->yMin));
239     for (i = 0; i < str1->len; ++i) {
240       fputc(str1->text[i] & 0xff, stdout);
241     }
242     printf("'\n");
243   }
244   printf("\n------------------------------------------------------------\n\n");
245 #endif
246   str1 = yxStrings;
247   while (str1 && (str2 = str1->yxNext)) {
248     space = str1->yMax - str1->yMin;
249     d = str2->xMin - str1->xMax;
250     if (((rawOrder &&
251           ((str2->yMin >= str1->yMin && str2->yMin <= str1->yMax) ||
252            (str2->yMax >= str1->yMin && str2->yMax <= str1->yMax))) ||
253          (!rawOrder && str2->yMin < str1->yMax)) &&
254         d > -0.5 * space && d < space) {
255       n = str1->len + str2->len;
256       if ((addSpace = d > 0.1 * space)) {
257         ++n;
258       }
259       str1->size = (n + 15) & ~15;
260       str1->text = (Unicode *)grealloc(str1->text,
261                                        str1->size * sizeof(Unicode));
262       str1->xRight = (double *)grealloc(str1->xRight,
263                                         str1->size * sizeof(double));
264       if (addSpace) {
265         str1->text[str1->len] = 0x20;
266         str1->xRight[str1->len] = str2->xMin;
267         ++str1->len;
268       }
269       for (i = 0; i < str2->len; ++i) {
270         str1->text[str1->len] = str2->text[i];
271         str1->xRight[str1->len] = str2->xRight[i];
272         ++str1->len;
273       }
274       if (str2->xMax > str1->xMax) {
275         str1->xMax = str2->xMax;
276       }
277       if (str2->yMax > str1->yMax) {
278         str1->yMax = str2->yMax;
279       }
280       str1->yxNext = str2->yxNext;
281       delete str2;
282     } else {
283       str1 = str2;
284     }
285   }
286 }
287
288 GBool TextPage::findText(Unicode *s, int len,
289                          GBool top, GBool bottom,
290                          double *xMin, double *yMin,
291                          double *xMax, double *yMax) {
292   TextString *str;
293   Unicode *p;
294   Unicode u1, u2;
295   int m, i, j;
296   double x;
297
298   // scan all strings on page
299   for (str = yxStrings; str; str = str->yxNext) {
300
301     // check: above top limit?
302     if (!top && (str->yMax < *yMin ||
303                  (str->yMin < *yMin && str->xMax <= *xMin))) {
304       continue;
305     }
306
307     // check: below bottom limit?
308     if (!bottom && (str->yMin > *yMax ||
309                     (str->yMax > *yMax && str->xMin >= *xMax))) {
310       return gFalse;
311     }
312
313     // search each position in this string
314     m = str->len;
315     for (i = 0, p = str->text; i <= m - len; ++i, ++p) {
316
317       // check: above top limit?
318       if (!top && str->yMin < *yMin) {
319         x = (((i == 0) ? str->xMin : str->xRight[i-1]) + str->xRight[i]) / 2;
320         if (x < *xMin) {
321           continue;
322         }
323       }
324
325       // check: below bottom limit?
326       if (!bottom && str->yMax > *yMax) {
327         x = (((i == 0) ? str->xMin : str->xRight[i-1]) + str->xRight[i]) / 2;
328         if (x > *xMax) {
329           return gFalse;
330         }
331       }
332
333       // compare the strings
334       for (j = 0; j < len; ++j) {
335 #if 1 //~ this lowercases Latin A-Z only -- this will eventually be
336       //~ extended to handle other character sets
337         if (p[j] >= 0x41 && p[j] <= 0x5a) {
338           u1 = p[j] + 0x20;
339         } else {
340           u1 = p[j];
341         }
342         if (s[j] >= 0x41 && s[j] <= 0x5a) {
343           u2 = s[j] + 0x20;
344         } else {
345           u2 = s[j];
346         }
347 #endif
348         if (u1 != u2) {
349           break;
350         }
351       }
352
353       // found it
354       if (j == len) {
355         *xMin = (i == 0) ? str->xMin : str->xRight[i-1];
356         *xMax = str->xRight[i + len - 1];
357         *yMin = str->yMin;
358         *yMax = str->yMax;
359         return gTrue;
360       }
361     }
362   }
363   return gFalse;
364 }
365
366 GString *TextPage::getText(double xMin, double yMin,
367                            double xMax, double yMax) {
368   GString *s;
369   UnicodeMap *uMap;
370   char space[8], eol[16], buf[8];
371   int spaceLen, eolLen, n;
372   TextString *str1;
373   double x0, x1, x2, y;
374   double xPrev, yPrev;
375   int i1, i2, i;
376   GBool multiLine;
377
378   s = new GString();
379   if (!(uMap = globalParams->getTextEncoding())) {
380     return s;
381   }
382   spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
383   eolLen = 0; // make gcc happy
384   switch (globalParams->getTextEOL()) {
385   case eolUnix:
386     eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
387     break;
388   case eolDOS:
389     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
390     eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
391     break;
392   case eolMac:
393     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
394     break;
395   }
396   xPrev = yPrev = 0;
397   multiLine = gFalse;
398   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
399     y = 0.5 * (str1->yMin + str1->yMax);
400     if (y > yMax) {
401       break;
402     }
403     if (y > yMin && str1->xMin < xMax && str1->xMax > xMin) {
404       x0 = x1 = x2 = str1->xMin;
405       for (i1 = 0; i1 < str1->len; ++i1) {
406         x0 = (i1==0) ? str1->xMin : str1->xRight[i1-1];
407         x1 = str1->xRight[i1];
408         if (0.5 * (x0 + x1) >= xMin) {
409           break;
410         }
411       }
412       for (i2 = str1->len - 1; i2 > i1; --i2) {
413         x1 = (i2==0) ? str1->xMin : str1->xRight[i2-1];
414         x2 = str1->xRight[i2];
415         if (0.5 * (x1 + x2) <= xMax) {
416           break;
417         }
418       }
419       if (s->getLength() > 0) {
420         if (x0 < xPrev || str1->yMin > yPrev) {
421           s->append(eol, eolLen);
422           multiLine = gTrue;
423         } else {
424           for (i = 0; i < 4; ++i) {
425             s->append(space, spaceLen);
426           }
427         }
428       }
429       for (i = i1; i <= i2; ++i) {
430         n = uMap->mapUnicode(str1->text[i], buf, sizeof(buf));
431         s->append(buf, n);
432       }
433       xPrev = x2;
434       yPrev = str1->yMax;
435     }
436   }
437   if (multiLine) {
438     s->append(eol, eolLen);
439   }
440   uMap->decRefCnt();
441   return s;
442 }
443
444 void TextPage::dump(void *outputStream, TextOutputFunc outputFunc) {
445   UnicodeMap *uMap;
446   char space[8], eol[16], eop[8], buf[8];
447   int spaceLen, eolLen, eopLen, n;
448   TextString *str1, *str2, *str3;
449   double yMin, yMax;
450   int col1, col2, d, i;
451
452   // get the output encoding
453   if (!(uMap = globalParams->getTextEncoding())) {
454     return;
455   }
456   spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
457   eolLen = 0; // make gcc happy
458   switch (globalParams->getTextEOL()) {
459   case eolUnix:
460     eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
461     break;
462   case eolDOS:
463     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
464     eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
465     break;
466   case eolMac:
467     eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
468     break;
469   }
470   eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop));
471
472   // build x-major list
473   xyStrings = NULL;
474   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
475     for (str2 = NULL, str3 = xyStrings;
476          str3;
477          str2 = str3, str3 = str3->xyNext) {
478       if (str1->xMin < str3->xMin ||
479           (str1->xMin == str3->xMin && str1->yMin < str3->yMin)) {
480         break;
481       }
482     }
483     if (str2) {
484       str2->xyNext = str1;
485     } else {
486       xyStrings = str1;
487     }
488     str1->xyNext = str3;
489   }
490
491   // do column assignment
492   for (str1 = xyStrings; str1; str1 = str1->xyNext) {
493     col1 = 0;
494     for (str2 = xyStrings; str2 != str1; str2 = str2->xyNext) {
495       if (str1->xMin >= str2->xMax) {
496         col2 = str2->col + str2->len + 4;
497         if (col2 > col1) {
498           col1 = col2;
499         }
500       } else if (str1->xMin > str2->xMin) {
501         col2 = str2->col +
502                (int)(((str1->xMin - str2->xMin) / (str2->xMax - str2->xMin)) *
503                      str2->len);
504         if (col2 > col1) {
505           col1 = col2;
506         }
507       }
508     }
509     str1->col = col1;
510   }
511
512 #if 0 //~ for debugging
513   fprintf((FILE *)outputStream, "~~~~~~~~~~\n");
514   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
515     fprintf((FILE *)outputStream, "(%4d,%4d) - (%4d,%4d) [%3d] '",
516             (int)str1->xMin, (int)str1->yMin,
517             (int)str1->xMax, (int)str1->yMax, str1->col);
518     for (i = 0; i < str1->len; ++i) {
519       fputc(str1->text[i] & 0xff, stdout);
520     }
521     printf("'\n");
522   }
523   fprintf((FILE *)outputStream, "~~~~~~~~~~\n");
524 #endif
525
526   // output
527   col1 = 0;
528   yMax = yxStrings ? yxStrings->yMax : 0;
529   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
530
531     // line this string up with the correct column
532     if (rawOrder && col1 == 0) {
533       col1 = str1->col;
534     } else {
535       for (; col1 < str1->col; ++col1) {
536         (*outputFunc)(outputStream, space, spaceLen);
537       }
538     }
539
540     // print the string
541     for (i = 0; i < str1->len; ++i) {
542       if ((n = uMap->mapUnicode(str1->text[i], buf, sizeof(buf))) > 0) {
543         (*outputFunc)(outputStream, buf, n);
544       }
545     }
546
547     // increment column
548     col1 += str1->len;
549
550     // update yMax for this line
551     if (str1->yMax > yMax) {
552       yMax = str1->yMax;
553     }
554
555     // if we've hit the end of the line...
556     if (!(str1->yxNext &&
557           !(rawOrder && str1->yxNext->yMax < str1->yMin) &&
558           str1->yxNext->yMin < 0.2*str1->yMin + 0.8*str1->yMax &&
559           str1->yxNext->xMin >= str1->xMax)) {
560
561       // print a return
562       (*outputFunc)(outputStream, eol, eolLen);
563
564       // print extra vertical space if necessary
565       if (str1->yxNext) {
566
567         // find yMin for next line
568         yMin = str1->yxNext->yMin;
569         for (str2 = str1->yxNext; str2; str2 = str2->yxNext) {
570           if (str2->yMin < yMin) {
571             yMin = str2->yMin;
572           }
573           if (!(str2->yxNext && str2->yxNext->yMin < str2->yMax &&
574                 str2->yxNext->xMin >= str2->xMax))
575             break;
576         }
577           
578         // print the space
579         d = (int)((yMin - yMax) / (str1->yMax - str1->yMin) + 0.5);
580         // various things (weird font matrices) can result in bogus
581         // values here, so do a sanity check
582         if (rawOrder && d > 2) {
583           d = 2;
584         } else if (!rawOrder && d > 5) {
585           d = 5;
586         }
587         for (; d > 0; --d) {
588           (*outputFunc)(outputStream, eol, eolLen);
589         }
590       }
591
592       // set up for next line
593       col1 = 0;
594       yMax = str1->yxNext ? str1->yxNext->yMax : 0;
595     }
596   }
597
598   // end of page
599   (*outputFunc)(outputStream, eol, eolLen);
600   (*outputFunc)(outputStream, eop, eopLen);
601   (*outputFunc)(outputStream, eol, eolLen);
602
603   uMap->decRefCnt();
604 }
605
606 void TextPage::clear() {
607   TextString *p1, *p2;
608
609   if (curStr) {
610     delete curStr;
611     curStr = NULL;
612   }
613   for (p1 = yxStrings; p1; p1 = p2) {
614     p2 = p1->yxNext;
615     delete p1;
616   }
617   yxStrings = NULL;
618   xyStrings = NULL;
619   yxCur1 = yxCur2 = NULL;
620 }
621
622 //------------------------------------------------------------------------
623 // TextOutputDev
624 //------------------------------------------------------------------------
625
626 static void outputToFile(void *stream, char *text, int len) {
627   fwrite(text, 1, len, (FILE *)stream);
628 }
629
630 TextOutputDev::TextOutputDev(char *fileName, GBool rawOrderA, GBool append) {
631   text = NULL;
632   rawOrder = rawOrderA;
633   ok = gTrue;
634
635   // open file
636   needClose = gFalse;
637   if (fileName) {
638     if (!strcmp(fileName, "-")) {
639       outputStream = stdout;
640     } else if ((outputStream = fopen(fileName, append ? "ab" : "wb"))) {
641       needClose = gTrue;
642     } else {
643       error(-1, "Couldn't open text file '%s'", fileName);
644       ok = gFalse;
645       return;
646     }
647     outputFunc = &outputToFile;
648   } else {
649     outputStream = NULL;
650   }
651
652   // set up text object
653   text = new TextPage(rawOrder);
654 }
655
656 TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream,
657                              GBool rawOrderA) {
658   outputFunc = func;
659   outputStream = stream;
660   needClose = gFalse;
661   rawOrder = rawOrderA;
662   text = new TextPage(rawOrder);
663   ok = gTrue;
664 }
665
666 TextOutputDev::~TextOutputDev() {
667   if (needClose) {
668 #ifdef MACOS
669     ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle);
670 #endif
671     fclose((FILE *)outputStream);
672   }
673   if (text) {
674     delete text;
675   }
676 }
677
678 void TextOutputDev::startPage(int pageNum, GfxState *state) {
679   text->clear();
680 }
681
682 void TextOutputDev::endPage() {
683   text->coalesce();
684   if (outputStream) {
685     text->dump(outputStream, outputFunc);
686   }
687 }
688
689 void TextOutputDev::updateFont(GfxState *state) {
690   text->updateFont(state);
691 }
692
693 void TextOutputDev::beginString(GfxState *state, GString *s) {
694   text->beginString(state);
695 }
696
697 void TextOutputDev::endString(GfxState *state) {
698   text->endString();
699 }
700
701 void TextOutputDev::drawChar(GfxState *state, double x, double y,
702                              double dx, double dy,
703                              double originX, double originY,
704                              CharCode c, Unicode *u, int uLen) {
705   text->addChar(state, x, y, dx, dy, u, uLen);
706 }
707
708 GBool TextOutputDev::findText(Unicode *s, int len,
709                               GBool top, GBool bottom,
710                               double *xMin, double *yMin,
711                               double *xMax, double *yMax) {
712   return text->findText(s, len, top, bottom, xMin, yMin, xMax, yMax);
713 }