diff options
Diffstat (limited to 'Build/source/libs/poppler/poppler-src/poppler/TextOutputDev.cc')
-rw-r--r-- | Build/source/libs/poppler/poppler-src/poppler/TextOutputDev.cc | 5880 |
1 files changed, 0 insertions, 5880 deletions
diff --git a/Build/source/libs/poppler/poppler-src/poppler/TextOutputDev.cc b/Build/source/libs/poppler/poppler-src/poppler/TextOutputDev.cc deleted file mode 100644 index 4a3070af7c0..00000000000 --- a/Build/source/libs/poppler/poppler-src/poppler/TextOutputDev.cc +++ /dev/null @@ -1,5880 +0,0 @@ -//======================================================================== -// -// TextOutputDev.cc -// -// Copyright 1997-2003 Glyph & Cog, LLC -// -//======================================================================== - -//======================================================================== -// -// Modified under the Poppler project - http://poppler.freedesktop.org -// -// All changes made under the Poppler project to this file are licensed -// under GPL version 2 or later -// -// Copyright (C) 2005-2007 Kristian Høgsberg <krh@redhat.com> -// Copyright (C) 2005 Nickolay V. Shmyrev <nshmyrev@yandex.ru> -// Copyright (C) 2006-2008, 2011-2013 Carlos Garcia Campos <carlosgc@gnome.org> -// Copyright (C) 2006, 2007, 2013 Ed Catmur <ed@catmur.co.uk> -// Copyright (C) 2006 Jeff Muizelaar <jeff@infidigm.net> -// Copyright (C) 2007, 2008, 2012, 2017 Adrian Johnson <ajohnson@redneon.com> -// Copyright (C) 2008 Koji Otani <sho@bbr.jp> -// Copyright (C) 2008, 2010-2012, 2014-2018 Albert Astals Cid <aacid@kde.org> -// Copyright (C) 2008 Pino Toscano <pino@kde.org> -// Copyright (C) 2008, 2010 Hib Eris <hib@hiberis.nl> -// Copyright (C) 2009 Ross Moore <ross@maths.mq.edu.au> -// Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net> -// Copyright (C) 2010 Brian Ewins <brian.ewins@gmail.com> -// Copyright (C) 2010 Marek Kasik <mkasik@redhat.com> -// Copyright (C) 2010 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp> -// Copyright (C) 2011 Sam Liao <phyomh@gmail.com> -// Copyright (C) 2012 Horst Prote <prote@fmi.uni-stuttgart.de> -// Copyright (C) 2012, 2013-2018 Jason Crain <jason@aquaticape.us> -// Copyright (C) 2012 Peter Breitenlohner <peb@mppmu.mpg.de> -// Copyright (C) 2013 José Aliste <jaliste@src.gnome.org> -// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de> -// Copyright (C) 2013 Ed Catmur <ed@catmur.co.uk> -// Copyright (C) 2016 Khaled Hosny <khaledhosny@eglug.org> -// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich -// -// To see a description of the changes please see the Changelog file that -// came with your tarball or type make ChangeLog if you are building from git -// -//======================================================================== - -#include <config.h> - -#ifdef USE_GCC_PRAGMAS -#pragma implementation -#endif - -#include <stdio.h> -#include <stdlib.h> -#include <stddef.h> -#include <cmath> -#include <float.h> -#include <ctype.h> -#include <algorithm> -#ifdef _WIN32 -#include <fcntl.h> // for O_BINARY -#include <io.h> // for setmode -#endif -#include "goo/gmem.h" -#include "goo/GooString.h" -#include "goo/GooList.h" -#include "poppler-config.h" -#include "Error.h" -#include "GlobalParams.h" -#include "UnicodeMap.h" -#include "UnicodeTypeTable.h" -#include "Link.h" -#include "TextOutputDev.h" -#include "Page.h" -#include "Annot.h" -#include "UTF.h" - -#ifdef MACOS -// needed for setting type/creator of MacOS files -#include "ICSupport.h" -#endif - -//------------------------------------------------------------------------ -// parameters -//------------------------------------------------------------------------ - -// Each bucket in a text pool includes baselines within a range of -// this many points. -#define textPoolStep 4 - -// Inter-character space width which will cause addChar to start a new -// word. -#define minWordBreakSpace 0.1 - -// Negative inter-character space width, i.e., overlap, which will -// cause addChar to start a new word. -#define minDupBreakOverlap 0.2 - -// Max distance between baselines of two lines within a block, as a -// fraction of the font size. -#define maxLineSpacingDelta 1.5 - -// Max difference in primary font sizes on two lines in the same -// block. Delta1 is used when examining new lines above and below the -// current block; delta2 is used when examining text that overlaps the -// current block; delta3 is used when examining text to the left and -// right of the current block. -#define maxBlockFontSizeDelta1 0.05 -#define maxBlockFontSizeDelta2 0.6 -#define maxBlockFontSizeDelta3 0.2 - -// Max difference in font sizes inside a word. -#define maxWordFontSizeDelta 0.05 - -// Maximum distance between baselines of two words on the same line, -// e.g., distance between subscript or superscript and the primary -// baseline, as a fraction of the font size. -#define maxIntraLineDelta 0.5 - -// Minimum inter-word spacing, as a fraction of the font size. (Only -// used for raw ordering.) -#define minWordSpacing 0.15 - -// Maximum inter-word spacing, as a fraction of the font size. -#define maxWordSpacing 1.5 - -// Maximum horizontal spacing which will allow a word to be pulled -// into a block. -#define minColSpacing1 0.3 - -// Minimum spacing between columns, as a fraction of the font size. -#define minColSpacing2 1.0 - -// Maximum vertical spacing between blocks within a flow, as a -// multiple of the font size. -#define maxBlockSpacing 2.5 - -// Minimum spacing between characters within a word, as a fraction of -// the font size. -#define minCharSpacing -0.5 - -// Maximum spacing between characters within a word, as a fraction of -// the font size, when there is no obvious extra-wide character -// spacing. -#define maxCharSpacing 0.03 - -// When extra-wide character spacing is detected, the inter-character -// space threshold is set to the minimum inter-character space -// multiplied by this constant. -#define maxWideCharSpacingMul 1.3 - -// Upper limit on spacing between characters in a word. -#define maxWideCharSpacing 0.4 - -// Max difference in primary,secondary coordinates (as a fraction of -// the font size) allowed for duplicated text (fake boldface, drop -// shadows) which is to be discarded. -#define dupMaxPriDelta 0.1 -#define dupMaxSecDelta 0.2 - -// Max width of underlines (in points). -#define maxUnderlineWidth 3 - -// Min distance between baseline and underline (in points). -//~ this should be font-size-dependent -#define minUnderlineGap -2 - -// Max distance between baseline and underline (in points). -//~ this should be font-size-dependent -#define maxUnderlineGap 4 - -// Max horizontal distance between edge of word and start of underline -// (in points). -//~ this should be font-size-dependent -#define underlineSlack 1 - -// Max distance between edge of text and edge of link border -#define hyperlinkSlack 2 - -// Max distance between characters when combining a base character and -// combining character -#define combMaxMidDelta 0.3 -#define combMaxBaseDelta 0.4 - -static int reorderText(Unicode *text, int len, UnicodeMap *uMap, GBool primaryLR, GooString *s, Unicode* u) { - char lre[8], rle[8], popdf[8], buf[8]; - int lreLen = 0, rleLen = 0, popdfLen = 0, n; - int nCols, i, j, k; - - nCols = 0; - - if (s) { - lreLen = uMap->mapUnicode(0x202a, lre, sizeof(lre)); - rleLen = uMap->mapUnicode(0x202b, rle, sizeof(rle)); - popdfLen = uMap->mapUnicode(0x202c, popdf, sizeof(popdf)); - } - - if (primaryLR) { - i = 0; - while (i < len) { - // output a left-to-right section - for (j = i; j < len && !unicodeTypeR(text[j]); ++j) ; - for (k = i; k < j; ++k) { - if (s) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - } - if (u) u[nCols] = text[k]; - ++nCols; - } - i = j; - // output a right-to-left section - for (j = i; - j < len && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); - ++j) ; - if (j > i) { - if (s) s->append(rle, rleLen); - for (k = j - 1; k >= i; --k) { - if (s) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - } - if (u) u[nCols] = text[k]; - ++nCols; - } - if (s) s->append(popdf, popdfLen); - i = j; - } - } - } else { - // Note: This code treats numeric characters (European and - // Arabic/Indic) as left-to-right, which isn't strictly correct - // (incurs extra LRE/POPDF pairs), but does produce correct - // visual formatting. - if (s) s->append(rle, rleLen); - i = len - 1; - while (i >= 0) { - // output a right-to-left section - for (j = i; - j >= 0 && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); - --j) ; - for (k = i; k > j; --k) { - if (s) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - } - if (u) u[nCols] = text[k]; - ++nCols; - } - i = j; - // output a left-to-right section - for (j = i; j >= 0 && !unicodeTypeR(text[j]); --j) ; - if (j < i) { - if (s) s->append(lre, lreLen); - for (k = j + 1; k <= i; ++k) { - if (s) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - } - if (u) u[nCols] = text[k]; - ++nCols; - } - if (s) s->append(popdf, popdfLen); - i = j; - } - } - if (s) s->append(popdf, popdfLen); - } - - return nCols; -} - -//------------------------------------------------------------------------ -// TextUnderline -//------------------------------------------------------------------------ - -class TextUnderline { -public: - - TextUnderline(double x0A, double y0A, double x1A, double y1A) - { x0 = x0A; y0 = y0A; x1 = x1A; y1 = y1A; horiz = y0 == y1; } - ~TextUnderline() {} - - double x0, y0, x1, y1; - GBool horiz; -}; - -//------------------------------------------------------------------------ -// TextLink -//------------------------------------------------------------------------ - -class TextLink { -public: - - TextLink(int xMinA, int yMinA, int xMaxA, int yMaxA, AnnotLink *linkA) - { xMin = xMinA; yMin = yMinA; xMax = xMaxA; yMax = yMaxA; link = linkA; } - ~TextLink() {} - - int xMin, yMin, xMax, yMax; - AnnotLink *link; -}; - -//------------------------------------------------------------------------ -// TextFontInfo -//------------------------------------------------------------------------ - -TextFontInfo::TextFontInfo(GfxState *state) { - gfxFont = state->getFont(); - if (gfxFont) - gfxFont->incRefCnt(); -#ifdef TEXTOUT_WORD_LIST - fontName = (gfxFont && gfxFont->getName()) ? gfxFont->getName()->copy() - : (GooString *)nullptr; - flags = gfxFont ? gfxFont->getFlags() : 0; -#endif -} - -TextFontInfo::~TextFontInfo() { - if (gfxFont) - gfxFont->decRefCnt(); -#ifdef TEXTOUT_WORD_LIST - if (fontName) { - delete fontName; - } -#endif -} - -GBool TextFontInfo::matches(GfxState *state) { - return state->getFont() == gfxFont; -} - -GBool TextFontInfo::matches(TextFontInfo *fontInfo) { - return gfxFont == fontInfo->gfxFont; -} - -double TextFontInfo::getAscent() { - return gfxFont ? gfxFont->getAscent() : 0.95; -} - -double TextFontInfo::getDescent() { - return gfxFont ? gfxFont->getDescent() : -0.35; -} - -int TextFontInfo::getWMode() { - return gfxFont ? gfxFont->getWMode() : 0; -} - -//------------------------------------------------------------------------ -// TextWord -//------------------------------------------------------------------------ - -TextWord::TextWord(GfxState *state, int rotA, double fontSizeA) { - rot = rotA; - fontSize = fontSizeA; - text = nullptr; - charcode = nullptr; - edge = nullptr; - charPos = nullptr; - font = nullptr; - textMat = nullptr; - len = size = 0; - spaceAfter = gFalse; - next = nullptr; - -#ifdef TEXTOUT_WORD_LIST - GfxRGB rgb; - - if ((state->getRender() & 3) == 1) { - state->getStrokeRGB(&rgb); - } else { - state->getFillRGB(&rgb); - } - colorR = colToDbl(rgb.r); - colorG = colToDbl(rgb.g); - colorB = colToDbl(rgb.b); -#endif - - underlined = gFalse; - link = nullptr; -} - -TextWord::~TextWord() { - gfree(text); - gfree(charcode); - gfree(edge); - gfree(charPos); - gfree(font); - gfree(textMat); -} - -void TextWord::addChar(GfxState *state, TextFontInfo *fontA, double x, double y, - double dx, double dy, int charPosA, int charLen, - CharCode c, Unicode u, const Matrix &textMatA) { - ensureCapacity(len+1); - text[len] = u; - charcode[len] = c; - charPos[len] = charPosA; - charPos[len + 1] = charPosA + charLen; - font[len] = fontA; - textMat[len] = textMatA; - - if (len == 0) - setInitialBounds(fontA, x, y); - - if (wMode) { // vertical writing mode - // NB: the rotation value has been incremented by 1 (in - // TextPage::beginWord()) for vertical writing mode - switch (rot) { - case 0: - edge[len] = x - fontSize; - xMax = edge[len+1] = x; - break; - case 1: - edge[len] = y - fontSize; - yMax = edge[len+1] = y; - break; - case 2: - edge[len] = x + fontSize; - xMin = edge[len+1] = x; - break; - case 3: - edge[len] = y + fontSize; - yMin = edge[len+1] = y; - break; - } - } else { // horizontal writing mode - switch (rot) { - case 0: - edge[len] = x; - xMax = edge[len+1] = x + dx; - break; - case 1: - edge[len] = y; - yMax = edge[len+1] = y + dy; - break; - case 2: - edge[len] = x; - xMin = edge[len+1] = x + dx; - break; - case 3: - edge[len] = y; - yMin = edge[len+1] = y + dy; - break; - } - } - ++len; -} - -void TextWord::setInitialBounds(TextFontInfo *fontA, double x, double y) { - double ascent = fontA->getAscent() * fontSize; - double descent = fontA->getDescent() * fontSize; - wMode = fontA->getWMode(); - - if (wMode) { // vertical writing mode - // NB: the rotation value has been incremented by 1 (in - // TextPage::beginWord()) for vertical writing mode - switch (rot) { - case 0: - xMin = x - fontSize; - yMin = y - fontSize; - yMax = y; - base = y; - break; - case 1: - xMin = x; - yMin = y - fontSize; - xMax = x + fontSize; - base = x; - break; - case 2: - yMin = y; - xMax = x + fontSize; - yMax = y + fontSize; - base = y; - break; - case 3: - xMin = x - fontSize; - xMax = x; - yMax = y + fontSize; - base = x; - break; - } - } else { // horizontal writing mode - switch (rot) { - case 0: - xMin = x; - yMin = y - ascent; - yMax = y - descent; - if (yMin == yMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - yMin = y; - yMax = y + 1; - } - base = y; - break; - case 1: - xMin = x + descent; - yMin = y; - xMax = x + ascent; - if (xMin == xMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - xMin = x; - xMax = x + 1; - } - base = x; - break; - case 2: - yMin = y + descent; - xMax = x; - yMax = y + ascent; - if (yMin == yMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - yMin = y; - yMax = y + 1; - } - base = y; - break; - case 3: - xMin = x - ascent; - xMax = x - descent; - yMax = y; - if (xMin == xMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - xMin = x; - xMax = x + 1; - } - base = x; - break; - } - } -} - -void TextWord::ensureCapacity(int capacity) { - if (capacity > size) { - size = std::max(size + 16, capacity); - text = (Unicode *)greallocn(text, size, sizeof(Unicode)); - charcode = (CharCode *)greallocn(charcode, (size + 1), sizeof(CharCode)); - edge = (double *)greallocn(edge, (size + 1), sizeof(double)); - charPos = (int *)greallocn(charPos, size + 1, sizeof(int)); - font = (TextFontInfo **)greallocn(font, size, sizeof(TextFontInfo *)); - textMat = (Matrix *)greallocn(textMat, size, sizeof(Matrix)); - } -} - -struct CombiningTable { - Unicode base; - Unicode comb; -}; - -static struct CombiningTable combiningTable[] = { - {0x0060, 0x0300}, // grave - {0x00a8, 0x0308}, // dieresis - {0x00af, 0x0304}, // macron - {0x00b4, 0x0301}, // acute - {0x00b8, 0x0327}, // cedilla - {0x02c6, 0x0302}, // circumflex - {0x02c7, 0x030c}, // caron - {0x02d8, 0x0306}, // breve - {0x02d9, 0x0307}, // dotaccent - {0x02da, 0x030a}, // ring - {0x02dc, 0x0303}, // tilde - {0x02dd, 0x030b} // hungarumlaut (double acute accent) -}; - -// returning combining versions of characters -static Unicode getCombiningChar(Unicode u) { - int len = sizeof(combiningTable) / sizeof(combiningTable[0]); - for (int i = 0; i < len; ++i) { - if (u == combiningTable[i].base) - return combiningTable[i].comb; - } - return 0; -} - -GBool TextWord::addCombining(GfxState *state, TextFontInfo *fontA, double fontSizeA, double x, double y, - double dx, double dy, int charPosA, int charLen, - CharCode c, Unicode u, const Matrix &textMatA) { - if (len == 0 || wMode != 0 || fontA->getWMode() != 0) - return gFalse; - - Unicode cCurrent = getCombiningChar(u); - Unicode cPrev = getCombiningChar(text[len-1]); - double edgeMid = (edge[len-1] + edge[len]) / 2; - double charMid, maxScaledMidDelta, charBase, maxScaledBaseDelta; - - if (cCurrent != 0 && unicodeTypeAlphaNum(text[len-1])) { - // Current is a combining character, previous is base character - maxScaledMidDelta = fabs(edge[len] - edge[len-1]) * combMaxMidDelta; - charMid = charBase = maxScaledBaseDelta = 0; - - // Test if characters overlap - if (rot == 0 || rot == 2) { - charMid = x + (dx / 2); - charBase = y; - maxScaledBaseDelta = (yMax - yMin) * combMaxBaseDelta; - } else { - charMid = y + (dy / 2); - charBase = x; - maxScaledBaseDelta = (xMax - xMin) * combMaxBaseDelta; - } - - if (fabs(charMid - edgeMid) >= maxScaledMidDelta || - fabs(charBase - base) >= maxScaledBaseDelta) - return gFalse; - - // Add character, but don't adjust edge / bounding box because - // combining character's positioning could be odd. - ensureCapacity(len+1); - text[len] = cCurrent; - charcode[len] = c; - charPos[len] = charPosA; - charPos[len+1] = charPosA + charLen; - font[len] = fontA; - textMat[len] = textMatA; - edge[len+1] = edge[len]; - edge[len] = (edge[len+1] + edge[len-1]) / 2; - ++len; - return gTrue; - } - - if (cPrev != 0 && unicodeTypeAlphaNum(u)) { - // Previous is a combining character, current is base character - maxScaledBaseDelta = (fontA->getAscent() - fontA->getDescent()) * fontSizeA * combMaxBaseDelta; - charMid = charBase = maxScaledMidDelta = 0; - - // Test if characters overlap - if (rot == 0 || rot == 2) { - charMid = x + (dx / 2); - charBase = y; - maxScaledMidDelta = fabs(dx * combMaxMidDelta); - } else { - charMid = y + (dy / 2); - charBase = x; - maxScaledMidDelta = fabs(dy * combMaxMidDelta); - } - - if (fabs(charMid - edgeMid) >= maxScaledMidDelta || - fabs(charBase - base) >= maxScaledBaseDelta) - return gFalse; - - // move combining character to after base character - ensureCapacity(len+1); - fontSize = fontSizeA; - text[len] = cPrev; - charcode[len] = charcode[len-1]; - charPos[len] = charPosA; - charPos[len+1] = charPosA + charLen; - font[len] = font[len-1]; - textMat[len] = textMat[len-1]; - - text[len-1] = u; - charcode[len-1] = c; - font[len-1] = fontA; - textMat[len-1] = textMatA; - - if (len == 1) - setInitialBounds(fontA, x, y); - - // Updated edges / bounding box because we changed the base - // character. - if (wMode) { - switch (rot) { - case 0: - edge[len-1] = x - fontSize; - xMax = edge[len+1] = x; - break; - case 1: - edge[len-1] = y - fontSize; - yMax = edge[len+1] = y; - break; - case 2: - edge[len-1] = x + fontSize; - xMin = edge[len+1] = x; - break; - case 3: - edge[len-1] = y + fontSize; - yMin = edge[len+1] = y; - break; - } - } else { - switch (rot) { - case 0: - edge[len-1] = x; - xMax = edge[len+1] = x + dx; - break; - case 1: - edge[len-1] = y; - yMax = edge[len+1] = y + dy; - break; - case 2: - edge[len-1] = x; - xMin = edge[len+1] = x + dx; - break; - case 3: - edge[len-1] = y; - yMin = edge[len+1] = y + dy; - break; - } - } - - edge[len] = (edge[len+1] + edge[len-1]) / 2; - ++len; - return gTrue; - } - return gFalse; -} - -void TextWord::merge(TextWord *word) { - int i; - - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->yMin < yMin) { - yMin = word->yMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMax > yMax) { - yMax = word->yMax; - } - ensureCapacity(len + word->len); - for (i = 0; i < word->len; ++i) { - text[len + i] = word->text[i]; - charcode[len + i] = word->charcode[i]; - edge[len + i] = word->edge[i]; - charPos[len + i] = word->charPos[i]; - font[len + i] = word->font[i]; - textMat[len + i] = word->textMat[i]; - } - edge[len + word->len] = word->edge[word->len]; - charPos[len + word->len] = word->charPos[word->len]; - len += word->len; -} - -inline int TextWord::primaryCmp(TextWord *word) { - double cmp; - - cmp = 0; // make gcc happy - switch (rot) { - case 0: - cmp = xMin - word->xMin; - break; - case 1: - cmp = yMin - word->yMin; - break; - case 2: - cmp = word->xMax - xMax; - break; - case 3: - cmp = word->yMax - yMax; - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -double TextWord::primaryDelta(TextWord *word) { - double delta; - - delta = 0; // make gcc happy - switch (rot) { - case 0: - delta = word->xMin - xMax; - break; - case 1: - delta = word->yMin - yMax; - break; - case 2: - delta = xMin - word->xMax; - break; - case 3: - delta = yMin - word->yMax; - break; - } - return delta; -} - -int TextWord::cmpYX(const void *p1, const void *p2) { - TextWord *word1 = *(TextWord **)p1; - TextWord *word2 = *(TextWord **)p2; - double cmp; - - cmp = word1->yMin - word2->yMin; - if (cmp == 0) { - cmp = word1->xMin - word2->xMin; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -#ifdef TEXTOUT_WORD_LIST - -GooString *TextWord::getText() { - GooString *s; - UnicodeMap *uMap; - char buf[8]; - int n, i; - - s = new GooString(); - if (!(uMap = globalParams->getTextEncoding())) { - return s; - } - for (i = 0; i < len; ++i) { - n = uMap->mapUnicode(text[i], buf, sizeof(buf)); - s->append(buf, n); - } - uMap->decRefCnt(); - return s; -} - -void TextWord::getCharBBox(int charIdx, double *xMinA, double *yMinA, - double *xMaxA, double *yMaxA) { - if (charIdx < 0 || charIdx >= len) { - return; - } - switch (rot) { - case 0: - *xMinA = edge[charIdx]; - *xMaxA = edge[charIdx + 1]; - *yMinA = yMin; - *yMaxA = yMax; - break; - case 1: - *xMinA = xMin; - *xMaxA = xMax; - *yMinA = edge[charIdx]; - *yMaxA = edge[charIdx + 1]; - break; - case 2: - *xMinA = edge[charIdx + 1]; - *xMaxA = edge[charIdx]; - *yMinA = yMin; - *yMaxA = yMax; - break; - case 3: - *xMinA = xMin; - *xMaxA = xMax; - *yMinA = edge[charIdx + 1]; - *yMaxA = edge[charIdx]; - break; - } -} - -#endif // TEXTOUT_WORD_LIST - -//------------------------------------------------------------------------ -// TextPool -//------------------------------------------------------------------------ - -TextPool::TextPool() { - minBaseIdx = 0; - maxBaseIdx = -1; - pool = nullptr; - cursor = nullptr; - cursorBaseIdx = -1; -} - -TextPool::~TextPool() { - int baseIdx; - TextWord *word, *word2; - - for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) { - for (word = pool[baseIdx - minBaseIdx]; word; word = word2) { - word2 = word->next; - delete word; - } - } - gfree(pool); -} - -int TextPool::getBaseIdx(double base) { - int baseIdx; - - baseIdx = (int)(base / textPoolStep); - if (baseIdx < minBaseIdx) { - return minBaseIdx; - } - if (baseIdx > maxBaseIdx) { - return maxBaseIdx; - } - return baseIdx; -} - -void TextPool::addWord(TextWord *word) { - TextWord **newPool; - int wordBaseIdx, newMinBaseIdx, newMaxBaseIdx, baseIdx; - TextWord *w0, *w1; - - // expand the array if needed - wordBaseIdx = (int)(word->base / textPoolStep); - if (unlikely(wordBaseIdx <= INT_MIN + 128 || wordBaseIdx >= INT_MAX - 128)) { - error(errSyntaxWarning, -1, "wordBaseIdx out of range"); - delete word; - return; - } - if (minBaseIdx > maxBaseIdx) { - minBaseIdx = wordBaseIdx - 128; - maxBaseIdx = wordBaseIdx + 128; - pool = (TextWord **)gmallocn(maxBaseIdx - minBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) { - pool[baseIdx - minBaseIdx] = nullptr; - } - } else if (wordBaseIdx < minBaseIdx) { - newMinBaseIdx = wordBaseIdx - 128; - newPool = (TextWord **)gmallocn(maxBaseIdx - newMinBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = newMinBaseIdx; baseIdx < minBaseIdx; ++baseIdx) { - newPool[baseIdx - newMinBaseIdx] = nullptr; - } - memcpy(&newPool[minBaseIdx - newMinBaseIdx], pool, - (maxBaseIdx - minBaseIdx + 1) * sizeof(TextWord *)); - gfree(pool); - pool = newPool; - minBaseIdx = newMinBaseIdx; - } else if (wordBaseIdx > maxBaseIdx) { - newMaxBaseIdx = wordBaseIdx + 128; - pool = (TextWord **)greallocn(pool, newMaxBaseIdx - minBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = maxBaseIdx + 1; baseIdx <= newMaxBaseIdx; ++baseIdx) { - pool[baseIdx - minBaseIdx] = nullptr; - } - maxBaseIdx = newMaxBaseIdx; - } - - // insert the new word - if (cursor && wordBaseIdx == cursorBaseIdx && - word->primaryCmp(cursor) >= 0) { - w0 = cursor; - w1 = cursor->next; - } else { - w0 = nullptr; - w1 = pool[wordBaseIdx - minBaseIdx]; - } - for (; w1 && word->primaryCmp(w1) > 0; w0 = w1, w1 = w1->next) ; - word->next = w1; - if (w0) { - w0->next = word; - } else { - pool[wordBaseIdx - minBaseIdx] = word; - } - cursor = word; - cursorBaseIdx = wordBaseIdx; -} - -//------------------------------------------------------------------------ -// TextLine -//------------------------------------------------------------------------ - -TextLine::TextLine(TextBlock *blkA, int rotA, double baseA) { - blk = blkA; - rot = rotA; - base = baseA; - words = lastWord = nullptr; - text = nullptr; - edge = nullptr; - col = nullptr; - len = 0; - convertedLen = 0; - hyphenated = gFalse; - next = nullptr; - xMin = yMin = 0; - xMax = yMax = -1; - normalized = nullptr; - normalized_len = 0; - normalized_idx = nullptr; -} - -TextLine::~TextLine() { - TextWord *word; - - while (words) { - word = words; - words = words->next; - delete word; - } - gfree(text); - gfree(edge); - gfree(col); - if (normalized) { - gfree(normalized); - gfree(normalized_idx); - } -} - -void TextLine::addWord(TextWord *word) { - if (lastWord) { - lastWord->next = word; - } else { - words = word; - } - lastWord = word; - - if (xMin > xMax) { - xMin = word->xMin; - xMax = word->xMax; - yMin = word->yMin; - yMax = word->yMax; - } else { - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMin < yMin) { - yMin = word->yMin; - } - if (word->yMax > yMax) { - yMax = word->yMax; - } - } -} - -double TextLine::primaryDelta(TextLine *line) { - double delta; - - delta = 0; // make gcc happy - switch (rot) { - case 0: - delta = line->xMin - xMax; - break; - case 1: - delta = line->yMin - yMax; - break; - case 2: - delta = xMin - line->xMax; - break; - case 3: - delta = yMin - line->yMax; - break; - } - return delta; -} - -int TextLine::primaryCmp(TextLine *line) { - double cmp; - - cmp = 0; // make gcc happy - switch (rot) { - case 0: - cmp = xMin - line->xMin; - break; - case 1: - cmp = yMin - line->yMin; - break; - case 2: - cmp = line->xMax - xMax; - break; - case 3: - cmp = line->yMax - yMax; - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLine::secondaryCmp(TextLine *line) { - double cmp; - - cmp = (rot == 0 || rot == 3) ? base - line->base : line->base - base; - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLine::cmpYX(TextLine *line) { - int cmp; - - if ((cmp = secondaryCmp(line))) { - return cmp; - } - return primaryCmp(line); -} - -int TextLine::cmpXY(const void *p1, const void *p2) { - TextLine *line1 = *(TextLine **)p1; - TextLine *line2 = *(TextLine **)p2; - int cmp; - - if ((cmp = line1->primaryCmp(line2))) { - return cmp; - } - return line1->secondaryCmp(line2); -} - -void TextLine::coalesce(UnicodeMap *uMap) { - TextWord *word0, *word1; - double space, delta, minSpace; - GBool isUnicode; - char buf[8]; - int i, j; - - if (words->next) { - - // compute the inter-word space threshold - if (words->len > 1 || words->next->len > 1) { - minSpace = 0; - } else { - minSpace = words->primaryDelta(words->next); - for (word0 = words->next, word1 = word0->next; - word1 && minSpace > 0; - word0 = word1, word1 = word0->next) { - if (word1->len > 1) { - minSpace = 0; - } - delta = word0->primaryDelta(word1); - if (delta < minSpace) { - minSpace = delta; - } - } - } - if (minSpace <= 0) { - space = maxCharSpacing * words->fontSize; - } else { - space = maxWideCharSpacingMul * minSpace; - if (space > maxWideCharSpacing * words->fontSize) { - space = maxWideCharSpacing * words->fontSize; - } - } - - // merge words - word0 = words; - word1 = words->next; - while (word1) { - if (word0->primaryDelta(word1) >= space) { - word0->spaceAfter = gTrue; - word0 = word1; - word1 = word1->next; - } else if (word0->font[word0->len - 1] == word1->font[0] && - word0->underlined == word1->underlined && - fabs(word0->fontSize - word1->fontSize) < - maxWordFontSizeDelta * words->fontSize && - word1->charPos[0] == word0->charPos[word0->len]) { - word0->merge(word1); - word0->next = word1->next; - delete word1; - word1 = word0->next; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - - // build the line text - isUnicode = uMap ? uMap->isUnicode() : gFalse; - len = 0; - for (word1 = words; word1; word1 = word1->next) { - len += word1->len; - if (word1->spaceAfter) { - ++len; - } - } - text = (Unicode *)gmallocn(len, sizeof(Unicode)); - edge = (double *)gmallocn(len + 1, sizeof(double)); - i = 0; - for (word1 = words; word1; word1 = word1->next) { - for (j = 0; j < word1->len; ++j) { - text[i] = word1->text[j]; - edge[i] = word1->edge[j]; - ++i; - } - edge[i] = word1->edge[word1->len]; - if (word1->spaceAfter) { - text[i] = (Unicode)0x0020; - ++i; - } - } - - // compute convertedLen and set up the col array - col = (int *)gmallocn(len + 1, sizeof(int)); - convertedLen = 0; - for (i = 0; i < len; ++i) { - col[i] = convertedLen; - if (isUnicode) { - ++convertedLen; - } else if (uMap) { - convertedLen += uMap->mapUnicode(text[i], buf, sizeof(buf)); - } - } - col[len] = convertedLen; - - // check for hyphen at end of line - //~ need to check for other chars used as hyphens - hyphenated = text[len - 1] == (Unicode)'-'; -} - -//------------------------------------------------------------------------ -// TextLineFrag -//------------------------------------------------------------------------ - -class TextLineFrag { -public: - - TextLine *line; // the line object - int start, len; // offset and length of this fragment - // (in Unicode chars) - double xMin, xMax; // bounding box coordinates - double yMin, yMax; - double base; // baseline virtual coordinate - int col; // first column - - void init(TextLine *lineA, int startA, int lenA); - void computeCoords(GBool oneRot); - - static int cmpYXPrimaryRot(const void *p1, const void *p2); - static int cmpYXLineRot(const void *p1, const void *p2); - static int cmpXYLineRot(const void *p1, const void *p2); - static int cmpXYColumnPrimaryRot(const void *p1, const void *p2); - static int cmpXYColumnLineRot(const void *p1, const void *p2); -}; - -void TextLineFrag::init(TextLine *lineA, int startA, int lenA) { - line = lineA; - start = startA; - len = lenA; - col = line->col[start]; -} - -void TextLineFrag::computeCoords(GBool oneRot) { - TextBlock *blk; - double d0, d1, d2, d3, d4; - - if (oneRot) { - - switch (line->rot) { - case 0: - xMin = line->edge[start]; - xMax = line->edge[start + len]; - yMin = line->yMin; - yMax = line->yMax; - break; - case 1: - xMin = line->xMin; - xMax = line->xMax; - yMin = line->edge[start]; - yMax = line->edge[start + len]; - break; - case 2: - xMin = line->edge[start + len]; - xMax = line->edge[start]; - yMin = line->yMin; - yMax = line->yMax; - break; - case 3: - xMin = line->xMin; - xMax = line->xMax; - yMin = line->edge[start + len]; - yMax = line->edge[start]; - break; - } - base = line->base; - - } else { - - if (line->rot == 0 && line->blk->page->primaryRot == 0) { - - xMin = line->edge[start]; - xMax = line->edge[start + len]; - yMin = line->yMin; - yMax = line->yMax; - base = line->base; - - } else { - - blk = line->blk; - d0 = line->edge[start]; - d1 = line->edge[start + len]; - d2 = d3 = d4 = 0; // make gcc happy - - switch (line->rot) { - case 0: - d2 = line->yMin; - d3 = line->yMax; - d4 = line->base; - d0 = (d0 - blk->xMin) / (blk->xMax - blk->xMin); - d1 = (d1 - blk->xMin) / (blk->xMax - blk->xMin); - d2 = (d2 - blk->yMin) / (blk->yMax - blk->yMin); - d3 = (d3 - blk->yMin) / (blk->yMax - blk->yMin); - d4 = (d4 - blk->yMin) / (blk->yMax - blk->yMin); - break; - case 1: - d2 = line->xMax; - d3 = line->xMin; - d4 = line->base; - d0 = (d0 - blk->yMin) / (blk->yMax - blk->yMin); - d1 = (d1 - blk->yMin) / (blk->yMax - blk->yMin); - d2 = (blk->xMax - d2) / (blk->xMax - blk->xMin); - d3 = (blk->xMax - d3) / (blk->xMax - blk->xMin); - d4 = (blk->xMax - d4) / (blk->xMax - blk->xMin); - break; - case 2: - d2 = line->yMax; - d3 = line->yMin; - d4 = line->base; - d0 = (blk->xMax - d0) / (blk->xMax - blk->xMin); - d1 = (blk->xMax - d1) / (blk->xMax - blk->xMin); - d2 = (blk->yMax - d2) / (blk->yMax - blk->yMin); - d3 = (blk->yMax - d3) / (blk->yMax - blk->yMin); - d4 = (blk->yMax - d4) / (blk->yMax - blk->yMin); - break; - case 3: - d2 = line->xMin; - d3 = line->xMax; - d4 = line->base; - d0 = (blk->yMax - d0) / (blk->yMax - blk->yMin); - d1 = (blk->yMax - d1) / (blk->yMax - blk->yMin); - d2 = (d2 - blk->xMin) / (blk->xMax - blk->xMin); - d3 = (d3 - blk->xMin) / (blk->xMax - blk->xMin); - d4 = (d4 - blk->xMin) / (blk->xMax - blk->xMin); - break; - } - - switch (line->blk->page->primaryRot) { - case 0: - xMin = blk->xMin + d0 * (blk->xMax - blk->xMin); - xMax = blk->xMin + d1 * (blk->xMax - blk->xMin); - yMin = blk->yMin + d2 * (blk->yMax - blk->yMin); - yMax = blk->yMin + d3 * (blk->yMax - blk->yMin); - base = blk->yMin + d4 * (blk->yMax - blk->yMin); - break; - case 1: - xMin = blk->xMax - d3 * (blk->xMax - blk->xMin); - xMax = blk->xMax - d2 * (blk->xMax - blk->xMin); - yMin = blk->yMin + d0 * (blk->yMax - blk->yMin); - yMax = blk->yMin + d1 * (blk->yMax - blk->yMin); - base = blk->xMax - d4 * (blk->xMax - blk->xMin); - break; - case 2: - xMin = blk->xMax - d1 * (blk->xMax - blk->xMin); - xMax = blk->xMax - d0 * (blk->xMax - blk->xMin); - yMin = blk->yMax - d3 * (blk->yMax - blk->yMin); - yMax = blk->yMax - d2 * (blk->yMax - blk->yMin); - base = blk->yMax - d4 * (blk->yMax - blk->yMin); - break; - case 3: - xMin = blk->xMin + d2 * (blk->xMax - blk->xMin); - xMax = blk->xMin + d3 * (blk->xMax - blk->xMin); - yMin = blk->yMax - d1 * (blk->yMax - blk->yMin); - yMax = blk->yMax - d0 * (blk->yMax - blk->yMin); - base = blk->xMin + d4 * (blk->xMax - blk->xMin); - break; - } - - } - } -} - -int TextLineFrag::cmpYXPrimaryRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (frag1->line->blk->page->primaryRot) { - case 0: - if (fabs(cmp = frag1->yMin - frag2->yMin) < 0.01) { - cmp = frag1->xMin - frag2->xMin; - } - break; - case 1: - if (fabs(cmp = frag2->xMax - frag1->xMax) < 0.01) { - cmp = frag1->yMin - frag2->yMin; - } - break; - case 2: - if (fabs(cmp = frag2->yMin - frag1->yMin) < 0.01) { - cmp = frag2->xMax - frag1->xMax; - } - break; - case 3: - if (fabs(cmp = frag1->xMax - frag2->xMax) < 0.01) { - cmp = frag2->yMax - frag1->yMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLineFrag::cmpYXLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: - if ((cmp = frag1->yMin - frag2->yMin) == 0) { - cmp = frag1->xMin - frag2->xMin; - } - break; - case 1: - if ((cmp = frag2->xMax - frag1->xMax) == 0) { - cmp = frag1->yMin - frag2->yMin; - } - break; - case 2: - if ((cmp = frag2->yMin - frag1->yMin) == 0) { - cmp = frag2->xMax - frag1->xMax; - } - break; - case 3: - if ((cmp = frag1->xMax - frag2->xMax) == 0) { - cmp = frag2->yMax - frag1->yMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLineFrag::cmpXYLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: - if ((cmp = frag1->xMin - frag2->xMin) == 0) { - cmp = frag1->yMin - frag2->yMin; - } - break; - case 1: - if ((cmp = frag1->yMin - frag2->yMin) == 0) { - cmp = frag2->xMax - frag1->xMax; - } - break; - case 2: - if ((cmp = frag2->xMax - frag1->xMax) == 0) { - cmp = frag2->yMin - frag1->yMin; - } - break; - case 3: - if ((cmp = frag2->yMax - frag1->yMax) == 0) { - cmp = frag1->xMax - frag2->xMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextLineFrag::cmpXYColumnPrimaryRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - // if columns overlap, compare y values - if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] - - frag2->line->col[frag2->start]) && - frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start])) { - cmp = 0; // make gcc happy - switch (frag1->line->blk->page->primaryRot) { - case 0: cmp = frag1->yMin - frag2->yMin; break; - case 1: cmp = frag2->xMax - frag1->xMax; break; - case 2: cmp = frag2->yMin - frag1->yMin; break; - case 3: cmp = frag1->xMax - frag2->xMax; break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; - } - - // otherwise, compare starting column - return frag1->col - frag2->col; -} - -int TextLineFrag::cmpXYColumnLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; - - // if columns overlap, compare y values - if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] - - frag2->line->col[frag2->start]) && - frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start])) { - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: cmp = frag1->yMin - frag2->yMin; break; - case 1: cmp = frag2->xMax - frag1->xMax; break; - case 2: cmp = frag2->yMin - frag1->yMin; break; - case 3: cmp = frag1->xMax - frag2->xMax; break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; - } - - // otherwise, compare starting column - return frag1->col - frag2->col; -} - -//------------------------------------------------------------------------ -// TextBlock -//------------------------------------------------------------------------ - -TextBlock::TextBlock(TextPage *pageA, int rotA) { - page = pageA; - rot = rotA; - xMin = yMin = 0; - xMax = yMax = -1; - priMin = 0; - priMax = page->pageWidth; - pool = new TextPool(); - lines = nullptr; - curLine = nullptr; - next = nullptr; - stackNext = nullptr; - tableId = -1; - tableEnd = gFalse; -} - -TextBlock::~TextBlock() { - TextLine *line; - - delete pool; - while (lines) { - line = lines; - lines = lines->next; - delete line; - } -} - -void TextBlock::addWord(TextWord *word) { - pool->addWord(word); - if (xMin > xMax) { - xMin = word->xMin; - xMax = word->xMax; - yMin = word->yMin; - yMax = word->yMax; - } else { - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMin < yMin) { - yMin = word->yMin; - } - if (word->yMax > yMax) { - yMax = word->yMax; - } - } -} - -void TextBlock::coalesce(UnicodeMap *uMap, double fixedPitch) { - TextWord *word0, *word1, *word2, *bestWord0, *bestWord1, *lastWord; - TextLine *line, *line0, *line1; - int poolMinBaseIdx, startBaseIdx, minBaseIdx, maxBaseIdx; - int baseIdx, bestWordBaseIdx, idx0, idx1; - double minBase, maxBase; - double fontSize, wordSpacing, delta, priDelta, secDelta; - TextLine **lineArray; - GBool found, overlap; - int col1, col2; - int i, j, k; - - // discard duplicated text (fake boldface, drop shadows) - for (idx0 = pool->minBaseIdx; idx0 <= pool->maxBaseIdx; ++idx0) { - word0 = pool->getPool(idx0); - while (word0) { - priDelta = dupMaxPriDelta * word0->fontSize; - secDelta = dupMaxSecDelta * word0->fontSize; - maxBaseIdx = pool->getBaseIdx(word0->base + secDelta); - found = gFalse; - word1 = word2 = nullptr; // make gcc happy - for (idx1 = idx0; idx1 <= maxBaseIdx; ++idx1) { - if (idx1 == idx0) { - word1 = word0; - word2 = word0->next; - } else { - word1 = nullptr; - word2 = pool->getPool(idx1); - } - for (; word2; word1 = word2, word2 = word2->next) { - if (word2->len == word0->len && - !memcmp(word2->text, word0->text, - word0->len * sizeof(Unicode))) { - switch (rot) { - case 0: - case 2: - found = fabs(word0->xMin - word2->xMin) < priDelta && - fabs(word0->xMax - word2->xMax) < priDelta && - fabs(word0->yMin - word2->yMin) < secDelta && - fabs(word0->yMax - word2->yMax) < secDelta; - break; - case 1: - case 3: - found = fabs(word0->xMin - word2->xMin) < secDelta && - fabs(word0->xMax - word2->xMax) < secDelta && - fabs(word0->yMin - word2->yMin) < priDelta && - fabs(word0->yMax - word2->yMax) < priDelta; - break; - } - } - if (found) { - break; - } - } - if (found) { - break; - } - } - if (found) { - if (word1) { - word1->next = word2->next; - } else { - pool->setPool(idx1, word2->next); - } - delete word2; - } else { - word0 = word0->next; - } - } - } - - // build the lines - curLine = nullptr; - poolMinBaseIdx = pool->minBaseIdx; - charCount = 0; - nLines = 0; - while (1) { - - // find the first non-empty line in the pool - for (; - poolMinBaseIdx <= pool->maxBaseIdx && !pool->getPool(poolMinBaseIdx); - ++poolMinBaseIdx) ; - if (poolMinBaseIdx > pool->maxBaseIdx) { - break; - } - - // look for the left-most word in the first four lines of the - // pool -- this avoids starting with a superscript word - startBaseIdx = poolMinBaseIdx; - for (baseIdx = poolMinBaseIdx + 1; - baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx; - ++baseIdx) { - if (!pool->getPool(baseIdx)) { - continue; - } - if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx)) - < 0) { - startBaseIdx = baseIdx; - } - } - - // create a new line - word0 = pool->getPool(startBaseIdx); - pool->setPool(startBaseIdx, word0->next); - word0->next = nullptr; - line = new TextLine(this, word0->rot, word0->base); - line->addWord(word0); - lastWord = word0; - - // compute the search range - fontSize = word0->fontSize; - minBase = word0->base - maxIntraLineDelta * fontSize; - maxBase = word0->base + maxIntraLineDelta * fontSize; - minBaseIdx = pool->getBaseIdx(minBase); - maxBaseIdx = pool->getBaseIdx(maxBase); - wordSpacing = fixedPitch ? fixedPitch : maxWordSpacing * fontSize; - - // find the rest of the words in this line - while (1) { - - // find the left-most word whose baseline is in the range for - // this line - bestWordBaseIdx = 0; - bestWord0 = bestWord1 = nullptr; - overlap = gFalse; - for (baseIdx = minBaseIdx; - !overlap && baseIdx <= maxBaseIdx; - ++baseIdx) { - for (word0 = nullptr, word1 = pool->getPool(baseIdx); - word1; - word0 = word1, word1 = word1->next) { - if (word1->base >= minBase && - word1->base <= maxBase) { - delta = lastWord->primaryDelta(word1); - if (delta < minCharSpacing * fontSize) { - overlap = gTrue; - break; - } else { - if (delta < wordSpacing && - (!bestWord1 || word1->primaryCmp(bestWord1) < 0)) { - bestWordBaseIdx = baseIdx; - bestWord0 = word0; - bestWord1 = word1; - } - break; - } - } - } - } - if (overlap || !bestWord1) { - break; - } - - // remove it from the pool, and add it to the line - if (bestWord0) { - bestWord0->next = bestWord1->next; - } else { - pool->setPool(bestWordBaseIdx, bestWord1->next); - } - bestWord1->next = nullptr; - line->addWord(bestWord1); - lastWord = bestWord1; - } - - // add the line - if (curLine && line->cmpYX(curLine) > 0) { - line0 = curLine; - line1 = curLine->next; - } else { - line0 = nullptr; - line1 = lines; - } - for (; - line1 && line->cmpYX(line1) > 0; - line0 = line1, line1 = line1->next) ; - if (line0) { - line0->next = line; - } else { - lines = line; - } - line->next = line1; - curLine = line; - line->coalesce(uMap); - charCount += line->len; - ++nLines; - } - - // sort lines into xy order for column assignment - lineArray = (TextLine **)gmallocn(nLines, sizeof(TextLine *)); - for (line = lines, i = 0; line; line = line->next, ++i) { - lineArray[i] = line; - } - qsort(lineArray, nLines, sizeof(TextLine *), &TextLine::cmpXY); - - // column assignment - nColumns = 0; - if (fixedPitch) { - for (i = 0; i < nLines; ++i) { - line0 = lineArray[i]; - col1 = 0; // make gcc happy - switch (rot) { - case 0: - col1 = (int)((line0->xMin - xMin) / fixedPitch + 0.5); - break; - case 1: - col1 = (int)((line0->yMin - yMin) / fixedPitch + 0.5); - break; - case 2: - col1 = (int)((xMax - line0->xMax) / fixedPitch + 0.5); - break; - case 3: - col1 = (int)((yMax - line0->yMax) / fixedPitch + 0.5); - break; - } - for (k = 0; k <= line0->len; ++k) { - line0->col[k] += col1; - } - if (line0->col[line0->len] > nColumns) { - nColumns = line0->col[line0->len]; - } - } - } else { - for (i = 0; i < nLines; ++i) { - line0 = lineArray[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - line1 = lineArray[j]; - if (line1->primaryDelta(line0) >= 0) { - col2 = line1->col[line1->len] + 1; - } else { - k = 0; // make gcc happy - switch (rot) { - case 0: - for (k = 0; - k < line1->len && - line0->xMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 1: - for (k = 0; - k < line1->len && - line0->yMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 2: - for (k = 0; - k < line1->len && - line0->xMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 3: - for (k = 0; - k < line1->len && - line0->yMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - } - col2 = line1->col[k]; - } - if (col2 > col1) { - col1 = col2; - } - } - for (k = 0; k <= line0->len; ++k) { - line0->col[k] += col1; - } - if (line0->col[line0->len] > nColumns) { - nColumns = line0->col[line0->len]; - } - } - } - gfree(lineArray); -} - -void TextBlock::updatePriMinMax(TextBlock *blk) { - double newPriMin, newPriMax; - GBool gotPriMin, gotPriMax; - - gotPriMin = gotPriMax = gFalse; - newPriMin = newPriMax = 0; // make gcc happy - switch (page->primaryRot) { - case 0: - case 2: - if (blk->yMin < yMax && blk->yMax > yMin) { - if (blk->xMin < xMin) { - newPriMin = blk->xMax; - gotPriMin = gTrue; - } - if (blk->xMax > xMax) { - newPriMax = blk->xMin; - gotPriMax = gTrue; - } - } - break; - case 1: - case 3: - if (blk->xMin < xMax && blk->xMax > xMin) { - if (blk->yMin < yMin) { - newPriMin = blk->yMax; - gotPriMin = gTrue; - } - if (blk->yMax > yMax) { - newPriMax = blk->yMin; - gotPriMax = gTrue; - } - } - break; - } - if (gotPriMin) { - if (newPriMin > xMin) { - newPriMin = xMin; - } - if (newPriMin > priMin) { - priMin = newPriMin; - } - } - if (gotPriMax) { - if (newPriMax < xMax) { - newPriMax = xMax; - } - if (newPriMax < priMax) { - priMax = newPriMax; - } - } -} - -int TextBlock::cmpXYPrimaryRot(const void *p1, const void *p2) { - TextBlock *blk1 = *(TextBlock **)p1; - TextBlock *blk2 = *(TextBlock **)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (blk1->page->primaryRot) { - case 0: - if ((cmp = blk1->xMin - blk2->xMin) == 0) { - cmp = blk1->yMin - blk2->yMin; - } - break; - case 1: - if ((cmp = blk1->yMin - blk2->yMin) == 0) { - cmp = blk2->xMax - blk1->xMax; - } - break; - case 2: - if ((cmp = blk2->xMax - blk1->xMax) == 0) { - cmp = blk2->yMin - blk1->yMin; - } - break; - case 3: - if ((cmp = blk2->yMax - blk1->yMax) == 0) { - cmp = blk1->xMax - blk2->xMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextBlock::cmpYXPrimaryRot(const void *p1, const void *p2) { - TextBlock *blk1 = *(TextBlock **)p1; - TextBlock *blk2 = *(TextBlock **)p2; - double cmp; - - cmp = 0; // make gcc happy - switch (blk1->page->primaryRot) { - case 0: - if ((cmp = blk1->yMin - blk2->yMin) == 0) { - cmp = blk1->xMin - blk2->xMin; - } - break; - case 1: - if ((cmp = blk2->xMax - blk1->xMax) == 0) { - cmp = blk1->yMin - blk2->yMin; - } - break; - case 2: - if ((cmp = blk2->yMin - blk1->yMin) == 0) { - cmp = blk2->xMax - blk1->xMax; - } - break; - case 3: - if ((cmp = blk1->xMax - blk2->xMax) == 0) { - cmp = blk2->yMax - blk1->yMax; - } - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -int TextBlock::primaryCmp(TextBlock *blk) { - double cmp; - - cmp = 0; // make gcc happy - switch (rot) { - case 0: - cmp = xMin - blk->xMin; - break; - case 1: - cmp = yMin - blk->yMin; - break; - case 2: - cmp = blk->xMax - xMax; - break; - case 3: - cmp = blk->yMax - yMax; - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -double TextBlock::secondaryDelta(TextBlock *blk) { - double delta; - - delta = 0; // make gcc happy - switch (rot) { - case 0: - delta = blk->yMin - yMax; - break; - case 1: - delta = xMin - blk->xMax; - break; - case 2: - delta = yMin - blk->yMax; - break; - case 3: - delta = blk->xMin - xMax; - break; - } - return delta; -} - -GBool TextBlock::isBelow(TextBlock *blk) { - GBool below; - - below = gFalse; // make gcc happy - switch (page->primaryRot) { - case 0: - below = xMin >= blk->priMin && xMax <= blk->priMax && - yMin > blk->yMin; - break; - case 1: - below = yMin >= blk->priMin && yMax <= blk->priMax && - xMax < blk->xMax; - break; - case 2: - below = xMin >= blk->priMin && xMax <= blk->priMax && - yMax < blk->yMax; - break; - case 3: - below = yMin >= blk->priMin && yMax <= blk->priMax && - xMin > blk->xMin; - break; - } - - return below; -} - -GBool TextBlock::isBeforeByRule1(TextBlock *blk1) { - GBool before = gFalse; - GBool overlap = gFalse; - - switch (this->page->primaryRot) { - case 0: - case 2: - overlap = ((this->ExMin <= blk1->ExMin) && - (blk1->ExMin <= this->ExMax)) || - ((blk1->ExMin <= this->ExMin) && - (this->ExMin <= blk1->ExMax)); - break; - case 1: - case 3: - overlap = ((this->EyMin <= blk1->EyMin) && - (blk1->EyMin <= this->EyMax)) || - ((blk1->EyMin <= this->EyMin) && - (this->EyMin <= blk1->EyMax)); - break; - } - switch (this->page->primaryRot) { - case 0: - before = overlap && this->EyMin < blk1->EyMin; - break; - case 1: - before = overlap && this->ExMax > blk1->ExMax; - break; - case 2: - before = overlap && this->EyMax > blk1->EyMax; - break; - case 3: - before = overlap && this->ExMin < blk1->ExMin; - break; - } - return before; -} - -GBool TextBlock::isBeforeByRule2(TextBlock *blk1) { - double cmp = 0; - int rotLR = rot; - - if (!page->primaryLR) { - rotLR = (rotLR + 2) % 4; - } - - switch (rotLR) { - case 0: - cmp = ExMax - blk1->ExMin; - break; - case 1: - cmp = EyMin - blk1->EyMax; - break; - case 2: - cmp = blk1->ExMax - ExMin; - break; - case 3: - cmp = blk1->EyMin - EyMax; - break; - } - return cmp <= 0; -} - -// Sort into reading order by performing a topological sort using the rules -// given in "High Performance Document Layout Analysis", T.M. Breuel, 2003. -// See http://pubs.iupr.org/#2003-breuel-sdiut -// Topological sort is done by depth first search, see -// http://en.wikipedia.org/wiki/Topological_sorting -int TextBlock::visitDepthFirst(TextBlock *blkList, int pos1, - TextBlock **sorted, int sortPos, - GBool* visited, - TextBlock **cache, int cacheSize) { - int pos2; - TextBlock *blk1, *blk2, *blk3; - GBool before; - - if (visited[pos1]) { - return sortPos; - } - - blk1 = this; - -#if 0 // for debugging - printf("visited: %d %.2f..%.2f %.2f..%.2f\n", - sortPos, blk1->ExMin, blk1->ExMax, blk1->EyMin, blk1->EyMax); -#endif - visited[pos1] = gTrue; - pos2 = -1; - for (blk2 = blkList; blk2; blk2 = blk2->next) { - pos2++; - if (visited[pos2]) { - // skip visited nodes - continue; - } - before = gFalse; - - // is blk2 before blk1? (for table entries) - if (blk1->tableId >= 0 && blk1->tableId == blk2->tableId) { - if (page->primaryLR) { - if (blk2->xMax <= blk1->xMin && - blk2->yMin <= blk1->yMax && - blk2->yMax >= blk1->yMin) - before = gTrue; - } else { - if (blk2->xMin >= blk1->xMax && - blk2->yMin <= blk1->yMax && - blk2->yMax >= blk1->yMin) - before = gTrue; - } - - if (blk2->yMax <= blk1->yMin) - before = gTrue; - } else { - if (blk2->isBeforeByRule1(blk1)) { - // Rule (1) blk1 and blk2 overlap, and blk2 is above blk1. - before = gTrue; -#if 0 // for debugging - printf("rule1: %.2f..%.2f %.2f..%.2f %.2f..%.2f %.2f..%.2f\n", - blk2->ExMin, blk2->ExMax, blk2->EyMin, blk2->EyMax, - blk1->ExMin, blk1->ExMax, blk1->EyMin, blk1->EyMax); -#endif - } else if (blk2->isBeforeByRule2(blk1)) { - // Rule (2) blk2 left of blk1, and no intervening blk3 - // such that blk1 is before blk3 by rule 1, - // and blk3 is before blk2 by rule 1. - before = gTrue; - for (int i = 0; i < cacheSize && cache[i]; ++i) { - if (blk1->isBeforeByRule1(cache[i]) && - cache[i]->isBeforeByRule1(blk2)) { - before = gFalse; - std::rotate(cache, cache + i, cache + i + 1); - break; - } - } - - if (before) { - for (blk3 = blkList; blk3; blk3 = blk3->next) { - if (blk3 == blk2 || blk3 == blk1) { - continue; - } - if (blk1->isBeforeByRule1(blk3) && - blk3->isBeforeByRule1(blk2)) { - before = gFalse; - std::copy_backward(cache, cache + cacheSize - 1, - cache + cacheSize); - cache[0] = blk3; - break; - } - } - } -#if 0 // for debugging - if (before) { - printf("rule2: %.2f..%.2f %.2f..%.2f %.2f..%.2f %.2f..%.2f\n", - blk1->ExMin, blk1->ExMax, blk1->EyMin, blk1->EyMax, - blk2->ExMin, blk2->ExMax, blk2->EyMin, blk2->EyMax); - } -#endif - } - } - if (before) { - // blk2 is before blk1, so it needs to be visited - // before we can add blk1 to the sorted list. - sortPos = blk2->visitDepthFirst(blkList, pos2, sorted, sortPos, visited, cache, cacheSize); - } - } -#if 0 // for debugging - printf("sorted: %d %.2f..%.2f %.2f..%.2f\n", - sortPos, blk1->ExMin, blk1->ExMax, blk1->EyMin, blk1->EyMax); -#endif - sorted[sortPos++] = blk1; - return sortPos; -} - -int TextBlock::visitDepthFirst(TextBlock *blkList, int pos1, - TextBlock **sorted, int sortPos, - GBool* visited) { - const int blockCacheSize = 4; - TextBlock *blockCache[blockCacheSize]; - std::fill(blockCache, blockCache + blockCacheSize, (TextBlock*)nullptr); - return visitDepthFirst(blkList, pos1, sorted, sortPos, visited, blockCache, - blockCacheSize); -} - -//------------------------------------------------------------------------ -// TextFlow -//------------------------------------------------------------------------ - -TextFlow::TextFlow(TextPage *pageA, TextBlock *blk) { - page = pageA; - xMin = blk->xMin; - xMax = blk->xMax; - yMin = blk->yMin; - yMax = blk->yMax; - priMin = blk->priMin; - priMax = blk->priMax; - blocks = lastBlk = blk; - next = nullptr; -} - -TextFlow::~TextFlow() { - TextBlock *blk; - - while (blocks) { - blk = blocks; - blocks = blocks->next; - delete blk; - } -} - -void TextFlow::addBlock(TextBlock *blk) { - if (lastBlk) { - lastBlk->next = blk; - } else { - blocks = blk; - } - lastBlk = blk; - if (blk->xMin < xMin) { - xMin = blk->xMin; - } - if (blk->xMax > xMax) { - xMax = blk->xMax; - } - if (blk->yMin < yMin) { - yMin = blk->yMin; - } - if (blk->yMax > yMax) { - yMax = blk->yMax; - } -} - -GBool TextFlow::blockFits(TextBlock *blk, TextBlock *prevBlk) { - GBool fits; - - // lower blocks must use smaller fonts - if (blk->lines->words->fontSize > lastBlk->lines->words->fontSize) { - return gFalse; - } - - fits = gFalse; // make gcc happy - switch (page->primaryRot) { - case 0: - fits = blk->xMin >= priMin && blk->xMax <= priMax; - break; - case 1: - fits = blk->yMin >= priMin && blk->yMax <= priMax; - break; - case 2: - fits = blk->xMin >= priMin && blk->xMax <= priMax; - break; - case 3: - fits = blk->yMin >= priMin && blk->yMax <= priMax; - break; - } - return fits; -} - -#ifdef TEXTOUT_WORD_LIST - -//------------------------------------------------------------------------ -// TextWordList -//------------------------------------------------------------------------ - -TextWordList::TextWordList(TextPage *text, GBool physLayout) { - TextFlow *flow; - TextBlock *blk; - TextLine *line; - TextWord *word; - TextWord **wordArray; - int nWords, i; - - words = new GooList(); - - if (text->rawOrder) { - for (word = text->rawWords; word; word = word->next) { - words->append(word); - } - - } else if (physLayout) { - // this is inefficient, but it's also the least useful of these - // three cases - nWords = 0; - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - ++nWords; - } - } - } - } - wordArray = (TextWord **)gmallocn(nWords, sizeof(TextWord *)); - i = 0; - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - wordArray[i++] = word; - } - } - } - } - qsort(wordArray, nWords, sizeof(TextWord *), &TextWord::cmpYX); - for (i = 0; i < nWords; ++i) { - words->append(wordArray[i]); - } - gfree(wordArray); - - } else { - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - words->append(word); - } - } - } - } - } -} - -TextWordList::~TextWordList() { - delete words; -} - -int TextWordList::getLength() { - return words->getLength(); -} - -TextWord *TextWordList::get(int idx) { - if (idx < 0 || idx >= words->getLength()) { - return nullptr; - } - return (TextWord *)words->get(idx); -} - -#endif // TEXTOUT_WORD_LIST - -//------------------------------------------------------------------------ -// TextPage -//------------------------------------------------------------------------ - -TextPage::TextPage(GBool rawOrderA) { - int rot; - - refCnt = 1; - rawOrder = rawOrderA; - curWord = nullptr; - charPos = 0; - curFont = nullptr; - curFontSize = 0; - nest = 0; - nTinyChars = 0; - lastCharOverlap = gFalse; - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - pools[rot] = new TextPool(); - } - } - flows = nullptr; - blocks = nullptr; - rawWords = nullptr; - rawLastWord = nullptr; - fonts = new GooList(); - lastFindXMin = lastFindYMin = 0; - haveLastFind = gFalse; - underlines = new GooList(); - links = new GooList(); - mergeCombining = gTrue; -} - -TextPage::~TextPage() { - int rot; - - clear(); - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - delete pools[rot]; - } - } - delete fonts; - deleteGooList(underlines, TextUnderline); - deleteGooList(links, TextLink); -} - -void TextPage::incRefCnt() { - refCnt++; -} - -void TextPage::decRefCnt() { - if (--refCnt == 0) - delete this; -} - -void TextPage::startPage(GfxState *state) { - clear(); - if (state) { - pageWidth = state->getPageWidth(); - pageHeight = state->getPageHeight(); - } else { - pageWidth = pageHeight = 0; - } -} - -void TextPage::endPage() { - if (curWord) { - endWord(); - } -} - -void TextPage::clear() { - int rot; - TextFlow *flow; - TextWord *word; - - if (curWord) { - delete curWord; - curWord = nullptr; - } - if (rawOrder) { - while (rawWords) { - word = rawWords; - rawWords = rawWords->next; - delete word; - } - } else { - for (rot = 0; rot < 4; ++rot) { - delete pools[rot]; - } - while (flows) { - flow = flows; - flows = flows->next; - delete flow; - } - gfree(blocks); - } - deleteGooList(fonts, TextFontInfo); - deleteGooList(underlines, TextUnderline); - deleteGooList(links, TextLink); - - curWord = nullptr; - charPos = 0; - curFont = nullptr; - curFontSize = 0; - nest = 0; - nTinyChars = 0; - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - pools[rot] = new TextPool(); - } - } - flows = nullptr; - blocks = nullptr; - rawWords = nullptr; - rawLastWord = nullptr; - fonts = new GooList(); - underlines = new GooList(); - links = new GooList(); -} - -void TextPage::updateFont(GfxState *state) { - GfxFont *gfxFont; - double *fm; - char *name; - int code, mCode, letterCode, anyCode; - double w; - int i; - - // get the font info object - curFont = nullptr; - for (i = 0; i < fonts->getLength(); ++i) { - curFont = (TextFontInfo *)fonts->get(i); - if (curFont->matches(state)) { - break; - } - curFont = nullptr; - } - if (!curFont) { - curFont = new TextFontInfo(state); - fonts->append(curFont); - } - - // adjust the font size - gfxFont = state->getFont(); - curFontSize = state->getTransformedFontSize(); - if (gfxFont && gfxFont->getType() == fontType3) { - // This is a hack which makes it possible to deal with some Type 3 - // fonts. The problem is that it's impossible to know what the - // base coordinate system used in the font is without actually - // rendering the font. This code tries to guess by looking at the - // width of the character 'm' (which breaks if the font is a - // subset that doesn't contain 'm'). - mCode = letterCode = anyCode = -1; - for (code = 0; code < 256; ++code) { - name = ((Gfx8BitFont *)gfxFont)->getCharName(code); - int nameLen = name ? strlen(name) : 0; - GBool nameOneChar = nameLen == 1 || (nameLen > 1 && name[1] == '\0'); - if (nameOneChar && name[0] == 'm') { - mCode = code; - } - if (letterCode < 0 && nameOneChar && - ((name[0] >= 'A' && name[0] <= 'Z') || - (name[0] >= 'a' && name[0] <= 'z'))) { - letterCode = code; - } - if (anyCode < 0 && name && - ((Gfx8BitFont *)gfxFont)->getWidth(code) > 0) { - anyCode = code; - } - } - if (mCode >= 0 && - (w = ((Gfx8BitFont *)gfxFont)->getWidth(mCode)) > 0) { - // 0.6 is a generic average 'm' width -- yes, this is a hack - curFontSize *= w / 0.6; - } else if (letterCode >= 0 && - (w = ((Gfx8BitFont *)gfxFont)->getWidth(letterCode)) > 0) { - // even more of a hack: 0.5 is a generic letter width - curFontSize *= w / 0.5; - } else if (anyCode >= 0 && - (w = ((Gfx8BitFont *)gfxFont)->getWidth(anyCode)) > 0) { - // better than nothing: 0.5 is a generic character width - curFontSize *= w / 0.5; - } - fm = gfxFont->getFontMatrix(); - if (fm[0] != 0) { - curFontSize *= fabs(fm[3] / fm[0]); - } - } -} - -void TextPage::beginWord(GfxState *state) { - GfxFont *gfxFont; - double *fontm; - double m[4], m2[4]; - int rot; - - // This check is needed because Type 3 characters can contain - // text-drawing operations (when TextPage is being used via - // {X,Win}SplashOutputDev rather than TextOutputDev). - if (curWord) { - ++nest; - return; - } - - // compute the rotation - state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]); - gfxFont = state->getFont(); - if (gfxFont && gfxFont->getType() == fontType3) { - fontm = state->getFont()->getFontMatrix(); - m2[0] = fontm[0] * m[0] + fontm[1] * m[2]; - m2[1] = fontm[0] * m[1] + fontm[1] * m[3]; - m2[2] = fontm[2] * m[0] + fontm[3] * m[2]; - m2[3] = fontm[2] * m[1] + fontm[3] * m[3]; - m[0] = m2[0]; - m[1] = m2[1]; - m[2] = m2[2]; - m[3] = m2[3]; - } - if (fabs(m[0] * m[3]) > fabs(m[1] * m[2])) { - rot = (m[0] > 0 || m[3] < 0) ? 0 : 2; - } else { - rot = (m[2] > 0) ? 1 : 3; - } - - // for vertical writing mode, the lines are effectively rotated 90 - // degrees - if (gfxFont && gfxFont->getWMode()) { - rot = (rot + 1) & 3; - } - - curWord = new TextWord(state, rot, curFontSize); -} - -void TextPage::addChar(GfxState *state, double x, double y, - double dx, double dy, - CharCode c, int nBytes, Unicode *u, int uLen) { - double x1, y1, w1, h1, dx2, dy2, base, sp, delta; - GBool overlap; - int i; - int wMode; - Matrix mat; - - // subtract char and word spacing from the dx,dy values - sp = state->getCharSpace(); - if (c == (CharCode)0x20) { - sp += state->getWordSpace(); - } - state->textTransformDelta(sp * state->getHorizScaling(), 0, &dx2, &dy2); - dx -= dx2; - dy -= dy2; - state->transformDelta(dx, dy, &w1, &h1); - - // throw away chars that aren't inside the page bounds - // (and also do a sanity check on the character size) - state->transform(x, y, &x1, &y1); - if (x1 + w1 < 0 || x1 > pageWidth || - y1 + h1 < 0 || y1 > pageHeight || - x1 != x1 || y1 != y1 || // IEEE way of checking for isnan - w1 != w1 || h1 != h1) { - charPos += nBytes; - return; - } - - // check the tiny chars limit - if (fabs(w1) < 3 && fabs(h1) < 3) { - if (++nTinyChars > 50000) { - charPos += nBytes; - return; - } - } - - // break words at space character - if (uLen == 1 && UnicodeIsWhitespace(u[0])) { - charPos += nBytes; - endWord(); - return; - } else if (uLen == 1 && u[0] == (Unicode)0x0) { - // ignore null characters - charPos += nBytes; - return; - } - - state->getFontTransMat(&mat.m[0], &mat.m[1], &mat.m[2], &mat.m[3]); - mat.m[0] *= state->getHorizScaling(); - mat.m[1] *= state->getHorizScaling(); - mat.m[4] = x1; - mat.m[5] = y1; - - if (mergeCombining && curWord && uLen == 1 && - curWord->addCombining(state, curFont, curFontSize, x1, y1, w1, h1, charPos, nBytes, c, - u[0], mat)) { - charPos += nBytes; - return; - } - - // start a new word if: - // (1) this character doesn't fall in the right place relative to - // the end of the previous word (this places upper and lower - // constraints on the position deltas along both the primary - // and secondary axes), or - // (2) this character overlaps the previous one (duplicated text), or - // (3) the previous character was an overlap (we want each duplicated - // character to be in a word by itself at this stage), - // (4) the font size has changed - // (5) the WMode changed - if (curWord && curWord->len > 0) { - base = sp = delta = 0; // make gcc happy - switch (curWord->rot) { - case 0: - base = y1; - sp = x1 - curWord->xMax; - delta = x1 - curWord->edge[curWord->len - 1]; - break; - case 1: - base = x1; - sp = y1 - curWord->yMax; - delta = y1 - curWord->edge[curWord->len - 1]; - break; - case 2: - base = y1; - sp = curWord->xMin - x1; - delta = curWord->edge[curWord->len - 1] - x1; - break; - case 3: - base = x1; - sp = curWord->yMin - y1; - delta = curWord->edge[curWord->len - 1] - y1; - break; - } - overlap = fabs(delta) < dupMaxPriDelta * curWord->fontSize && - fabs(base - curWord->base) < dupMaxSecDelta * curWord->fontSize; - wMode = curFont->getWMode(); - if (overlap || lastCharOverlap || - sp < -minDupBreakOverlap * curWord->fontSize || - sp > minWordBreakSpace * curWord->fontSize || - fabs(base - curWord->base) > 0.5 || - curFontSize != curWord->fontSize || - wMode != curWord->wMode) { - endWord(); - } - lastCharOverlap = overlap; - } else { - lastCharOverlap = gFalse; - } - - if (uLen != 0) { - // start a new word if needed - if (!curWord) { - beginWord(state); - } - - // page rotation and/or transform matrices can cause text to be - // drawn in reverse order -- in this case, swap the begin/end - // coordinates and break text into individual chars - if ((curWord->rot == 0 && w1 < 0) || - (curWord->rot == 1 && h1 < 0) || - (curWord->rot == 2 && w1 > 0) || - (curWord->rot == 3 && h1 > 0)) { - endWord(); - beginWord(state); - x1 += w1; - y1 += h1; - w1 = -w1; - h1 = -h1; - } - - // add the characters to the current word - w1 /= uLen; - h1 /= uLen; - for (i = 0; i < uLen; ++i) { - curWord->addChar(state, curFont, x1 + i*w1, y1 + i*h1, w1, h1, charPos, nBytes, c, u[i], mat); - } - } - charPos += nBytes; -} - -void TextPage::incCharCount(int nChars) { - charPos += nChars; -} - -void TextPage::endWord() { - // This check is needed because Type 3 characters can contain - // text-drawing operations (when TextPage is being used via - // {X,Win}SplashOutputDev rather than TextOutputDev). - if (nest > 0) { - --nest; - return; - } - - if (curWord) { - addWord(curWord); - curWord = nullptr; - } -} - -void TextPage::addWord(TextWord *word) { - // throw away zero-length words -- they don't have valid xMin/xMax - // values, and they're useless anyway - if (word->len == 0) { - delete word; - return; - } - - if (rawOrder) { - if (rawLastWord) { - rawLastWord->next = word; - } else { - rawWords = word; - } - rawLastWord = word; - } else { - pools[word->rot]->addWord(word); - } -} - -void TextPage::addUnderline(double x0, double y0, double x1, double y1) { - underlines->append(new TextUnderline(x0, y0, x1, y1)); -} - -void TextPage::addLink(int xMin, int yMin, int xMax, int yMax, AnnotLink *link) { - links->append(new TextLink(xMin, yMin, xMax, yMax, link)); -} - -void TextPage::coalesce(GBool physLayout, double fixedPitch, GBool doHTML) { - UnicodeMap *uMap; - TextPool *pool; - TextWord *word0, *word1, *word2; - TextLine *line; - TextBlock *blkList, *blk, *lastBlk, *blk0, *blk1, *blk2; - TextFlow *flow, *lastFlow; - TextUnderline *underline; - TextLink *link; - int rot, poolMinBaseIdx, baseIdx, startBaseIdx, endBaseIdx; - double minBase, maxBase, newMinBase, newMaxBase; - double fontSize, colSpace1, colSpace2, lineSpace, intraLineSpace, blkSpace; - GBool found; - int count[4]; - int lrCount; - int col1, col2; - int i, j, n; - - if (rawOrder) { - primaryRot = 0; - primaryLR = gTrue; - return; - } - - uMap = globalParams->getTextEncoding(); - blkList = nullptr; - lastBlk = nullptr; - nBlocks = 0; - primaryRot = 0; - -#if 0 // for debugging - printf("*** initial words ***\n"); - for (rot = 0; rot < 4; ++rot) { - pool = pools[rot]; - for (baseIdx = pool->minBaseIdx; baseIdx <= pool->maxBaseIdx; ++baseIdx) { - for (word0 = pool->getPool(baseIdx); word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f rot=%d link=%p '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, rot*90, word0->link); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); - } - printf("'\n"); - } - } - } - printf("\n"); -#endif - -#if 0 //~ for debugging - for (i = 0; i < underlines->getLength(); ++i) { - underline = (TextUnderline *)underlines->get(i); - printf("underline: x=%g..%g y=%g..%g horiz=%d\n", - underline->x0, underline->x1, underline->y0, underline->y1, - underline->horiz); - } -#endif - - if (doHTML) { - - //----- handle underlining - for (i = 0; i < underlines->getLength(); ++i) { - underline = (TextUnderline *)underlines->get(i); - if (underline->horiz) { - // rot = 0 - if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) { - startBaseIdx = pools[0]->getBaseIdx(underline->y0 + minUnderlineGap); - endBaseIdx = pools[0]->getBaseIdx(underline->y0 + maxUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) { - //~ need to check the y value against the word baseline - if (underline->x0 < word0->xMin + underlineSlack && - word0->xMax - underlineSlack < underline->x1) { - word0->underlined = gTrue; - } - } - } - } - - // rot = 2 - if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) { - startBaseIdx = pools[2]->getBaseIdx(underline->y0 - maxUnderlineGap); - endBaseIdx = pools[2]->getBaseIdx(underline->y0 - minUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) { - if (underline->x0 < word0->xMin + underlineSlack && - word0->xMax - underlineSlack < underline->x1) { - word0->underlined = gTrue; - } - } - } - } - } else { - // rot = 1 - if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) { - startBaseIdx = pools[1]->getBaseIdx(underline->x0 - maxUnderlineGap); - endBaseIdx = pools[1]->getBaseIdx(underline->x0 - minUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) { - if (underline->y0 < word0->yMin + underlineSlack && - word0->yMax - underlineSlack < underline->y1) { - word0->underlined = gTrue; - } - } - } - } - - // rot = 3 - if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) { - startBaseIdx = pools[3]->getBaseIdx(underline->x0 + minUnderlineGap); - endBaseIdx = pools[3]->getBaseIdx(underline->x0 + maxUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) { - if (underline->y0 < word0->yMin + underlineSlack && - word0->yMax - underlineSlack < underline->y1) { - word0->underlined = gTrue; - } - } - } - } - } - } - - //----- handle links - for (i = 0; i < links->getLength(); ++i) { - link = (TextLink *)links->get(i); - - // rot = 0 - if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) { - startBaseIdx = pools[0]->getBaseIdx(link->yMin); - endBaseIdx = pools[0]->getBaseIdx(link->yMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) { - if (link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax && - link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax) { - word0->link = link->link; - } - } - } - } - - // rot = 2 - if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) { - startBaseIdx = pools[2]->getBaseIdx(link->yMin); - endBaseIdx = pools[2]->getBaseIdx(link->yMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) { - if (link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax && - link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax) { - word0->link = link->link; - } - } - } - } - - // rot = 1 - if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) { - startBaseIdx = pools[1]->getBaseIdx(link->xMin); - endBaseIdx = pools[1]->getBaseIdx(link->xMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) { - if (link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax && - link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax) { - word0->link = link->link; - } - } - } - } - - // rot = 3 - if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) { - startBaseIdx = pools[3]->getBaseIdx(link->xMin); - endBaseIdx = pools[3]->getBaseIdx(link->xMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) { - if (link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax && - link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax) { - word0->link = link->link; - } - } - } - } - } - } - - //----- assemble the blocks - - //~ add an outer loop for writing mode (vertical text) - - // build blocks for each rotation value - for (rot = 0; rot < 4; ++rot) { - pool = pools[rot]; - poolMinBaseIdx = pool->minBaseIdx; - count[rot] = 0; - - // add blocks until no more words are left - while (1) { - - // find the first non-empty line in the pool - for (; - poolMinBaseIdx <= pool->maxBaseIdx && - !pool->getPool(poolMinBaseIdx); - ++poolMinBaseIdx) ; - if (poolMinBaseIdx > pool->maxBaseIdx) { - break; - } - - // look for the left-most word in the first four lines of the - // pool -- this avoids starting with a superscript word - startBaseIdx = poolMinBaseIdx; - for (baseIdx = poolMinBaseIdx + 1; - baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx; - ++baseIdx) { - if (!pool->getPool(baseIdx)) { - continue; - } - if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx)) - < 0) { - startBaseIdx = baseIdx; - } - } - - // create a new block - word0 = pool->getPool(startBaseIdx); - pool->setPool(startBaseIdx, word0->next); - word0->next = nullptr; - blk = new TextBlock(this, rot); - blk->addWord(word0); - - fontSize = word0->fontSize; - minBase = maxBase = word0->base; - colSpace1 = minColSpacing1 * fontSize; - colSpace2 = minColSpacing2 * fontSize; - lineSpace = maxLineSpacingDelta * fontSize; - intraLineSpace = maxIntraLineDelta * fontSize; - - // add words to the block - do { - found = gFalse; - - // look for words on the line above the current top edge of - // the block - newMinBase = minBase; - for (baseIdx = pool->getBaseIdx(minBase); - baseIdx >= pool->getBaseIdx(minBase - lineSpace); - --baseIdx) { - word0 = nullptr; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base < minBase && - word1->base >= minBase - lineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin) - : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta1 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = nullptr; - blk->addWord(word2); - found = gTrue; - newMinBase = word2->base; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - minBase = newMinBase; - - // look for words on the line below the current bottom edge of - // the block - newMaxBase = maxBase; - for (baseIdx = pool->getBaseIdx(maxBase); - baseIdx <= pool->getBaseIdx(maxBase + lineSpace); - ++baseIdx) { - word0 = nullptr; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base > maxBase && - word1->base <= maxBase + lineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin) - : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta1 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = nullptr; - blk->addWord(word2); - found = gTrue; - newMaxBase = word2->base; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - maxBase = newMaxBase; - - // look for words that are on lines already in the block, and - // that overlap the block horizontally - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = nullptr; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax + colSpace1 && - word1->xMax > blk->xMin - colSpace1) - : (word1->yMin < blk->yMax + colSpace1 && - word1->yMax > blk->yMin - colSpace1)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta2 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = nullptr; - blk->addWord(word2); - found = gTrue; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - - // only check for outlying words (the next two chunks of code) - // if we didn't find anything else - if (found) { - continue; - } - - // scan down the left side of the block, looking for words - // that are near (but not overlapping) the block; if there are - // three or fewer, add them to the block - n = 0; - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMax <= blk->xMin && - word1->xMax > blk->xMin - colSpace2) - : (word1->yMax <= blk->yMin && - word1->yMax > blk->yMin - colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - ++n; - break; - } - word1 = word1->next; - } - } - if (n > 0 && n <= 3) { - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = nullptr; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMax <= blk->xMin && - word1->xMax > blk->xMin - colSpace2) - : (word1->yMax <= blk->yMin && - word1->yMax > blk->yMin - colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = nullptr; - blk->addWord(word2); - if (word2->base < minBase) { - minBase = word2->base; - } else if (word2->base > maxBase) { - maxBase = word2->base; - } - found = gTrue; - break; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - } - - // scan down the right side of the block, looking for words - // that are near (but not overlapping) the block; if there are - // three or fewer, add them to the block - n = 0; - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin >= blk->xMax && - word1->xMin < blk->xMax + colSpace2) - : (word1->yMin >= blk->yMax && - word1->yMin < blk->yMax + colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - ++n; - break; - } - word1 = word1->next; - } - } - if (n > 0 && n <= 3) { - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = nullptr; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin >= blk->xMax && - word1->xMin < blk->xMax + colSpace2) - : (word1->yMin >= blk->yMax && - word1->yMin < blk->yMax + colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = nullptr; - blk->addWord(word2); - if (word2->base < minBase) { - minBase = word2->base; - } else if (word2->base > maxBase) { - maxBase = word2->base; - } - found = gTrue; - break; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - } - - } while (found); - - //~ need to compute the primary writing mode (horiz/vert) in - //~ addition to primary rotation - - // coalesce the block, and add it to the list - blk->coalesce(uMap, fixedPitch); - if (lastBlk) { - lastBlk->next = blk; - } else { - blkList = blk; - } - lastBlk = blk; - count[rot] += blk->charCount; - ++nBlocks; - } - - if (count[rot] > count[primaryRot]) { - primaryRot = rot; - } - } - -#if 0 // for debugging - printf("*** rotation ***\n"); - for (rot = 0; rot < 4; ++rot) { - printf(" %d: %6d\n", rot, count[rot]); - } - printf(" primary rot = %d\n", primaryRot); - printf("\n"); -#endif - -#if 0 // for debugging - printf("*** blocks ***\n"); - for (blk = blkList; blk; blk = blk->next) { - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax); - for (line = blk->lines; line; line = line->next) { - printf(" line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f\n", - line->xMin, line->xMax, line->yMin, line->yMax, line->base); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); - } - printf("'\n"); - } - } - } - printf("\n"); -#endif - - // determine the primary direction - lrCount = 0; - for (blk = blkList; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word0 = line->words; word0; word0 = word0->next) { - for (i = 0; i < word0->len; ++i) { - if (unicodeTypeL(word0->text[i])) { - ++lrCount; - } else if (unicodeTypeR(word0->text[i])) { - --lrCount; - } - } - } - } - } - primaryLR = lrCount >= 0; - -#if 0 // for debugging - printf("*** direction ***\n"); - printf("lrCount = %d\n", lrCount); - printf("primaryLR = %d\n", primaryLR); -#endif - - //----- column assignment - - // sort blocks into xy order for column assignment - if (blocks) - gfree (blocks); - if (physLayout && fixedPitch) { - - blocks = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - for (blk = blkList, i = 0; blk; blk = blk->next, ++i) { - blocks[i] = blk; - col1 = 0; // make gcc happy - switch (primaryRot) { - case 0: - col1 = (int)(blk->xMin / fixedPitch + 0.5); - break; - case 1: - col1 = (int)(blk->yMin / fixedPitch + 0.5); - break; - case 2: - col1 = (int)((pageWidth - blk->xMax) / fixedPitch + 0.5); - break; - case 3: - col1 = (int)((pageHeight - blk->yMax) / fixedPitch + 0.5); - break; - } - blk->col = col1; - for (line = blk->lines; line; line = line->next) { - for (j = 0; j <= line->len; ++j) { - line->col[j] += col1; - } - } - } - - } else { - - // sort blocks into xy order for column assignment - blocks = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - for (blk = blkList, i = 0; blk; blk = blk->next, ++i) { - blocks[i] = blk; - } - if (blocks) { - qsort(blocks, nBlocks, sizeof(TextBlock *), &TextBlock::cmpXYPrimaryRot); - } - - // column assignment - for (i = 0; i < nBlocks; ++i) { - blk0 = blocks[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - blk1 = blocks[j]; - col2 = 0; // make gcc happy - switch (primaryRot) { - case 0: - if (blk0->xMin > blk1->xMax) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->xMax == blk1->xMin) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->xMin - blk1->xMin) / - (blk1->xMax - blk1->xMin)) * - blk1->nColumns); - } - break; - case 1: - if (blk0->yMin > blk1->yMax) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->yMax == blk1->yMin) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->yMin - blk1->yMin) / - (blk1->yMax - blk1->yMin)) * - blk1->nColumns); - } - break; - case 2: - if (blk0->xMax < blk1->xMin) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->xMin == blk1->xMax) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->xMax - blk1->xMax) / - (blk1->xMin - blk1->xMax)) * - blk1->nColumns); - } - break; - case 3: - if (blk0->yMax < blk1->yMin) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->yMin == blk1->yMax) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->yMax - blk1->yMax) / - (blk1->yMin - blk1->yMax)) * - blk1->nColumns); - } - break; - } - if (col2 > col1) { - col1 = col2; - } - } - blk0->col = col1; - for (line = blk0->lines; line; line = line->next) { - for (j = 0; j <= line->len; ++j) { - line->col[j] += col1; - } - } - } - - } - -#if 0 // for debugging - printf("*** blocks, after column assignment ***\n"); - for (blk = blkList; blk; blk = blk->next) { - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f col=%d nCols=%d\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->col, - blk->nColumns); - for (line = blk->lines; line; line = line->next) { - printf(" line: col[0]=%d\n", line->col[0]); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); - } - printf("'\n"); - } - } - } - printf("\n"); -#endif - - //----- reading order sort - - // compute space on left and right sides of each block - for (i = 0; i < nBlocks; ++i) { - blk0 = blocks[i]; - for (j = 0; j < nBlocks; ++j) { - blk1 = blocks[j]; - if (blk1 != blk0) { - blk0->updatePriMinMax(blk1); - } - } - } - -#if 0 // for debugging - printf("PAGE\n"); -#endif - - int sortPos = 0; - GBool *visited = (GBool *)gmallocn(nBlocks, sizeof(GBool)); - for (i = 0; i < nBlocks; i++) { - visited[i] = gFalse; - } - - double bxMin0, byMin0, bxMin1, byMin1; - int numTables = 0; - int tableId = -1; - int correspondenceX, correspondenceY; - double xCentre1, yCentre1, xCentre2, yCentre2; - double xCentre3, yCentre3, xCentre4, yCentre4; - double deltaX, deltaY; - TextBlock *fblk2 = nullptr, *fblk3 = nullptr, *fblk4 = nullptr; - - for (blk1 = blkList; blk1; blk1 = blk1->next) { - blk1->ExMin = blk1->xMin; - blk1->ExMax = blk1->xMax; - blk1->EyMin = blk1->yMin; - blk1->EyMax = blk1->yMax; - - bxMin0 = DBL_MAX; - byMin0 = DBL_MAX; - bxMin1 = DBL_MAX; - byMin1 = DBL_MAX; - - fblk2 = nullptr; - fblk3 = nullptr; - fblk4 = nullptr; - - /* find fblk2, fblk3 and fblk4 so that - * fblk2 is on the right of blk1 and overlap with blk1 in y axis - * fblk3 is under blk1 and overlap with blk1 in x axis - * fblk4 is under blk1 and on the right of blk1 - * and they are closest to blk1 - */ - for (blk2 = blkList; blk2; blk2 = blk2->next) { - if (blk2 != blk1) { - if (blk2->yMin <= blk1->yMax && - blk2->yMax >= blk1->yMin && - blk2->xMin > blk1->xMax && - blk2->xMin < bxMin0) { - bxMin0 = blk2->xMin; - fblk2 = blk2; - } else if (blk2->xMin <= blk1->xMax && - blk2->xMax >= blk1->xMin && - blk2->yMin > blk1->yMax && - blk2->yMin < byMin0) { - byMin0 = blk2->yMin; - fblk3 = blk2; - } else if (blk2->xMin > blk1->xMax && - blk2->xMin < bxMin1 && - blk2->yMin > blk1->yMax && - blk2->yMin < byMin1) { - bxMin1 = blk2->xMin; - byMin1 = blk2->yMin; - fblk4 = blk2; - } - } - } - - /* fblk4 can not overlap with fblk3 in x and with fblk2 in y - * fblk2 can not overlap with fblk3 in x and y - * fblk4 has to overlap with fblk3 in y and with fblk2 in x - */ - if (fblk2 != nullptr && - fblk3 != nullptr && - fblk4 != nullptr) { - if (((fblk3->xMin <= fblk4->xMax && fblk3->xMax >= fblk4->xMin) || - (fblk2->yMin <= fblk4->yMax && fblk2->yMax >= fblk4->yMin) || - (fblk2->xMin <= fblk3->xMax && fblk2->xMax >= fblk3->xMin) || - (fblk2->yMin <= fblk3->yMax && fblk2->yMax >= fblk3->yMin)) || - !(fblk4->xMin <= fblk2->xMax && fblk4->xMax >= fblk2->xMin && - fblk4->yMin <= fblk3->yMax && fblk4->yMax >= fblk3->yMin)) { - fblk2 = nullptr; - fblk3 = nullptr; - fblk4 = nullptr; - } - } - - // if we found any then look whether they form a table - if (fblk2 != nullptr && - fblk3 != nullptr && - fblk4 != nullptr) { - tableId = -1; - correspondenceX = 0; - correspondenceY = 0; - deltaX = 0.0; - deltaY = 0.0; - - if (blk1->lines && blk1->lines->words) - deltaX = blk1->lines->words->getFontSize(); - if (fblk2->lines && fblk2->lines->words) - deltaX = deltaX < fblk2->lines->words->getFontSize() ? - deltaX : fblk2->lines->words->getFontSize(); - if (fblk3->lines && fblk3->lines->words) - deltaX = deltaX < fblk3->lines->words->getFontSize() ? - deltaX : fblk3->lines->words->getFontSize(); - if (fblk4->lines && fblk4->lines->words) - deltaX = deltaX < fblk4->lines->words->getFontSize() ? - deltaX : fblk4->lines->words->getFontSize(); - - deltaY = deltaX; - - deltaX *= minColSpacing1; - deltaY *= maxIntraLineDelta; - - xCentre1 = (blk1->xMax + blk1->xMin) / 2.0; - yCentre1 = (blk1->yMax + blk1->yMin) / 2.0; - xCentre2 = (fblk2->xMax + fblk2->xMin) / 2.0; - yCentre2 = (fblk2->yMax + fblk2->yMin) / 2.0; - xCentre3 = (fblk3->xMax + fblk3->xMin) / 2.0; - yCentre3 = (fblk3->yMax + fblk3->yMin) / 2.0; - xCentre4 = (fblk4->xMax + fblk4->xMin) / 2.0; - yCentre4 = (fblk4->yMax + fblk4->yMin) / 2.0; - - // are blocks centrally aligned in x ? - if (fabs (xCentre1 - xCentre3) <= deltaX && - fabs (xCentre2 - xCentre4) <= deltaX) - correspondenceX++; - - // are blocks centrally aligned in y ? - if (fabs (yCentre1 - yCentre2) <= deltaY && - fabs (yCentre3 - yCentre4) <= deltaY) - correspondenceY++; - - // are blocks aligned to the left ? - if (fabs (blk1->xMin - fblk3->xMin) <= deltaX && - fabs (fblk2->xMin - fblk4->xMin) <= deltaX) - correspondenceX++; - - // are blocks aligned to the right ? - if (fabs (blk1->xMax - fblk3->xMax) <= deltaX && - fabs (fblk2->xMax - fblk4->xMax) <= deltaX) - correspondenceX++; - - // are blocks aligned to the top ? - if (fabs (blk1->yMin - fblk2->yMin) <= deltaY && - fabs (fblk3->yMin - fblk4->yMin) <= deltaY) - correspondenceY++; - - // are blocks aligned to the bottom ? - if (fabs (blk1->yMax - fblk2->yMax) <= deltaY && - fabs (fblk3->yMax - fblk4->yMax) <= deltaY) - correspondenceY++; - - // are blocks aligned in x and y ? - if (correspondenceX > 0 && - correspondenceY > 0) { - - // find maximal tableId - tableId = tableId < fblk4->tableId ? fblk4->tableId : tableId; - tableId = tableId < fblk3->tableId ? fblk3->tableId : tableId; - tableId = tableId < fblk2->tableId ? fblk2->tableId : tableId; - tableId = tableId < blk1->tableId ? blk1->tableId : tableId; - - // if the tableId is -1, then we found new table - if (tableId < 0) { - tableId = numTables; - numTables++; - } - - blk1->tableId = tableId; - fblk2->tableId = tableId; - fblk3->tableId = tableId; - fblk4->tableId = tableId; - } - } - } - - /* set extended bounding boxes of all table entries - * so that they contain whole table - * (we need to process whole table size when comparing it - * with regular text blocks) - */ - PDFRectangle *envelopes = new PDFRectangle [numTables]; - TextBlock **ending_blocks = new TextBlock* [numTables]; - - for (i = 0; i < numTables; i++) { - envelopes[i].x1 = DBL_MAX; - envelopes[i].x2 = DBL_MIN; - envelopes[i].y1 = DBL_MAX; - envelopes[i].y2 = DBL_MIN; - } - - for (blk1 = blkList; blk1; blk1 = blk1->next) { - if (blk1->tableId >= 0) { - if (blk1->ExMin < envelopes[blk1->tableId].x1) { - envelopes[blk1->tableId].x1 = blk1->ExMin; - if (!blk1->page->primaryLR) - ending_blocks[blk1->tableId] = blk1; - } - - if (blk1->ExMax > envelopes[blk1->tableId].x2) { - envelopes[blk1->tableId].x2 = blk1->ExMax; - if (blk1->page->primaryLR) - ending_blocks[blk1->tableId] = blk1; - } - - envelopes[blk1->tableId].y1 = blk1->EyMin < envelopes[blk1->tableId].y1 ? - blk1->EyMin : envelopes[blk1->tableId].y1; - envelopes[blk1->tableId].y2 = blk1->EyMax > envelopes[blk1->tableId].y2 ? - blk1->EyMax : envelopes[blk1->tableId].y2; - } - } - - for (blk1 = blkList; blk1; blk1 = blk1->next) { - if (blk1->tableId >= 0 && - blk1->xMin <= ending_blocks[blk1->tableId]->xMax && - blk1->xMax >= ending_blocks[blk1->tableId]->xMin) { - blk1->tableEnd = gTrue; - } - } - - for (blk1 = blkList; blk1; blk1 = blk1->next) { - if (blk1->tableId >= 0) { - blk1->ExMin = envelopes[blk1->tableId].x1; - blk1->ExMax = envelopes[blk1->tableId].x2; - blk1->EyMin = envelopes[blk1->tableId].y1; - blk1->EyMax = envelopes[blk1->tableId].y2; - } - } - delete[] envelopes; - delete[] ending_blocks; - - - /* set extended bounding boxes of all other blocks - * so that they extend in x without hitting neighbours - */ - for (blk1 = blkList; blk1; blk1 = blk1->next) { - if (!(blk1->tableId >= 0)) { - double xMax = DBL_MAX; - double xMin = DBL_MIN; - - for (blk2 = blkList; blk2; blk2 = blk2->next) { - if (blk2 == blk1) - continue; - - if (blk1->yMin <= blk2->yMax && blk1->yMax >= blk2->yMin) { - if (blk2->xMin < xMax && blk2->xMin > blk1->xMax) - xMax = blk2->xMin; - - if (blk2->xMax > xMin && blk2->xMax < blk1->xMin) - xMin = blk2->xMax; - } - } - - for (blk2 = blkList; blk2; blk2 = blk2->next) { - if (blk2 == blk1) - continue; - - if (blk2->xMax > blk1->ExMax && - blk2->xMax <= xMax && - blk2->yMin >= blk1->yMax) { - blk1->ExMax = blk2->xMax; - } - - if (blk2->xMin < blk1->ExMin && - blk2->xMin >= xMin && - blk2->yMin >= blk1->yMax) - blk1->ExMin = blk2->xMin; - } - } - } - - i = -1; - for (blk1 = blkList; blk1; blk1 = blk1->next) { - i++; - sortPos = blk1->visitDepthFirst(blkList, i, blocks, sortPos, visited); - } - if (visited) { - gfree(visited); - } - -#if 0 // for debugging - printf("*** blocks, after ro sort ***\n"); - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f space=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, - blk->priMin, blk->priMax); - for (line = blk->lines; line; line = line->next) { - printf(" line:\n"); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (j = 0; j < word0->len; ++j) { - fputc(word0->text[j] & 0xff, stdout); - } - printf("'\n"); - } - } - } - printf("\n"); - fflush(stdout); -#endif - - // build the flows - //~ this needs to be adjusted for writing mode (vertical text) - //~ this also needs to account for right-to-left column ordering - while (flows) { - flow = flows; - flows = flows->next; - delete flow; - } - flow = nullptr; - flows = lastFlow = nullptr; - // assume blocks are already in reading order, - // and construct flows accordingly. - for (i = 0; i < nBlocks; i++) { - blk = blocks[i]; - blk->next = nullptr; - if (flow) { - blk1 = blocks[i - 1]; - blkSpace = maxBlockSpacing * blk1->lines->words->fontSize; - if (blk1->secondaryDelta(blk) <= blkSpace && - blk->isBelow(blk1) && - flow->blockFits(blk, blk1)) { - flow->addBlock(blk); - continue; - } - } - flow = new TextFlow(this, blk); - if (lastFlow) { - lastFlow->next = flow; - } else { - flows = flow; - } - lastFlow = flow; - } - -#if 0 // for debugging - printf("*** flows ***\n"); - for (flow = flows; flow; flow = flow->next) { - printf("flow: x=%.2f..%.2f y=%.2f..%.2f pri:%.2f..%.2f\n", - flow->xMin, flow->xMax, flow->yMin, flow->yMax, - flow->priMin, flow->priMax); - for (blk = flow->blocks; blk; blk = blk->next) { - printf(" block: rot=%d x=%.2f..%.2f y=%.2f..%.2f pri=%.2f..%.2f\n", - blk->rot, blk->ExMin, blk->ExMax, blk->EyMin, blk->EyMax, - blk->priMin, blk->priMax); - for (line = blk->lines; line; line = line->next) { - printf(" line:\n"); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); - } - printf("'\n"); - } - } - } - } - printf("\n"); -#endif - - if (uMap) { - uMap->decRefCnt(); - } -} - -GBool TextPage::findText(Unicode *s, int len, - GBool startAtTop, GBool stopAtBottom, - GBool startAtLast, GBool stopAtLast, - GBool caseSensitive, GBool backward, - GBool wholeWord, - double *xMin, double *yMin, - double *xMax, double *yMax) { - TextBlock *blk; - TextLine *line; - Unicode *s2, *txt, *reordered; - Unicode *p; - int txtSize, m, i, j, k; - double xStart, yStart, xStop, yStop; - double xMin0, yMin0, xMax0, yMax0; - double xMin1, yMin1, xMax1, yMax1; - GBool found; - - - if (rawOrder) { - return gFalse; - } - - // handle right-to-left text - reordered = (Unicode*)gmallocn(len, sizeof(Unicode)); - reorderText(s, len, nullptr, primaryLR, nullptr, reordered); - - // normalize the search string - s2 = unicodeNormalizeNFKC(reordered, len, &len, nullptr); - - // convert the search string to uppercase - if (!caseSensitive) { - for (i = 0; i < len; ++i) { - s2[i] = unicodeToUpper(s2[i]); - } - } - - txt = nullptr; - txtSize = 0; - - xStart = yStart = xStop = yStop = 0; - if (startAtLast && haveLastFind) { - xStart = lastFindXMin; - yStart = lastFindYMin; - } else if (!startAtTop) { - xStart = *xMin; - yStart = *yMin; - } - if (stopAtLast && haveLastFind) { - xStop = lastFindXMin; - yStop = lastFindYMin; - } else if (!stopAtBottom) { - xStop = *xMax; - yStop = *yMax; - } - - found = gFalse; - xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy - xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy - - for (i = backward ? nBlocks - 1 : 0; - backward ? i >= 0 : i < nBlocks; - i += backward ? -1 : 1) { - blk = blocks[i]; - - // check: is the block above the top limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the blocks won't be sorted in the useful order) - if (!startAtTop && primaryRot == 0 && - (backward ? blk->yMin > yStart : blk->yMax < yStart)) { - continue; - } - - // check: is the block below the bottom limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the blocks won't be sorted in the useful order) - if (!stopAtBottom && primaryRot == 0 && - (backward ? blk->yMax < yStop : blk->yMin > yStop)) { - break; - } - - for (line = blk->lines; line; line = line->next) { - - // check: is the line above the top limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the lines won't be sorted in the useful order) - if (!startAtTop && primaryRot == 0 && - (backward ? line->yMin > yStart : line->yMin < yStart)) { - continue; - } - - // check: is the line below the bottom limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the lines won't be sorted in the useful order) - if (!stopAtBottom && primaryRot == 0 && - (backward ? line->yMin < yStop : line->yMin > yStop)) { - continue; - } - - if (!line->normalized) - line->normalized = unicodeNormalizeNFKC(line->text, line->len, - &line->normalized_len, - &line->normalized_idx, - true); - // convert the line to uppercase - m = line->normalized_len; - if (!caseSensitive) { - if (m > txtSize) { - txt = (Unicode *)greallocn(txt, m, sizeof(Unicode)); - txtSize = m; - } - for (k = 0; k < m; ++k) { - txt[k] = unicodeToUpper(line->normalized[k]); - } - } else { - txt = line->normalized; - } - - // search each position in this line - j = backward ? m - len : 0; - p = txt + j; - while (backward ? j >= 0 : j <= m - len) { - if (!wholeWord || - ((j == 0 || !unicodeTypeAlphaNum(txt[j - 1])) && - (j + len == m || !unicodeTypeAlphaNum(txt[j + len])))) { - - // compare the strings - for (k = 0; k < len; ++k) { - if (p[k] != s2[k]) { - break; - } - } - - // found it - if (k == len) { - // where s2 matches a subsequence of a compatibility equivalence - // decomposition, highlight the entire glyph, since we don't know - // the internal layout of subglyph components - int normStart = line->normalized_idx[j]; - int normAfterEnd = line->normalized_idx[j + len - 1] + 1; - switch (line->rot) { - case 0: - xMin1 = line->edge[normStart]; - xMax1 = line->edge[normAfterEnd]; - yMin1 = line->yMin; - yMax1 = line->yMax; - break; - case 1: - xMin1 = line->xMin; - xMax1 = line->xMax; - yMin1 = line->edge[normStart]; - yMax1 = line->edge[normAfterEnd]; - break; - case 2: - xMin1 = line->edge[normAfterEnd]; - xMax1 = line->edge[normStart]; - yMin1 = line->yMin; - yMax1 = line->yMax; - break; - case 3: - xMin1 = line->xMin; - xMax1 = line->xMax; - yMin1 = line->edge[normAfterEnd]; - yMax1 = line->edge[normStart]; - break; - } - if (backward) { - if ((startAtTop || - yMin1 < yStart || (yMin1 == yStart && xMin1 < xStart)) && - (stopAtBottom || - yMin1 > yStop || (yMin1 == yStop && xMin1 > xStop))) { - if (!found || - yMin1 > yMin0 || (yMin1 == yMin0 && xMin1 > xMin0)) { - xMin0 = xMin1; - xMax0 = xMax1; - yMin0 = yMin1; - yMax0 = yMax1; - found = gTrue; - } - } - } else { - if ((startAtTop || - yMin1 > yStart || (yMin1 == yStart && xMin1 > xStart)) && - (stopAtBottom || - yMin1 < yStop || (yMin1 == yStop && xMin1 < xStop))) { - if (!found || - yMin1 < yMin0 || (yMin1 == yMin0 && xMin1 < xMin0)) { - xMin0 = xMin1; - xMax0 = xMax1; - yMin0 = yMin1; - yMax0 = yMax1; - found = gTrue; - } - } - } - } - } - if (backward) { - --j; - --p; - } else { - ++j; - ++p; - } - } - } - } - - gfree(s2); - gfree(reordered); - if (!caseSensitive) { - gfree(txt); - } - - if (found) { - *xMin = xMin0; - *xMax = xMax0; - *yMin = yMin0; - *yMax = yMax0; - lastFindXMin = xMin0; - lastFindYMin = yMin0; - haveLastFind = gTrue; - return gTrue; - } - - return gFalse; -} - -GooString *TextPage::getText(double xMin, double yMin, - double xMax, double yMax) { - GooString *s; - UnicodeMap *uMap; - TextBlock *blk; - TextLine *line; - TextLineFrag *frags; - int nFrags, fragsSize; - TextLineFrag *frag; - char space[8], eol[16]; - int spaceLen, eolLen; - int lastRot; - double x, y, delta; - int col, idx0, idx1, i, j; - GBool multiLine, oneRot; - - s = new GooString(); - - // get the output encoding - if (!(uMap = globalParams->getTextEncoding())) { - return s; - } - - if (rawOrder) { - TextWord* word; - char mbc[16]; - int mbc_len; - - for (word = rawWords; word && word <= rawLastWord; word = word->next) { - for (j = 0; j < word->getLength(); ++j) { - double gXMin, gXMax, gYMin, gYMax; - word->getCharBBox(j, &gXMin, &gYMin, &gXMax, &gYMax); - if (xMin <= gXMin && gXMax <= xMax && yMin <= gYMin && gYMax <= yMax) - { - mbc_len = uMap->mapUnicode( *(word->getChar(j)), mbc, sizeof(mbc) ); - s->append(mbc, mbc_len); - } - } - } - return s; - } - - spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); - eolLen = 0; // make gcc happy - switch (globalParams->getTextEOL()) { - case eolUnix: - eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol)); - break; - case eolDOS: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen); - break; - case eolMac: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - break; - } - - //~ writing mode (horiz/vert) - - // collect the line fragments that are in the rectangle - fragsSize = 256; - frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag)); - nFrags = 0; - lastRot = -1; - oneRot = gTrue; - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - if (xMin < blk->xMax && blk->xMin < xMax && - yMin < blk->yMax && blk->yMin < yMax) { - for (line = blk->lines; line; line = line->next) { - if (xMin < line->xMax && line->xMin < xMax && - yMin < line->yMax && line->yMin < yMax) { - idx0 = idx1 = -1; - switch (line->rot) { - case 0: - y = 0.5 * (line->yMin + line->yMax); - if (yMin < y && y < yMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) { - idx1 = j; - break; - } - --j; - } - } - break; - case 1: - x = 0.5 * (line->xMin + line->xMax); - if (xMin < x && x < xMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) { - idx1 = j; - break; - } - --j; - } - } - break; - case 2: - y = 0.5 * (line->yMin + line->yMax); - if (yMin < y && y < yMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) { - idx1 = j; - break; - } - --j; - } - } - break; - case 3: - x = 0.5 * (line->xMin + line->xMax); - if (xMin < x && x < xMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) { - idx1 = j; - break; - } - --j; - } - } - break; - } - if (idx0 >= 0 && idx1 >= 0) { - if (nFrags == fragsSize) { - fragsSize *= 2; - frags = (TextLineFrag *) - greallocn(frags, fragsSize, sizeof(TextLineFrag)); - } - frags[nFrags].init(line, idx0, idx1 - idx0 + 1); - ++nFrags; - if (lastRot >= 0 && line->rot != lastRot) { - oneRot = gFalse; - } - lastRot = line->rot; - } - } - } - } - } - - // sort the fragments and generate the string - if (nFrags > 0) { - - for (i = 0; i < nFrags; ++i) { - frags[i].computeCoords(oneRot); - } - assignColumns(frags, nFrags, oneRot); - - // if all lines in the region have the same rotation, use it; - // otherwise, use the page's primary rotation - if (oneRot) { - qsort(frags, nFrags, sizeof(TextLineFrag), - &TextLineFrag::cmpYXLineRot); - } else { - qsort(frags, nFrags, sizeof(TextLineFrag), - &TextLineFrag::cmpYXPrimaryRot); - } - i = 0; - while (i < nFrags) { - delta = maxIntraLineDelta * frags[i].line->words->fontSize; - for (j = i+1; - j < nFrags && fabs(frags[j].base - frags[i].base) < delta; - ++j) ; - qsort(frags + i, j - i, sizeof(TextLineFrag), - oneRot ? &TextLineFrag::cmpXYColumnLineRot - : &TextLineFrag::cmpXYColumnPrimaryRot); - i = j; - } - - col = 0; - multiLine = gFalse; - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - - // insert a return - if (frag->col < col || - (i > 0 && fabs(frag->base - frags[i-1].base) > - maxIntraLineDelta * frags[i-1].line->words->fontSize)) { - s->append(eol, eolLen); - col = 0; - multiLine = gTrue; - } - - // column alignment - for (; col < frag->col; ++col) { - s->append(space, spaceLen); - } - - // get the fragment text - col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s); - } - - if (multiLine) { - s->append(eol, eolLen); - } - } - - gfree(frags); - uMap->decRefCnt(); - - return s; -} - -class TextSelectionVisitor { -public: - TextSelectionVisitor (TextPage *page); - virtual ~TextSelectionVisitor () { } - TextSelectionVisitor(const TextSelectionVisitor &) = delete; - TextSelectionVisitor& operator=(const TextSelectionVisitor &) = delete; - virtual void visitBlock (TextBlock *block, - TextLine *begin, - TextLine *end, - PDFRectangle *selection) = 0; - virtual void visitLine (TextLine *line, - TextWord *begin, - TextWord *end, - int edge_begin, - int edge_end, - PDFRectangle *selection) = 0; - virtual void visitWord (TextWord *word, int begin, int end, - PDFRectangle *selection) = 0; - -protected: - TextPage *page; -}; - -TextSelectionVisitor::TextSelectionVisitor (TextPage *page) - : page(page) -{ -} - - -class TextSelectionDumper : public TextSelectionVisitor { -public: - TextSelectionDumper(TextPage *page); - ~TextSelectionDumper(); - - void visitBlock (TextBlock *block, - TextLine *begin, - TextLine *end, - PDFRectangle *selection) override { }; - void visitLine (TextLine *line, - TextWord *begin, - TextWord *end, - int edge_begin, - int edge_end, - PDFRectangle *selection) override; - void visitWord (TextWord *word, int begin, int end, - PDFRectangle *selection) override; - void endPage(); - - GooString *getText(void); - GooList **takeWordList(int *nLines); - -private: - - void startLine(); - void finishLine(); - - GooList **lines; - int nLines, linesSize; - GooList *words; - int tableId; - TextBlock *currentBlock; -}; - -TextSelectionDumper::TextSelectionDumper(TextPage *page) - : TextSelectionVisitor(page) -{ - linesSize = 256; - lines = (GooList **)gmallocn(linesSize, sizeof(GooList *)); - nLines = 0; - - tableId = -1; - currentBlock = nullptr; - words = nullptr; -} - -TextSelectionDumper::~TextSelectionDumper() -{ - for (int i = 0; i < nLines; i++) - deleteGooList(lines[i], TextWordSelection); - gfree(lines); -} - -void TextSelectionDumper::startLine() -{ - finishLine(); - words = new GooList(); -} - -void TextSelectionDumper::finishLine() -{ - if (nLines == linesSize) { - linesSize *= 2; - lines = (GooList **)grealloc(lines, linesSize * sizeof(GooList *)); - } - - if (words && words->getLength() > 0) - lines[nLines++] = words; - else if (words) - delete words; - words = nullptr; -} - -void TextSelectionDumper::visitLine (TextLine *line, - TextWord *begin, - TextWord *end, - int edge_begin, - int edge_end, - PDFRectangle *selection) -{ - TextLineFrag frag; - - frag.init(line, edge_begin, edge_end - edge_begin); - - if (tableId >= 0 && frag.line->blk->tableId < 0) { - finishLine(); - - tableId = -1; - currentBlock = nullptr; - } - - if (frag.line->blk->tableId >= 0) { // a table - if (tableId == -1) { - tableId = frag.line->blk->tableId; - currentBlock = frag.line->blk; - } - - if (currentBlock == frag.line->blk) { // the same block - startLine(); - } else { // another block - if (currentBlock->tableEnd) { // previous block ended its row - startLine(); - } - currentBlock = frag.line->blk; - } - } else { // not a table - startLine(); - } -} - -void TextSelectionDumper::visitWord (TextWord *word, int begin, int end, - PDFRectangle *selection) -{ - words->append(new TextWordSelection(word, begin, end)); -} - -void TextSelectionDumper::endPage() -{ - finishLine(); -} - -GooString *TextSelectionDumper::getText (void) -{ - GooString *text; - int i, j; - UnicodeMap *uMap; - char space[8], eol[16]; - int spaceLen, eolLen; - - text = new GooString(); - - if (!(uMap = globalParams->getTextEncoding())) - return text; - - spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); - eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol)); - - for (i = 0; i < nLines; i++) { - GooList *lineWords = lines[i]; - for (j = 0; j < lineWords->getLength(); j++) { - TextWordSelection *sel = (TextWordSelection *)lineWords->get(j); - - page->dumpFragment (sel->word->text + sel->begin, sel->end - sel->begin, uMap, text); - if (j < lineWords->getLength() - 1) - text->append(space, spaceLen); - } - if (i < nLines - 1) - text->append(eol, eolLen); - } - - uMap->decRefCnt(); - - return text; -} - -GooList **TextSelectionDumper::takeWordList(int *nLinesOut) -{ - GooList **returnValue = lines; - - *nLinesOut = nLines; - if (nLines == 0) - return nullptr; - - nLines = 0; - lines = nullptr; - - return returnValue; -} - -class TextSelectionSizer : public TextSelectionVisitor { -public: - TextSelectionSizer(TextPage *page, double scale); - ~TextSelectionSizer() { } - - void visitBlock (TextBlock *block, - TextLine *begin, - TextLine *end, - PDFRectangle *selection) override { }; - void visitLine (TextLine *line, - TextWord *begin, - TextWord *end, - int edge_begin, - int edge_end, - PDFRectangle *selection) override; - void visitWord (TextWord *word, int begin, int end, - PDFRectangle *selection) override { }; - - GooList *getRegion () { return list; } - -private: - GooList *list; - double scale; -}; - -TextSelectionSizer::TextSelectionSizer(TextPage *page, double scale) - : TextSelectionVisitor(page), - scale(scale) -{ - list = new GooList(); -} - -void TextSelectionSizer::visitLine (TextLine *line, - TextWord *begin, - TextWord *end, - int edge_begin, - int edge_end, - PDFRectangle *selection) -{ - PDFRectangle *rect; - double x1, y1, x2, y2, margin; - - margin = (line->yMax - line->yMin) / 8; - x1 = line->edge[edge_begin]; - y1 = line->yMin - margin; - x2 = line->edge[edge_end]; - y2 = line->yMax + margin; - - rect = new PDFRectangle (floor (x1 * scale), - floor (y1 * scale), - ceil (x2 * scale), - ceil (y2 * scale)); - list->append (rect); -} - - -class TextSelectionPainter : public TextSelectionVisitor { -public: - TextSelectionPainter(TextPage *page, - double scale, - int rotation, - OutputDev *out, - GfxColor *box_color, - GfxColor *glyph_color); - ~TextSelectionPainter(); - - void visitBlock (TextBlock *block, - TextLine *begin, - TextLine *end, - PDFRectangle *selection) override { }; - void visitLine (TextLine *line, - TextWord *begin, - TextWord *end, - int edge_begin, - int edge_end, - PDFRectangle *selection) override; - void visitWord (TextWord *word, int begin, int end, - PDFRectangle *selection) override; - void endPage(); - -private: - OutputDev *out; - GfxColor *box_color, *glyph_color; - GfxState *state; - GooList *selectionList; - Matrix ctm, ictm; -}; - -TextSelectionPainter::TextSelectionPainter(TextPage *page, - double scale, - int rotation, - OutputDev *out, - GfxColor *box_color, - GfxColor *glyph_color) - : TextSelectionVisitor(page), - out(out), - box_color(box_color), - glyph_color(glyph_color) -{ - PDFRectangle box(0, 0, page->pageWidth, page->pageHeight); - - selectionList = new GooList(); - state = new GfxState(72 * scale, 72 * scale, &box, rotation, gFalse); - - state->getCTM(&ctm); - ctm.invertTo(&ictm); - - out->startPage(0, state, nullptr); - out->setDefaultCTM (state->getCTM()); - - state->setFillColorSpace(new GfxDeviceRGBColorSpace()); - state->setFillColor(box_color); - out->updateFillColor(state); -} - -TextSelectionPainter::~TextSelectionPainter() -{ - deleteGooList(selectionList, TextWordSelection); - delete state; -} - -void TextSelectionPainter::visitLine (TextLine *line, - TextWord *begin, - TextWord *end, - int edge_begin, - int edge_end, - PDFRectangle *selection) -{ - double x1, y1, x2, y2, margin; - - margin = (line->yMax - line->yMin) / 8; - x1 = floor (line->edge[edge_begin]); - y1 = floor (line->yMin - margin); - x2 = ceil (line->edge[edge_end]); - y2 = ceil (line->yMax + margin); - - ctm.transform(line->edge[edge_begin], line->yMin - margin, &x1, &y1); - ctm.transform(line->edge[edge_end], line->yMax + margin, &x2, &y2); - - x1 = floor (x1); - y1 = floor (y1); - x2 = ceil (x2); - y2 = ceil (y2); - - ictm.transform(x1, y1, &x1, &y1); - ictm.transform(x2, y2, &x2, &y2); - - state->moveTo(x1, y1); - state->lineTo(x2, y1); - state->lineTo(x2, y2); - state->lineTo(x1, y2); - state->closePath(); -} - -void TextSelectionPainter::visitWord (TextWord *word, int begin, int end, - PDFRectangle *selection) -{ - selectionList->append(new TextWordSelection(word, begin, end)); -} - -void TextSelectionPainter::endPage() -{ - out->fill(state); - - out->saveState(state); - out->clip(state); - - state->clearPath(); - - state->setFillColor(glyph_color); - out->updateFillColor(state); - - for (int i = 0; i < selectionList->getLength(); i++) { - TextWordSelection *sel = (TextWordSelection *) selectionList->get(i); - int begin = sel->begin; - - while (begin < sel->end) { - TextFontInfo *font = sel->word->font[begin]; - font->gfxFont->incRefCnt(); - Matrix *mat = &sel->word->textMat[begin]; - - state->setTextMat(mat->m[0], mat->m[1], mat->m[2], mat->m[3], 0, 0); - state->setFont(font->gfxFont, 1); - out->updateFont(state); - - int fEnd = begin + 1; - while (fEnd < sel->end && font->matches(sel->word->font[fEnd]) && - mat->m[0] == sel->word->textMat[fEnd].m[0] && - mat->m[1] == sel->word->textMat[fEnd].m[1] && - mat->m[2] == sel->word->textMat[fEnd].m[2] && - mat->m[3] == sel->word->textMat[fEnd].m[3]) - fEnd++; - - /* The only purpose of this string is to let the output device query - * it's length. Might want to change this interface later. */ - GooString *string = new GooString ((char *) sel->word->charcode, fEnd - begin); - out->beginString(state, string); - - for (int i = begin; i < fEnd; i++) { - if (i != begin && sel->word->charPos[i] == sel->word->charPos[i - 1]) - continue; - - out->drawChar(state, sel->word->textMat[i].m[4], sel->word->textMat[i].m[5], 0, 0, 0, 0, - sel->word->charcode[i], 1, nullptr, 0); - } - out->endString(state); - delete string; - begin = fEnd; - } - } - - out->restoreState(state); - out->endPage (); -} - -void TextWord::visitSelection(TextSelectionVisitor *visitor, - PDFRectangle *selection, - SelectionStyle style) -{ - int i, begin, end; - double mid; - - begin = len; - end = 0; - for (i = 0; i < len; i++) { - mid = (edge[i] + edge[i + 1]) / 2; - if (selection->x1 < mid || selection->x2 < mid) - if (i < begin) - begin = i; - if (mid < selection->x1 || mid < selection->x2) - end = i + 1; - } - - /* Skip empty selection. */ - if (end <= begin) - return; - - visitor->visitWord (this, begin, end, selection); -} - -void TextLine::visitSelection(TextSelectionVisitor *visitor, - PDFRectangle *selection, - SelectionStyle style) { - TextWord *p, *begin, *end, *current; - int i, edge_begin, edge_end; - PDFRectangle child_selection; - - begin = nullptr; - end = nullptr; - current = nullptr; - for (p = words; p != nullptr; p = p->next) { - if (blk->page->primaryLR) { - if ((selection->x1 < p->xMax) || - (selection->x2 < p->xMax)) - if (begin == nullptr) - begin = p; - - if (((selection->x1 > p->xMin) || - (selection->x2 > p->xMin)) && (begin != nullptr)) { - end = p->next; - current = p; - } - } else { - if ((selection->x1 > p->xMin) || - (selection->x2 > p->xMin)) - if (begin == nullptr) - begin = p; - - if (((selection->x1 < p->xMax) || - (selection->x2 < p->xMax)) && (begin != nullptr)) { - end = p->next; - current = p; - } - } - } - - if (!current) - current = begin; - - child_selection = *selection; - if (style == selectionStyleWord) { - child_selection.x1 = begin ? begin->xMin : xMin; - if (end && end->xMax != -1) { - child_selection.x2 = current->xMax; - } else { - child_selection.x2 = xMax; - } - } - - edge_begin = len; - edge_end = 0; - for (i = 0; i < len; i++) { - double mid = (edge[i] + edge[i + 1]) / 2; - if (child_selection.x1 < mid || child_selection.x2 < mid) - if (i < edge_begin) - edge_begin = i; - if (mid < child_selection.x2 || mid < child_selection.x1) - edge_end = i + 1; - } - - /* Skip empty selection. */ - if (edge_end <= edge_begin) - return; - - visitor->visitLine (this, begin, end, edge_begin, edge_end, - &child_selection); - - for (p = begin; p != end; p = p->next) - p->visitSelection (visitor, &child_selection, style); -} - -void TextBlock::visitSelection(TextSelectionVisitor *visitor, - PDFRectangle *selection, - SelectionStyle style) { - PDFRectangle child_selection; - double x[2], y[2], d, best_d[2]; - TextLine *p, *best_line[2]; - int i, count = 0, best_count[2], start, stop; - GBool all[2]; - - x[0] = selection->x1; - y[0] = selection->y1; - x[1] = selection->x2; - y[1] = selection->y2; - - for (i = 0; i < 2; i++) { - // the first/last lines are often not nearest - // the corners, so we have to force them to be - // selected when the selection runs outside this - // block. - if (page->primaryLR) { - all[i] = x[i] >= this->xMax && y[i] >= this->yMax; - if (x[i] <= this->xMin && y[i] <= this->yMin) { - best_line[i] = this->lines; - best_count[i] = 1; - } else { - best_line[i] = nullptr; - best_count[i] = 0; - } - } else { - all[i] = x[i] <= this->xMin && y[i] >= this->yMax; - if (x[i] >= this->xMax && y[i] <= this->yMin) { - best_line[i] = this->lines; - best_count[i] = 1; - } else { - best_line[i] = nullptr; - best_count[i] = 0; - } - } - best_d[i] = 0; - } - - // find the nearest line to the selection points - // using the manhattan distance. - for (p = this->lines; p; p = p->next) { - count++; - for (i = 0; i < 2; i++) { - d = fmax(p->xMin - x[i], 0.0) + - fmax(x[i] - p->xMax, 0.0) + - fmax(p->yMin - y[i], 0.0) + - fmax(y[i] - p->yMax, 0.0); - if (!best_line[i] || all[i] || - d < best_d[i]) { - best_line[i] = p; - best_count[i] = count; - best_d[i] = d; - } - } - } - // assert: best is always set. - if (!best_line[0] || !best_line[1]) { - return; - } - - // Now decide which point was first. - if (best_count[0] < best_count[1] || - (best_count[0] == best_count[1] && - y[0] < y[1])) { - start = 0; - stop = 1; - } else { - start = 1; - stop = 0; - } - - visitor->visitBlock(this, best_line[start], best_line[stop], selection); - - for (p = best_line[start]; p; p = p->next) { - if (page->primaryLR) { - child_selection.x1 = p->xMin; - child_selection.x2 = p->xMax; - } else { - child_selection.x1 = p->xMax; - child_selection.x2 = p->xMin; - } - child_selection.y1 = p->yMin; - child_selection.y2 = p->yMax; - if (style == selectionStyleLine) { - if (p == best_line[start]) { - child_selection.x1 = 0; - child_selection.y1 = 0; - } - if (p == best_line[stop]) { - child_selection.x2 = page->pageWidth; - child_selection.y2 = page->pageHeight; - } - } else { - if (p == best_line[start]) { - child_selection.x1 = fmax(p->xMin, fmin(p->xMax, x[start])); - child_selection.y1 = fmax(p->yMin, fmin(p->yMax, y[start])); - } - if (p == best_line[stop]) { - child_selection.x2 = fmax(p->xMin, fmin(p->xMax, x[stop])); - child_selection.y2 = fmax(p->yMin, fmin(p->yMax, y[stop])); - } - } - p->visitSelection(visitor, &child_selection, style); - if (p == best_line[stop]) { - return; - } - } -} - -void TextPage::visitSelection(TextSelectionVisitor *visitor, - PDFRectangle *selection, - SelectionStyle style) -{ - PDFRectangle child_selection; - double x[2], y[2], d, best_d[2]; - double xMin, yMin, xMax, yMax; - TextFlow *flow, *best_flow[2]; - TextBlock *blk, *best_block[2]; - int i, count = 0, best_count[2], start, stop; - - if (!flows) - return; - - x[0] = selection->x1; - y[0] = selection->y1; - x[1] = selection->x2; - y[1] = selection->y2; - - xMin = pageWidth; - yMin = pageHeight; - xMax = 0.0; - yMax = 0.0; - - for (i = 0; i < 2; i++) { - best_block[i] = nullptr; - best_flow[i] = nullptr; - best_count[i] = 0; - best_d[i] = 0; - } - - // find the nearest blocks to the selection points - // using the manhattan distance. - for (flow = flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - count++; - // the first/last blocks in reading order are - // often not the closest to the page corners; - // track the corners, force those blocks to - // be selected if the selection runs across - // multiple pages. - xMin = fmin(xMin, blk->xMin); - yMin = fmin(yMin, blk->yMin); - xMax = fmax(xMax, blk->xMax); - yMax = fmax(yMax, blk->yMax); - for (i = 0; i < 2; i++) { - d = fmax(blk->xMin - x[i], 0.0) + - fmax(x[i] - blk->xMax, 0.0) + - fmax(blk->yMin - y[i], 0.0) + - fmax(y[i] - blk->yMax, 0.0); - if (!best_block[i] || - d < best_d[i] || - (!blk->next && !flow->next && - x[i] >= fmin(xMax, pageWidth) && - y[i] >= fmin(yMax, pageHeight))) { - best_block[i] = blk; - best_flow[i] = flow; - best_count[i] = count; - best_d[i] = d; - } - } - } - } - for (i = 0; i < 2; i++) { - if (primaryLR) { - if (x[i] < xMin && y[i] < yMin) { - best_block[i] = flows->blocks; - best_flow[i] = flows; - best_count[i] = 1; - } - } else { - if (x[i] > xMax && y[i] < yMin) { - best_block[i] = flows->blocks; - best_flow[i] = flows; - best_count[i] = 1; - } - } - } - // assert: best is always set. - if (!best_block[0] || !best_block[1]) { - return; - } - - // Now decide which point was first. - if (best_count[0] < best_count[1] || - (best_count[0] == best_count[1] && y[0] < y[1])) { - start = 0; - stop = 1; - } else { - start = 1; - stop = 0; - } - - for (flow = best_flow[start]; flow; flow = flow->next) { - if (flow == best_flow[start]) { - blk = best_block[start]; - } else { - blk = flow->blocks; - } - for (; blk; blk = blk->next) { - if (primaryLR) { - child_selection.x1 = blk->xMin; - child_selection.x2 = blk->xMax; - } else { - child_selection.x1 = blk->xMax; - child_selection.x2 = blk->xMin; - } - child_selection.y1 = blk->yMin; - child_selection.y2 = blk->yMax; - if (blk == best_block[start]) { - child_selection.x1 = fmax(blk->xMin, fmin(blk->xMax, x[start])); - child_selection.y1 = fmax(blk->yMin, fmin(blk->yMax, y[start])); - } - if (blk == best_block[stop]) { - child_selection.x2 = fmax(blk->xMin, fmin(blk->xMax, x[stop])); - child_selection.y2 = fmax(blk->yMin, fmin(blk->yMax, y[stop])); - blk->visitSelection(visitor, &child_selection, style); - return; - } - blk->visitSelection(visitor, &child_selection, style); - } - } -} - -void TextPage::drawSelection(OutputDev *out, - double scale, - int rotation, - PDFRectangle *selection, - SelectionStyle style, - GfxColor *glyph_color, GfxColor *box_color) -{ - TextSelectionPainter painter(this, scale, rotation, - out, box_color, glyph_color); - - visitSelection(&painter, selection, style); - painter.endPage(); -} - -GooList *TextPage::getSelectionRegion(PDFRectangle *selection, - SelectionStyle style, - double scale) { - TextSelectionSizer sizer(this, scale); - - visitSelection(&sizer, selection, style); - - return sizer.getRegion(); -} - -GooString *TextPage::getSelectionText(PDFRectangle *selection, - SelectionStyle style) -{ - TextSelectionDumper dumper(this); - - visitSelection(&dumper, selection, style); - dumper.endPage(); - - return dumper.getText(); -} - -GooList **TextPage::getSelectionWords(PDFRectangle *selection, - SelectionStyle style, - int *nLines) -{ - TextSelectionDumper dumper(this); - - visitSelection(&dumper, selection, style); - dumper.endPage(); - - return dumper.takeWordList(nLines); -} - -GBool TextPage::findCharRange(int pos, int length, - double *xMin, double *yMin, - double *xMax, double *yMax) { - TextBlock *blk; - TextLine *line; - TextWord *word; - double xMin0, xMax0, yMin0, yMax0; - double xMin1, xMax1, yMin1, yMax1; - GBool first; - int i, j0, j1; - - if (rawOrder) { - return gFalse; - } - - //~ this doesn't correctly handle ranges split across multiple lines - //~ (the highlighted region is the bounding box of all the parts of - //~ the range) - first = gTrue; - xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy - xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - if (pos < word->charPos[word->len] && - pos + length > word->charPos[0]) { - for (j0 = 0; - j0 < word->len && pos >= word->charPos[j0 + 1]; - ++j0) ; - for (j1 = word->len - 1; - j1 > j0 && pos + length <= word->charPos[j1]; - --j1) ; - switch (line->rot) { - case 0: - xMin1 = word->edge[j0]; - xMax1 = word->edge[j1 + 1]; - yMin1 = word->yMin; - yMax1 = word->yMax; - break; - case 1: - xMin1 = word->xMin; - xMax1 = word->xMax; - yMin1 = word->edge[j0]; - yMax1 = word->edge[j1 + 1]; - break; - case 2: - xMin1 = word->edge[j1 + 1]; - xMax1 = word->edge[j0]; - yMin1 = word->yMin; - yMax1 = word->yMax; - break; - case 3: - xMin1 = word->xMin; - xMax1 = word->xMax; - yMin1 = word->edge[j1 + 1]; - yMax1 = word->edge[j0]; - break; - } - if (first || xMin1 < xMin0) { - xMin0 = xMin1; - } - if (first || xMax1 > xMax0) { - xMax0 = xMax1; - } - if (first || yMin1 < yMin0) { - yMin0 = yMin1; - } - if (first || yMax1 > yMax0) { - yMax0 = yMax1; - } - first = gFalse; - } - } - } - } - if (!first) { - *xMin = xMin0; - *xMax = xMax0; - *yMin = yMin0; - *yMax = yMax0; - return gTrue; - } - return gFalse; -} - -void TextPage::dump(void *outputStream, TextOutputFunc outputFunc, - GBool physLayout) { - UnicodeMap *uMap; - TextFlow *flow; - TextBlock *blk; - TextLine *line; - TextLineFrag *frags; - TextWord *word; - int nFrags, fragsSize; - TextLineFrag *frag; - char space[8], eol[16], eop[8]; - int spaceLen, eolLen, eopLen; - GBool pageBreaks; - GooString *s; - double delta; - int col, i, j, d, n; - - // get the output encoding - if (!(uMap = globalParams->getTextEncoding())) { - return; - } - spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); - eolLen = 0; // make gcc happy - switch (globalParams->getTextEOL()) { - case eolUnix: - eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol)); - break; - case eolDOS: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen); - break; - case eolMac: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - break; - } - eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop)); - pageBreaks = globalParams->getTextPageBreaks(); - - //~ writing mode (horiz/vert) - - // output the page in raw (content stream) order - if (rawOrder) { - - for (word = rawWords; word; word = word->next) { - s = new GooString(); - dumpFragment(word->text, word->len, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - if (word->next && - fabs(word->next->base - word->base) < - maxIntraLineDelta * word->fontSize && - word->next->xMin > - word->xMax - minDupBreakOverlap * word->fontSize) { - if (word->next->xMin > word->xMax + minWordSpacing * word->fontSize) { - (*outputFunc)(outputStream, space, spaceLen); - } - } else { - (*outputFunc)(outputStream, eol, eolLen); - } - } - - // output the page, maintaining the original physical layout - } else if (physLayout) { - - // collect the line fragments for the page and sort them - fragsSize = 256; - frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag)); - nFrags = 0; - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - for (line = blk->lines; line; line = line->next) { - if (nFrags == fragsSize) { - fragsSize *= 2; - frags = (TextLineFrag *)greallocn(frags, - fragsSize, sizeof(TextLineFrag)); - } - frags[nFrags].init(line, 0, line->len); - frags[nFrags].computeCoords(gTrue); - ++nFrags; - } - } - qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpYXPrimaryRot); - i = 0; - while (i < nFrags) { - delta = maxIntraLineDelta * frags[i].line->words->fontSize; - for (j = i+1; - j < nFrags && fabs(frags[j].base - frags[i].base) < delta; - ++j) ; - qsort(frags + i, j - i, sizeof(TextLineFrag), - &TextLineFrag::cmpXYColumnPrimaryRot); - i = j; - } - -#if 0 // for debugging - printf("*** line fragments ***\n"); - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - printf("frag: x=%.2f..%.2f y=%.2f..%.2f base=%.2f '", - frag->xMin, frag->xMax, frag->yMin, frag->yMax, frag->base); - for (n = 0; n < frag->len; ++n) { - fputc(frag->line->text[frag->start + n] & 0xff, stdout); - } - printf("'\n"); - } - printf("\n"); -#endif - - // generate output - col = 0; - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - - // column alignment - for (; col < frag->col; ++col) { - (*outputFunc)(outputStream, space, spaceLen); - } - - // print the line - s = new GooString(); - col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - - // print one or more returns if necessary - if (i == nFrags - 1 || - frags[i+1].col < col || - fabs(frags[i+1].base - frag->base) > - maxIntraLineDelta * frag->line->words->fontSize) { - if (i < nFrags - 1) { - d = (int)((frags[i+1].base - frag->base) / - frag->line->words->fontSize); - if (d < 1) { - d = 1; - } else if (d > 5) { - d = 5; - } - } else { - d = 1; - } - for (; d > 0; --d) { - (*outputFunc)(outputStream, eol, eolLen); - } - col = 0; - } - } - - gfree(frags); - - // output the page, "undoing" the layout - } else { - for (flow = flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - n = line->len; - if (line->hyphenated && (line->next || blk->next)) { - --n; - } - s = new GooString(); - dumpFragment(line->text, n, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - // output a newline when a hyphen is not suppressed - if (n == line->len) { - (*outputFunc)(outputStream, eol, eolLen); - } - } - } - (*outputFunc)(outputStream, eol, eolLen); - } - } - - // end of page - if (pageBreaks) { - (*outputFunc)(outputStream, eop, eopLen); - } - - uMap->decRefCnt(); -} - -void TextPage::setMergeCombining(GBool merge) { - mergeCombining = merge; -} - -void TextPage::assignColumns(TextLineFrag *frags, int nFrags, GBool oneRot) { - TextLineFrag *frag0, *frag1; - int rot, col1, col2, i, j, k; - - // all text in the region has the same rotation -- recompute the - // column numbers based only on the text in the region - if (oneRot) { - qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpXYLineRot); - rot = frags[0].line->rot; - for (i = 0; i < nFrags; ++i) { - frag0 = &frags[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - frag1 = &frags[j]; - col2 = 0; // make gcc happy - switch (rot) { - case 0: - if (frag0->xMin >= frag1->xMax) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->xMin >= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 1: - if (frag0->yMin >= frag1->yMax) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->yMin >= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 2: - if (frag0->xMax <= frag1->xMin) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->xMax <= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 3: - if (frag0->yMax <= frag1->yMin) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->yMax <= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - } - if (col2 > col1) { - col1 = col2; - } - } - frag0->col = col1; - } - - // the region includes text at different rotations -- use the - // globally assigned column numbers, offset by the minimum column - // number (i.e., shift everything over to column 0) - } else { - col1 = frags[0].col; - for (i = 1; i < nFrags; ++i) { - if (frags[i].col < col1) { - col1 = frags[i].col; - } - } - for (i = 0; i < nFrags; ++i) { - frags[i].col -= col1; - } - } -} - -int TextPage::dumpFragment(Unicode *text, int len, UnicodeMap *uMap, - GooString *s) { - if (uMap->isUnicode()) { - return reorderText(text, len, uMap, primaryLR, s, nullptr); - } else { - int nCols = 0; - - char buf[8]; - int buflen = 0; - - for (int i = 0; i < len; ++i) { - buflen = uMap->mapUnicode(text[i], buf, sizeof(buf)); - s->append(buf, buflen); - nCols += buflen; - } - - return nCols; - } -} - -#ifdef TEXTOUT_WORD_LIST -TextWordList *TextPage::makeWordList(GBool physLayout) { - return new TextWordList(this, physLayout); -} -#endif - -//------------------------------------------------------------------------ -// ActualText -//------------------------------------------------------------------------ -ActualText::ActualText(TextPage *out) { - out->incRefCnt(); - text = out; - actualText = nullptr; - actualTextNBytes = 0; -} - -ActualText::~ActualText() { - if (actualText) - delete actualText; - text->decRefCnt(); -} - -void ActualText::addChar(GfxState *state, double x, double y, - double dx, double dy, - CharCode c, int nBytes, Unicode *u, int uLen) { - if (!actualText) { - text->addChar(state, x, y, dx, dy, c, nBytes, u, uLen); - return; - } - - // Inside ActualText span. - if (!actualTextNBytes) { - actualTextX0 = x; - actualTextY0 = y; - } - actualTextX1 = x + dx; - actualTextY1 = y + dy; - actualTextNBytes += nBytes; -} - -void ActualText::begin(GfxState *state, const GooString *text) { - if (actualText) - delete actualText; - actualText = new GooString(text); - actualTextNBytes = 0; -} - -void ActualText::end(GfxState *state) { - // ActualText span closed. Output the span text and the - // extents of all the glyphs inside the span - - if (actualTextNBytes) { - Unicode *uni = nullptr; - int length; - - // now that we have the position info for all of the text inside - // the marked content span, we feed the "ActualText" back through - // text->addChar() - length = TextStringToUCS4(actualText, &uni); - text->addChar(state, actualTextX0, actualTextY0, - actualTextX1 - actualTextX0, actualTextY1 - actualTextY0, - 0, actualTextNBytes, uni, length); - gfree(uni); - } - - delete actualText; - actualText = nullptr; - actualTextNBytes = 0; -} - -//------------------------------------------------------------------------ -// TextOutputDev -//------------------------------------------------------------------------ - -static void TextOutputDev_outputToFile(void *stream, const char *text, int len) { - fwrite(text, 1, len, (FILE *)stream); -} - -TextOutputDev::TextOutputDev(char *fileName, GBool physLayoutA, - double fixedPitchA, GBool rawOrderA, - GBool append) { - text = nullptr; - physLayout = physLayoutA; - fixedPitch = physLayout ? fixedPitchA : 0; - rawOrder = rawOrderA; - doHTML = gFalse; - ok = gTrue; - - // open file - needClose = gFalse; - if (fileName) { - if (!strcmp(fileName, "-")) { - outputStream = stdout; -#ifdef _WIN32 - // keep DOS from munging the end-of-line characters - setmode(fileno(stdout), O_BINARY); -#endif - } else if ((outputStream = fopen(fileName, append ? "ab" : "wb"))) { - needClose = gTrue; - } else { - error(errIO, -1, "Couldn't open text file '{0:s}'", fileName); - ok = gFalse; - actualText = nullptr; - return; - } - outputFunc = &TextOutputDev_outputToFile; - } else { - outputStream = nullptr; - } - - // set up text object - text = new TextPage(rawOrderA); - actualText = new ActualText(text); -} - -TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream, - GBool physLayoutA, double fixedPitchA, - GBool rawOrderA) { - outputFunc = func; - outputStream = stream; - needClose = gFalse; - physLayout = physLayoutA; - fixedPitch = physLayout ? fixedPitchA : 0; - rawOrder = rawOrderA; - doHTML = gFalse; - text = new TextPage(rawOrderA); - actualText = new ActualText(text); - ok = gTrue; -} - -TextOutputDev::~TextOutputDev() { - if (needClose) { -#ifdef MACOS - ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle); -#endif - fclose((FILE *)outputStream); - } - if (text) { - text->decRefCnt(); - } - delete actualText; -} - -void TextOutputDev::startPage(int pageNum, GfxState *state, XRef *xref) { - text->startPage(state); -} - -void TextOutputDev::endPage() { - text->endPage(); - text->coalesce(physLayout, fixedPitch, doHTML); - if (outputStream) { - text->dump(outputStream, outputFunc, physLayout); - } -} - -void TextOutputDev::restoreState(GfxState *state) { - text->updateFont(state); -} - -void TextOutputDev::updateFont(GfxState *state) { - text->updateFont(state); -} - -void TextOutputDev::beginString(GfxState *state, const GooString *s) { -} - -void TextOutputDev::endString(GfxState *state) { -} - -void TextOutputDev::drawChar(GfxState *state, double x, double y, - double dx, double dy, - double originX, double originY, - CharCode c, int nBytes, Unicode *u, int uLen) { - actualText->addChar(state, x, y, dx, dy, c, nBytes, u, uLen); -} - -void TextOutputDev::incCharCount(int nChars) { - text->incCharCount(nChars); -} - -void TextOutputDev::beginActualText(GfxState *state, const GooString *text) -{ - actualText->begin(state, text); -} - -void TextOutputDev::endActualText(GfxState *state) -{ - actualText->end(state); -} - -void TextOutputDev::stroke(GfxState *state) { - GfxPath *path; - GfxSubpath *subpath; - double x[2], y[2]; - - if (!doHTML) { - return; - } - path = state->getPath(); - if (path->getNumSubpaths() != 1) { - return; - } - subpath = path->getSubpath(0); - if (subpath->getNumPoints() != 2) { - return; - } - state->transform(subpath->getX(0), subpath->getY(0), &x[0], &y[0]); - state->transform(subpath->getX(1), subpath->getY(1), &x[1], &y[1]); - - // look for a vertical or horizontal line - if (x[0] == x[1] || y[0] == y[1]) { - text->addUnderline(x[0], y[0], x[1], y[1]); - } -} - -void TextOutputDev::fill(GfxState *state) { - GfxPath *path; - GfxSubpath *subpath; - double x[5], y[5]; - double rx0, ry0, rx1, ry1, t; - int i; - - if (!doHTML) { - return; - } - path = state->getPath(); - if (path->getNumSubpaths() != 1) { - return; - } - subpath = path->getSubpath(0); - if (subpath->getNumPoints() != 5) { - return; - } - for (i = 0; i < 5; ++i) { - if (subpath->getCurve(i)) { - return; - } - state->transform(subpath->getX(i), subpath->getY(i), &x[i], &y[i]); - } - - // look for a rectangle - if (x[0] == x[1] && y[1] == y[2] && x[2] == x[3] && y[3] == y[4] && - x[0] == x[4] && y[0] == y[4]) { - rx0 = x[0]; - ry0 = y[0]; - rx1 = x[2]; - ry1 = y[1]; - } else if (y[0] == y[1] && x[1] == x[2] && y[2] == y[3] && x[3] == x[4] && - x[0] == x[4] && y[0] == y[4]) { - rx0 = x[0]; - ry0 = y[0]; - rx1 = x[1]; - ry1 = y[2]; - } else { - return; - } - if (rx1 < rx0) { - t = rx0; - rx0 = rx1; - rx1 = t; - } - if (ry1 < ry0) { - t = ry0; - ry0 = ry1; - ry1 = t; - } - - // skinny horizontal rectangle - if (ry1 - ry0 < rx1 - rx0) { - if (ry1 - ry0 < maxUnderlineWidth) { - ry0 = 0.5 * (ry0 + ry1); - text->addUnderline(rx0, ry0, rx1, ry0); - } - - // skinny vertical rectangle - } else { - if (rx1 - rx0 < maxUnderlineWidth) { - rx0 = 0.5 * (rx0 + rx1); - text->addUnderline(rx0, ry0, rx0, ry1); - } - } -} - -void TextOutputDev::eoFill(GfxState *state) { - if (!doHTML) { - return; - } - fill(state); -} - -void TextOutputDev::processLink(AnnotLink *link) { - double x1, y1, x2, y2; - int xMin, yMin, xMax, yMax, x, y; - - if (!doHTML) { - return; - } - link->getRect(&x1, &y1, &x2, &y2); - cvtUserToDev(x1, y1, &x, &y); - xMin = xMax = x; - yMin = yMax = y; - cvtUserToDev(x1, y2, &x, &y); - if (x < xMin) { - xMin = x; - } else if (x > xMax) { - xMax = x; - } - if (y < yMin) { - yMin = y; - } else if (y > yMax) { - yMax = y; - } - cvtUserToDev(x2, y1, &x, &y); - if (x < xMin) { - xMin = x; - } else if (x > xMax) { - xMax = x; - } - if (y < yMin) { - yMin = y; - } else if (y > yMax) { - yMax = y; - } - cvtUserToDev(x2, y2, &x, &y); - if (x < xMin) { - xMin = x; - } else if (x > xMax) { - xMax = x; - } - if (y < yMin) { - yMin = y; - } else if (y > yMax) { - yMax = y; - } - text->addLink(xMin, yMin, xMax, yMax, link); -} - -GBool TextOutputDev::findText(Unicode *s, int len, - GBool startAtTop, GBool stopAtBottom, - GBool startAtLast, GBool stopAtLast, - GBool caseSensitive, GBool backward, - GBool wholeWord, - double *xMin, double *yMin, - double *xMax, double *yMax) { - return text->findText(s, len, startAtTop, stopAtBottom, - startAtLast, stopAtLast, - caseSensitive, backward, wholeWord, - xMin, yMin, xMax, yMax); -} - -GooString *TextOutputDev::getText(double xMin, double yMin, - double xMax, double yMax) { - return text->getText(xMin, yMin, xMax, yMax); -} - -void TextOutputDev::drawSelection(OutputDev *out, - double scale, - int rotation, - PDFRectangle *selection, - SelectionStyle style, - GfxColor *glyph_color, GfxColor *box_color) { - text->drawSelection(out, scale, rotation, selection, style, glyph_color, box_color); -} - -GooList *TextOutputDev::getSelectionRegion(PDFRectangle *selection, - SelectionStyle style, - double scale) { - return text->getSelectionRegion(selection, style, scale); -} - -GooString *TextOutputDev::getSelectionText(PDFRectangle *selection, - SelectionStyle style) -{ - return text->getSelectionText(selection, style); -} - -GBool TextOutputDev::findCharRange(int pos, int length, - double *xMin, double *yMin, - double *xMax, double *yMax) { - return text->findCharRange(pos, length, xMin, yMin, xMax, yMax); -} - -void TextOutputDev::setMergeCombining(GBool merge) { - text->setMergeCombining(merge); -} - -#ifdef TEXTOUT_WORD_LIST -TextWordList *TextOutputDev::makeWordList() { - return text->makeWordList(physLayout); -} -#endif - -TextPage *TextOutputDev::takeText() { - TextPage *ret; - - ret = text; - text = new TextPage(rawOrder); - return ret; -} |