]> www.fi.muni.cz Git - evince.git/blob - pdf/xpdf/TextOutputDev.cc
2d9ddde1a1e1be09a5f49d4603d6284261b97e74
[evince.git] / pdf / xpdf / TextOutputDev.cc
1 //========================================================================
2 //
3 // TextOutputDev.cc
4 //
5 // Copyright 1997 Derek B. Noonburg
6 //
7 //========================================================================
8
9 #ifdef __GNUC__
10 #pragma implementation
11 #endif
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <stddef.h>
16 #include <ctype.h>
17 #include "GString.h"
18 #include "gmem.h"
19 #include "config.h"
20 #include "Error.h"
21 #include "GfxState.h"
22 #include "GfxFont.h"
23 #include "TextOutputDev.h"
24
25 #include "TextOutputFontInfo.h"
26
27 //------------------------------------------------------------------------
28 // Character substitutions
29 //------------------------------------------------------------------------
30
31 static char *isoLatin1Subst[] = {
32   "L",                          // Lslash
33   "OE",                         // OE
34   "S",                          // Scaron
35   "Y",                          // Ydieresis
36   "Z",                          // Zcaron
37   "fi",                         // fi
38   "fl",                         // fl
39   "i",                          // dotlessi
40   "l",                          // lslash
41   "oe",                         // oe
42   "s",                          // scaron
43   "z",                          // zcaron
44   "*",                          // bullet
45   "...",                        // ellipsis
46   "-", "-",                     // emdash, hyphen
47   "\"", "\"",                   // quotedblleft, quotedblright
48   "'",                          // quotesingle
49   "TM"                          // trademark
50 };
51
52 static char *ascii7Subst[] = {
53   "A", "A", "A", "A",           // A{acute,circumflex,dieresis,grave}
54   "A", "A",                     // A{ring,tilde}
55   "AE",                         // AE
56   "C",                          // Ccedilla
57   "E", "E", "E", "E",           // E{acute,circumflex,dieresis,grave}
58   "I", "I", "I", "I",           // I{acute,circumflex,dieresis,grave}
59   "L",                          // Lslash
60   "N",                          // Ntilde
61   "O", "O", "O", "O",           // O{acute,circumflex,dieresis,grave}
62   "O", "O",                     // O{slash,tilde}
63   "OE",                         // OE
64   "S",                          // Scaron
65   "U", "U", "U", "U",           // U{acute,circumflex,dieresis,grave}
66   "Y", "Y",                     // T{acute,dieresis}
67   "Z",                          // Zcaron
68   "a", "a", "a", "a",           // a{acute,circumflex,dieresis,grave}
69   "a", "a",                     // a{ring,tilde}
70   "ae",                         // ae
71   "c",                          // ccedilla
72   "e", "e", "e", "e",           // e{acute,circumflex,dieresis,grave}
73   "fi",                         // fi
74   "fl",                         // fl
75   "i",                          // dotlessi
76   "i", "i", "i", "i",           // i{acute,circumflex,dieresis,grave}
77   "l",                          // lslash
78   "n",                          // ntilde
79   "o", "o", "o", "o",           // o{acute,circumflex,dieresis,grave}
80   "o", "o",                     // o{slash,tilde}
81   "oe",                         // oe
82   "s",                          // scaron
83   "u", "u", "u", "u",           // u{acute,circumflex,dieresis,grave}
84   "y", "y",                     // t{acute,dieresis}
85   "z",                          // zcaron
86   "|",                          // brokenbar
87   "*",                          // bullet
88   "...",                        // ellipsis
89   "-", "-", "-",                // emdash, endash, hyphen
90   "\"", "\"",                   // quotedblleft, quotedblright
91   "'",                          // quotesingle
92   "(R)",                        // registered
93   "TM"                          // trademark
94 };
95
96 //------------------------------------------------------------------------
97 // TextString
98 //------------------------------------------------------------------------
99
100 TextString::TextString(GfxState *state, GBool hexCodes1) {
101   double x, y, h;
102
103   state->transform(state->getCurX(), state->getCurY(), &x, &y);
104   h = state->getTransformedFontSize();
105   //~ yMin/yMax computation should use font ascent/descent values
106   yMin = y - 0.95 * h;
107   yMax = yMin + 1.3 * h;
108   col = 0;
109   text = new GString();
110   xRight = NULL;
111   yxNext = NULL;
112   xyNext = NULL;
113   hexCodes = hexCodes1;
114 }
115
116 TextString::~TextString() {
117   delete text;
118   gfree(xRight);
119 }
120
121 void TextString::addChar(GfxState *state, double x, double y,
122                          double dx, double dy,
123                          Guchar c, GBool useASCII7) {
124   char *charName, *sub;
125   int c1;
126   int i, j, n, m;
127
128   // get current index
129   i = text->getLength();
130
131   // append translated character(s) to string
132   sub = NULL;
133   n = 1;
134   if ((charName = state->getFont()->getCharName(c))) {
135     if (useASCII7)
136       c1 = ascii7Encoding.getCharCode(charName);
137     else
138       c1 = isoLatin1Encoding.getCharCode(charName);
139     if (c1 < 0) {
140       m = strlen(charName);
141       if (hexCodes && m == 3 &&
142           (charName[0] == 'B' || charName[0] == 'C' ||
143            charName[0] == 'G') &&
144           isxdigit(charName[1]) && isxdigit(charName[2])) {
145         sscanf(charName+1, "%x", &c1);
146       } else if (!hexCodes && m >= 2 && m <= 3 &&
147                  isdigit(charName[0]) && isdigit(charName[1])) {
148         c1 = atoi(charName);
149         if (c1 >= 256)
150           c1 = -1;
151       } else if (!hexCodes && m >= 3 && m <= 5 && isdigit(charName[1])) {
152         c1 = atoi(charName+1);
153         if (c1 >= 256)
154           c1 = -1;
155       }
156       //~ this is a kludge -- is there a standard internal encoding
157       //~ used by all/most Type 1 fonts?
158       if (c1 == 262)            // hyphen
159         c1 = 45;
160       else if (c1 == 266)       // emdash
161         c1 = 208;
162       if (useASCII7)
163         c1 = ascii7Encoding.getCharCode(isoLatin1Encoding.getCharName(c1));
164     }
165     if (useASCII7) {
166       if (c1 >= 128) {
167         sub = ascii7Subst[c1 - 128];
168         n = strlen(sub);
169       }
170     } else {
171       if (c1 >= 256) {
172         sub = isoLatin1Subst[c1 - 256];
173         n = strlen(sub);
174       }
175     }
176   } else {
177     c1 = -1;
178   }
179   if (sub)
180     text->append(sub);
181   else if (c1 >= 0)
182     text->append((char)c1);
183   else
184     text->append(' ');
185
186   // update position information
187   if (i+n > ((i+15) & ~15))
188     xRight = (double *)grealloc(xRight, ((i+n+15) & ~15) * sizeof(double));
189   if (i == 0)
190     xMin = x;
191   for (j = 0; j < n; ++j)
192     xRight[i+j] = x + ((j+1) * dx) / n;
193   xMax = x + dx;
194 }
195
196 //------------------------------------------------------------------------
197 // TextPage
198 //------------------------------------------------------------------------
199
200 TextPage::TextPage(GBool useASCII71) {
201   useASCII7 = useASCII71;
202   curStr = NULL;
203   yxStrings = NULL;
204   xyStrings = NULL;
205 }
206
207 TextPage::~TextPage() {
208   clear();
209 }
210
211 void TextPage::beginString(GfxState *state, GString *s, GBool hexCodes) {
212   curStr = new TextString(state, hexCodes);
213 }
214
215 void TextPage::addChar(GfxState *state, double x, double y,
216                        double dx, double dy, Guchar c) {
217   double x1, y1, w1, h1;
218
219   state->transform(x, y, &x1, &y1);
220   state->transformDelta(dx, dy, &w1, &h1);
221   curStr->addChar(state, x1, y1, w1, h1, c, useASCII7);
222 }
223
224 void TextPage::endString() {
225   TextString *p1, *p2;
226   double h, y1, y2;
227
228   // throw away zero-length strings -- they don't have valid xMin/xMax
229   // values, and they're useless anyway
230   if (curStr->text->getLength() == 0) {
231     delete curStr;
232     curStr = NULL;
233     return;
234   }
235
236 #if 0 //~tmp
237   if (curStr->yMax - curStr->yMin > 20) {
238     delete curStr;
239     curStr = NULL;
240     return;
241   }
242 #endif
243
244   // insert string in y-major list
245   h = curStr->yMax - curStr->yMin;
246   y1 = curStr->yMin + 0.5 * h;
247   y2 = curStr->yMin + 0.8 * h;
248   for (p1 = NULL, p2 = yxStrings; p2; p1 = p2, p2 = p2->yxNext) {
249     if (y1 < p2->yMin || (y2 < p2->yMax && curStr->xMax < p2->xMin))
250       break;
251   }
252   if (p1)
253     p1->yxNext = curStr;
254   else
255     yxStrings = curStr;
256   curStr->yxNext = p2;
257   curStr = NULL;
258 }
259
260 void TextPage::coalesce() {
261   TextString *str1, *str2;
262   double space, d;
263   int n, i;
264
265 #if 0 //~ for debugging
266   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
267     printf("x=%3d..%3d  y=%3d..%3d  size=%2d '%s'\n",
268            (int)str1->xMin, (int)str1->xMax, (int)str1->yMin, (int)str1->yMax,
269            (int)(str1->yMax - str1->yMin), str1->text->getCString());
270   }
271   printf("\n------------------------------------------------------------\n\n");
272 #endif
273   str1 = yxStrings;
274   while (str1 && (str2 = str1->yxNext)) {
275     space = str1->yMax - str1->yMin;
276     d = str2->xMin - str1->xMax;
277 #if 0 //~tmp
278     if (str2->yMin < str1->yMax && d > -0.1 * space && d < 0.2 * space) {
279 #else
280     if (str2->yMin < str1->yMax && d > -0.5 * space && d < space) {
281 #endif
282       n = str1->text->getLength();
283       if (d > 0.1 * space)
284         str1->text->append(' ');
285       str1->text->append(str2->text);
286       str1->xRight = (double *)
287         grealloc(str1->xRight, str1->text->getLength() * sizeof(double));
288       if (d > 0.1 * space)
289         str1->xRight[n++] = str2->xMin;
290       for (i = 0; i < str2->text->getLength(); ++i)
291         str1->xRight[n++] = str2->xRight[i];
292       if (str2->xMax > str1->xMax)
293         str1->xMax = str2->xMax;
294       if (str2->yMax > str1->yMax)
295         str1->yMax = str2->yMax;
296       str1->yxNext = str2->yxNext;
297       delete str2;
298     } else {
299       str1 = str2;
300     }
301   }
302 }
303
304 GBool TextPage::findText(char *s, GBool top, GBool bottom,
305                          double *xMin, double *yMin,
306                          double *xMax, double *yMax) {
307   TextString *str;
308   char *p, *p1, *q;
309   int n, m, i;
310   double x;
311
312   // scan all strings on page
313   n = strlen(s);
314   for (str = yxStrings; str; str = str->yxNext) {
315
316     // check: above top limit?
317     if (!top && (str->yMax < *yMin ||
318                  (str->yMin < *yMin && str->xMax <= *xMin)))
319       continue;
320
321     // check: below bottom limit?
322     if (!bottom && (str->yMin > *yMax ||
323                     (str->yMax > *yMax && str->xMin >= *xMax)))
324       return gFalse;
325
326     // search each position in this string
327     m = str->text->getLength();
328     for (i = 0, p = str->text->getCString(); i <= m - n; ++i, ++p) {
329
330       // check: above top limit?
331       if (!top && str->yMin < *yMin) {
332         x = (((i == 0) ? str->xMin : str->xRight[i-1]) + str->xRight[i]) / 2;
333         if (x < *xMin)
334           continue;
335       }
336
337       // check: below bottom limit?
338       if (!bottom && str->yMax > *yMax) {
339         x = (((i == 0) ? str->xMin : str->xRight[i-1]) + str->xRight[i]) / 2;
340         if (x > *xMax)
341           return gFalse;
342       }
343
344       // compare the strings
345       for (p1 = p, q = s; *q; ++p1, ++q) {
346         if (tolower(*p1) != tolower(*q))
347           break;
348       }
349
350       // found it
351       if (!*q) {
352         *xMin = (i == 0) ? str->xMin : str->xRight[i-1];
353         *xMax = str->xRight[i+n-1];
354         *yMin = str->yMin;
355         *yMax = str->yMax;
356         return gTrue;
357       }
358     }
359   }
360   return gFalse;
361 }
362
363 GString *TextPage::getText(double xMin, double yMin,
364                            double xMax, double yMax) {
365   GString *s;
366   TextString *str1;
367   double x0, x1, x2, y;
368   double xPrev, yPrev;
369   int i1, i2;
370   GBool multiLine;
371
372   s = new GString();
373   xPrev = yPrev = 0;
374   multiLine = gFalse;
375   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
376     y = 0.5 * (str1->yMin + str1->yMax);
377     if (y > yMax)
378       break;
379     if (y > yMin && str1->xMin < xMax && str1->xMax > xMin) {
380       x0 = x1 = x2 = str1->xMin;
381       for (i1 = 0; i1 < str1->text->getLength(); ++i1) {
382         x0 = (i1==0) ? str1->xMin : str1->xRight[i1-1];
383         x1 = str1->xRight[i1];
384         if (0.5 * (x0 + x1) >= xMin)
385           break;
386       }
387       for (i2 = str1->text->getLength() - 1; i2 > i1; --i2) {
388         x1 = (i2==0) ? str1->xMin : str1->xRight[i2-1];
389         x2 = str1->xRight[i2];
390         if (0.5 * (x1 + x2) <= xMax)
391           break;
392       }
393       if (s->getLength() > 0) {
394         if (x0 < xPrev || str1->yMin > yPrev) {
395           s->append('\n');
396           multiLine = gTrue;
397         } else {
398           s->append("    ");
399         }
400       }
401       s->append(str1->text->getCString() + i1, i2 - i1 + 1);
402       xPrev = x2;
403       yPrev = str1->yMax;
404     }
405   }
406   if (multiLine)
407     s->append('\n');
408   return s;
409 }
410
411 void TextPage::dump(FILE *f) {
412   TextString *str1, *str2, *str3;
413   double yMin, yMax;
414   int col1, col2;
415   double d;
416
417   // build x-major list
418   xyStrings = NULL;
419   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
420     for (str2 = NULL, str3 = xyStrings;
421          str3;
422          str2 = str3, str3 = str3->xyNext) {
423       if (str1->xMin < str3->xMin ||
424           (str1->xMin == str3->xMin && str1->yMin < str3->yMin))
425         break;
426     }
427     if (str2)
428       str2->xyNext = str1;
429     else
430       xyStrings = str1;
431     str1->xyNext = str3;
432   }
433
434   // do column assignment
435   for (str1 = xyStrings; str1; str1 = str1->xyNext) {
436     col1 = 0;
437     for (str2 = xyStrings; str2 != str1; str2 = str2->xyNext) {
438       if (str1->xMin >= str2->xMax) {
439         col2 = str2->col + str2->text->getLength() + 4;
440         if (col2 > col1)
441           col1 = col2;
442       } else if (str1->xMin > str2->xMin) {
443         col2 = str2->col +
444                (int)(((str1->xMin - str2->xMin) / (str2->xMax - str2->xMin)) *
445                      str2->text->getLength());
446         if (col2 > col1) {
447           col1 = col2;
448         }
449       }
450     }
451     str1->col = col1;
452   }
453
454 #if 0 //~ for debugging
455   fprintf(f, "~~~~~~~~~~\n");
456   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
457     fprintf(f, "(%4d,%4d) - (%4d,%4d) [%3d] %s\n",
458             (int)str1->xMin, (int)str1->yMin, (int)str1->xMax, (int)str1->yMax,
459             str1->col, str1->text->getCString());
460   }
461   fprintf(f, "~~~~~~~~~~\n");
462 #endif
463
464   // output
465   col1 = 0;
466   yMax = yxStrings ? yxStrings->yMax : 0;
467   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
468
469     // line this string up with the correct column
470     for (; col1 < str1->col; ++col1)
471       fputc(' ', f);
472
473     // print the string
474     fputs(str1->text->getCString(), f);
475
476     // increment column
477     col1 += str1->text->getLength();
478
479     // update yMax for this line
480     if (str1->yMax > yMax)
481       yMax = str1->yMax;
482
483     // if we've hit the end of the line...
484 #if 0 //~
485     if (!(str1->yxNext && str1->yxNext->yMin < str1->yMax &&
486           str1->yxNext->xMin >= str1->xMax)) {
487 #else
488     if (!(str1->yxNext &&
489           str1->yxNext->yMin < 0.2*str1->yMin + 0.8*str1->yMax &&
490           str1->yxNext->xMin >= str1->xMax)) {
491 #endif
492
493       // print a return
494       fputc('\n', f);
495
496       // print extra vertical space if necessary
497       if (str1->yxNext) {
498
499         // find yMin for next line
500         yMin = str1->yxNext->yMin;
501         for (str2 = str1->yxNext; str2; str2 = str2->yxNext) {
502           if (str2->yMin < yMin)
503             yMin = str2->yMin;
504           if (!(str2->yxNext && str2->yxNext->yMin < str2->yMax &&
505                 str2->yxNext->xMin >= str2->xMax))
506             break;
507         }
508           
509         // print the space
510         d = (int)((yMin - yMax) / (str1->yMax - str1->yMin) + 0.5);
511         for (; d > 0; --d)
512           fputc('\n', f);
513       }
514
515       // set up for next line
516       col1 = 0;
517       yMax = str1->yxNext ? str1->yxNext->yMax : 0;
518     }
519   }
520 }
521
522 void TextPage::clear() {
523   TextString *p1, *p2;
524
525   if (curStr) {
526     delete curStr;
527     curStr = NULL;
528   }
529   for (p1 = yxStrings; p1; p1 = p2) {
530     p2 = p1->yxNext;
531     delete p1;
532   }
533   yxStrings = NULL;
534   xyStrings = NULL;
535 }
536
537 //------------------------------------------------------------------------
538 // TextOutputDev
539 //------------------------------------------------------------------------
540
541 TextOutputDev::TextOutputDev(char *fileName, GBool useASCII7) {
542   text = NULL;
543   ok = gTrue;
544
545   // open file
546   needClose = gFalse;
547   if (fileName) {
548     if (!strcmp(fileName, "-")) {
549       f = stdout;
550     } else if ((f = fopen(fileName, "w"))) {
551       needClose = gTrue;
552     } else {
553       error(-1, "Couldn't open text file '%s'", fileName);
554       ok = gFalse;
555       return;
556     }
557   } else {
558     f = NULL;
559   }
560
561   // set up text object
562   text = new TextPage(useASCII7);
563 }
564
565 TextOutputDev::~TextOutputDev() {
566   if (needClose)
567     fclose(f);
568   if (text)
569     delete text;
570 }
571
572 void TextOutputDev::startPage(int pageNum, GfxState *state) {
573   text->clear();
574 }
575
576 void TextOutputDev::endPage() {
577   text->coalesce();
578   if (f) {
579     text->dump(f);
580     fputc('\n', f);
581     fputs("\f\n", f);
582     fputc('\n', f);
583   }
584 }
585
586 void TextOutputDev::updateFont(GfxState *state) {
587   GfxFont *font;
588   char *charName;
589   int c;
590
591   // look for hex char codes in subsetted font
592   hexCodes = gFalse;
593   if ((font = state->getFont())) {
594     for (c = 0; c < 256; ++c) {
595       if ((charName = font->getCharName(c))) {
596         if ((charName[0] == 'B' || charName[0] == 'C' ||
597              charName[0] == 'G') &&
598             strlen(charName) == 3 &&
599             ((charName[1] >= 'a' && charName[1] <= 'f') ||
600              (charName[1] >= 'A' && charName[1] <= 'F') ||
601              (charName[2] >= 'a' && charName[2] <= 'f') ||
602              (charName[2] >= 'A' && charName[2] <= 'F'))) {
603           hexCodes = gTrue;
604           break;
605         }
606       }
607     }
608   }
609 }
610
611 void TextOutputDev::beginString(GfxState *state, GString *s) {
612   text->beginString(state, s, hexCodes);
613 }
614
615 void TextOutputDev::endString(GfxState *state) {
616   text->endString();
617 }
618
619 void TextOutputDev::drawChar(GfxState *state, double x, double y,
620                              double dx, double dy, Guchar c) {
621   text->addChar(state, x, y, dx, dy, c);
622 }
623
624 GBool TextOutputDev::findText(char *s, GBool top, GBool bottom,
625                               double *xMin, double *yMin,
626                               double *xMax, double *yMax) {
627   return text->findText(s, top, bottom, xMin, yMin, xMax, yMax);
628 }