diff options
Diffstat (limited to 'Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc')
-rw-r--r-- | Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc | 881 |
1 files changed, 684 insertions, 197 deletions
diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc index 9ec906c7379..e2da940bb41 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc @@ -58,27 +58,42 @@ // considered to be equivalent. #define splitGapSlack 0.2 -// The vertical gap threshold (minimum gap required to split -// vertically) depends on the (approximate) number of lines in the -// block: -// threshold = (max + slope * nLines) * avgFontSize -// with a min value of vertGapThresholdMin * avgFontSize. -#define vertGapThresholdMin 0.8 -#define vertGapThresholdMax 3 -#define vertGapThresholdSlope -0.5 - -// Vertical gap threshold for table mode. -#define vertGapThresholdTableMin 0.2 -#define vertGapThresholdTableMax 0.5 -#define vertGapThresholdTableSlope -0.02 +// Gap area (width * height) must be larger than this to allow a +// vertical split (or horizontal split if rot=1,3). +#define minGapArea 3 +#define minTableGapArea 1.5 // A large character has a font size larger than // largeCharThreshold * avgFontSize. #define largeCharThreshold 1.5 -// A block will be split vertically only if the resulting chunk -// widths are greater than vertSplitChunkThreshold * avgFontSize. -#define vertSplitChunkThreshold 2 +// A block will be split vertically only if the resulting chunk widths +// are greater than minChunkWidth * avgFontSize. However, Blocks of +// height less than maxSingleLineHeight * avgFontSize are not subject +// to this minimum chunk width criterion. +#define minChunkWidth 2 +#define maxSingleLineHeight 1.5 + +// Minimum vertical gap is minGapSize * minFontSize. +// (horizontal gap for rot=1,3) +#define minGapSize 0.2 + +// Maximum vertical gap for a "small" split is maxWordGapSize * +// avgFontSize. +#define maxWordGapSize 2.5 +#define tableModeMaxWordGapSize 1.1 + +// Adjacent lines can overlap by at most lineOverlapThreshold * +// lineHeight. +#define lineOverlapThreshold 0.33 + +// Subscripts (superscripts) must overlap the next (previous) line by +// minSubSuperscriptVertOverlap * fontSize. +#define minSubSuperscriptVertOverlap 0.25 + +// Subscripts (superscripts) may horizontally overlap adjacent +// characters by up to maxSubSuperscriptHorizOverlap * fontSize. +#define maxSubSuperscriptHorizOverlap 0.05 // Max difference in primary,secondary coordinates (as a fraction of // the font size) allowed for duplicated text (fake boldface, drop @@ -257,6 +272,16 @@ TextChar::TextChar(Unicode cA, int charPosA, int charLenA, if (yMax > 1e8) { yMax = 1e8; } + // zero-width characters will cause problems in the splitting code + if (rotA & 1) { + if (yMax - yMin < 1e-6) { + yMax = yMin + 1e-6; + } + } else { + if (xMax - xMin < 1e-6) { + xMax = xMin + 1e-6; + } + } rot = (Guchar)rotA; rotated = (char)rotatedA; clipped = (char)clippedA; @@ -527,6 +552,35 @@ void TextGaps::addGap(double x, double w) { } //------------------------------------------------------------------------ +// SplitLine +//------------------------------------------------------------------------ + +class SplitLine { +public: + + SplitLine(int firstCharIdxA, int lastCharIdxA, + double yMinA, double yMaxA); + ~SplitLine(); + + int firstCharIdx, lastCharIdx; + double yMin, yMax; // xMin, xMax if rot = {1,3} + GList *chars; // [TextChar] +}; + +SplitLine::SplitLine(int firstCharIdxA, int lastCharIdxA, + double yMinA, double yMaxA) { + firstCharIdx = firstCharIdxA; + lastCharIdx = lastCharIdxA; + yMin = yMinA; + yMax = yMaxA; + chars = NULL; +} + +SplitLine::~SplitLine() { + delete chars; +} + +//------------------------------------------------------------------------ // TextSuperLine //------------------------------------------------------------------------ @@ -671,6 +725,16 @@ TextFontInfo::TextFontInfo(GfxState *state) { } } +TextFontInfo::TextFontInfo() { + fontID.num = -1; + fontID.gen = -1; + fontName = NULL; + flags = 0; + mWidth = 0; + ascent = 0; + descent = 0; +} + TextFontInfo::~TextFontInfo() { if (fontName) { delete fontName; @@ -1388,11 +1452,13 @@ void TextPage::addChar(GfxState *state, double x, double y, } } - // skip space, tab, and non-breaking space characters + // skip various whitespace characters // (ActualText spans can result in multiple space chars) for (i = 0; i < uLen; ++i) { if (u[i] != (Unicode)0x20 && u[i] != (Unicode)0x09 && + u[i] != (Unicode)0x0a && + u[i] != (Unicode)0x0d && u[i] != (Unicode)0xa0) { break; } @@ -1507,6 +1573,36 @@ void TextPage::addChar(GfxState *state, double x, double y, charPos += nBytes; } +void TextPage::addSpecialChar(double xMin, double yMin, + double xMax, double yMax, + int rot, TextFontInfo *font, double fontSize, + Unicode u) { + chars->append(new TextChar(u, 0, 0, xMin, yMin, xMax, yMax, rot, + gFalse, gFalse, gFalse, font, fontSize, 0, 0, 0)); +} + +//~ this is inefficient -- consider using some sort of tree +//~ structure to store the chars (?) +void TextPage::removeChars(double xMin, double yMin, + double xMax, double yMax, + double xOverlapThresh, double yOverlapThresh) { + int i = 0; + while (i < chars->getLength()) { + TextChar *ch = (TextChar *)chars->get(i); + double xOverlap = (ch->xMax < xMax ? ch->xMax : xMax) + - (ch->xMin > xMin ? ch->xMin : xMin); + double yOverlap = (ch->yMax < yMax ? ch->yMax : yMax) + - (ch->yMin > yMin ? ch->yMin : yMin); + if (xOverlap > xOverlapThresh * (ch->xMax - ch->xMin) && + yOverlap > yOverlapThresh * (ch->yMax - ch->yMin)) { + chars->del(i); + delete ch; + } else { + ++i; + } + } +} + void TextPage::incCharCount(int nChars) { charPos += nChars; } @@ -2181,6 +2277,8 @@ void TextPage::writeRaw(void *outputStream, void TextPage::encodeFragment(Unicode *text, int len, UnicodeMap *uMap, GBool primaryLR, GString *s) { char lre[8], rle[8], popdf[8], buf[8]; + GBool open; + Unicode opposite; int lreLen, rleLen, popdfLen, n; int i, j, k; @@ -2195,7 +2293,11 @@ void TextPage::encodeFragment(Unicode *text, int len, UnicodeMap *uMap, i = 0; while (i < len) { // output a left-to-right section - for (j = i; j < len && !unicodeTypeR(text[j]); ++j) ; + for (j = i; + j < len && !unicodeEffectiveTypeR(text[j], + j > 0 ? text[j-1] : 0, + j < len-1 ? text[j+1] : 0); + ++j) ; for (k = i; k < j; ++k) { n = uMap->mapUnicode(text[k], buf, sizeof(buf)); s->append(buf, n); @@ -2203,12 +2305,18 @@ void TextPage::encodeFragment(Unicode *text, int len, UnicodeMap *uMap, i = j; // output a right-to-left section for (j = i; - j < len && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); + j < len && !unicodeEffectiveTypeLOrNum(text[j], + j > 0 ? text[j-1] : 0, + j < len-1 ? text[j+1] : 0); ++j) ; if (j > i) { s->append(rle, rleLen); for (k = j - 1; k >= i; --k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + if (unicodeBracketInfo(text[k], &open, &opposite)) { + n = uMap->mapUnicode(opposite, buf, sizeof(buf)); + } else { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + } s->append(buf, n); } s->append(popdf, popdfLen); @@ -2216,26 +2324,32 @@ void TextPage::encodeFragment(Unicode *text, int len, UnicodeMap *uMap, } } - } else { + } else { // !primaryLR - // 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. 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 >= 0 && !unicodeEffectiveTypeLOrNum(text[j], + j > 0 ? text[j-1] : 0, + j < len-1 ? text[j+1] : 0); --j) ; for (k = i; k > j; --k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + if (unicodeBracketInfo(text[k], &open, &opposite)) { + n = uMap->mapUnicode(opposite, buf, sizeof(buf)); + } else { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + } s->append(buf, n); } i = j; // output a left-to-right section - for (j = i; j >= 0 && !unicodeTypeR(text[j]); --j) ; + for (j = i; + j >= 0 && !unicodeEffectiveTypeR(text[j], + j > 0 ? text[j-1] : 0, + j < len-1 ? text[j+1] : 0); + --j) ; if (j < i) { s->append(lre, lreLen); for (k = j + 1; k <= i; ++k) { @@ -2257,6 +2371,45 @@ void TextPage::encodeFragment(Unicode *text, int len, UnicodeMap *uMap, } } +// Returns true if [u] is LTR, in the context of [left]-[u]-[right]. +// NB: returns false if [u] is neutral. +GBool TextPage::unicodeEffectiveTypeLOrNum(Unicode u, Unicode left, + Unicode right) { + GBool open; + Unicode opposite; + if (unicodeBracketInfo(u, &open, &opposite)) { + // bracket takes the direction of the first char inside the bracket + if (open) { + return unicodeTypeL(right) || unicodeTypeNum(right); + } else { + return unicodeTypeL(left) || unicodeTypeNum(left); + } + } else if (unicodeTypeNumSep(u)) { + // numeric separator is numeric if either side is a digit; neutral + // otherwise + return unicodeTypeDigit(left) || unicodeTypeDigit(right); + } else { + return unicodeTypeL(u) || unicodeTypeDigit(u); + } +} + +// Returns true if [u] is RTL, in the context of [left]-[u]-[right]. +// NB: returns false if [u] is neutral. +GBool TextPage::unicodeEffectiveTypeR(Unicode u, Unicode left, Unicode right) { + GBool open; + Unicode opposite; + if (unicodeBracketInfo(u, &open, &opposite)) { + // bracket takes the direction of the first char inside the bracket + if (open) { + return unicodeTypeR(right); + } else { + return unicodeTypeR(left); + } + } else { + return unicodeTypeR(u); + } +} + //------------------------------------------------------------------------ // TextPage: layout analysis //------------------------------------------------------------------------ @@ -3406,7 +3559,7 @@ TextBlock *TextPage::splitChars(GList *charsA) { } } if (chars2->getLength() > 0) { - tree[rot] = split(chars2, rot); + tree[rot] = split(chars2, rot, gFalse); } } delete chars2; @@ -3457,26 +3610,29 @@ TextBlock *TextPage::splitChars(GList *charsA) { } // Generate a tree of TextBlocks, marked as columns, lines, and words. -TextBlock *TextPage::split(GList *charsA, int rot) { +TextBlock *TextPage::split(GList *charsA, int rot, GBool vertOnly) { TextBlock *blk; GList *chars2, *chars3; + GList *splitLines; TextGaps *horizGaps, *vertGaps; TextChar *ch; - double xMin, yMin, xMax, yMax, avgFontSize; + double xMin, yMin, xMax, yMax, avgFontSize, minFontSize; double horizGapSize, vertGapSize, minHorizChunkWidth, minVertChunkWidth; - double gap, nLines, vertGapThreshold, minChunk; + double gap, gapThreshold, smallSplitThreshold, blockHeight, minChunk; double largeCharSize; double x0, x1, y0, y1; int nHorizGaps, nVertGaps, nLargeChars; int i; - GBool doHorizSplit, doVertSplit, smallSplit; + GBool singleLine; + GBool doHorizSplit, doVertSplit, doLineSplit, doLargeCharSplit, smallSplit; //----- find all horizontal and vertical gaps horizGaps = new TextGaps(); vertGaps = new TextGaps(); - findGaps(charsA, rot, &xMin, &yMin, &xMax, &yMax, &avgFontSize, - horizGaps, vertGaps); + splitLines = new GList(); + findGaps(charsA, rot, &xMin, &yMin, &xMax, &yMax, &avgFontSize, &minFontSize, + splitLines, horizGaps, vertGaps); //----- find the largest horizontal and vertical gaps @@ -3540,38 +3696,48 @@ TextBlock *TextPage::split(GList *charsA, int rot) { //----- compute splitting parameters - // approximation of number of lines in block - if (fabs(avgFontSize) < 0.001) { - nLines = 1; - } else if (rot & 1) { - nLines = (xMax - xMin) / avgFontSize; - } else { - nLines = (yMax - yMin) / avgFontSize; - } + // compute block height; check for single line + blockHeight = (rot & 1) ? xMax - xMin : yMax - yMin; + singleLine = blockHeight < maxSingleLineHeight * avgFontSize && + splitLines->getLength() == 1; // compute the minimum allowed vertical gap size // (this is a horizontal gap threshold for rot=1,3 if (control.mode == textOutTableLayout) { - vertGapThreshold = vertGapThresholdTableMax - + vertGapThresholdTableSlope * nLines; - if (vertGapThreshold < vertGapThresholdTableMin) { - vertGapThreshold = vertGapThresholdTableMin; + if (rot & 1) { + gapThreshold = (minTableGapArea * avgFontSize * avgFontSize) + / (xMax - xMin); + } else { + gapThreshold = (minTableGapArea * avgFontSize * avgFontSize) + / (yMax - yMin); + } + if (minGapSize * avgFontSize > gapThreshold) { + gapThreshold = minGapSize * avgFontSize; } - } else if (control.mode == textOutSimpleLayout) { - vertGapThreshold = simpleLayoutGapThreshold; + smallSplitThreshold = tableModeMaxWordGapSize * avgFontSize; } else { - vertGapThreshold = vertGapThresholdMax + vertGapThresholdSlope * nLines; - if (vertGapThreshold < vertGapThresholdMin) { - vertGapThreshold = vertGapThresholdMin; + if (rot & 1) { + gapThreshold = (minGapArea * avgFontSize * avgFontSize) / (xMax - xMin); + if (vertGapSize > gapThreshold) { + gapThreshold = vertGapSize; + } + } else { + gapThreshold = (minGapArea * avgFontSize * avgFontSize) / (yMax - yMin); + if (horizGapSize > gapThreshold) { + gapThreshold = horizGapSize; + } + } + if (minGapSize * avgFontSize > gapThreshold) { + gapThreshold = minGapSize * avgFontSize; } + smallSplitThreshold = maxWordGapSize * avgFontSize; } - vertGapThreshold = vertGapThreshold * avgFontSize; - // compute the minimum allowed chunk width - if (control.mode == textOutTableLayout) { + if (control.mode == textOutTableLayout || + singleLine) { minChunk = 0; } else { - minChunk = vertSplitChunkThreshold * avgFontSize; + minChunk = minChunkWidth * avgFontSize; } // look for large chars @@ -3593,49 +3759,68 @@ TextBlock *TextPage::split(GList *charsA, int rot) { } // figure out which type of split to do - doHorizSplit = doVertSplit = gFalse; + doHorizSplit = doVertSplit = doLineSplit = doLargeCharSplit = gFalse; smallSplit = gFalse; if (rot & 1) { - if (control.mode == textOutSimpleLayout) { + if (vertOnly) { + if (nHorizGaps > 0 && horizGapSize > minGapSize * minFontSize) { + doHorizSplit = gTrue; + smallSplit = horizGapSize < smallSplitThreshold; + } + } else if (control.mode == textOutSimpleLayout) { if (nVertGaps > 0) { doVertSplit = gTrue; - } else if (nHorizGaps > 0) { + } else if (nHorizGaps > 0 && horizGapSize > minGapSize * minFontSize) { doHorizSplit = gTrue; - smallSplit = horizGapSize <= vertGapThreshold; + smallSplit = horizGapSize < smallSplitThreshold; + } else if (!vertOnly && splitLines->getLength() > 1) { + doLineSplit = gTrue; } } else if (nHorizGaps > 0 && - (horizGapSize > vertGapSize || - control.mode == textOutTableLayout) && - horizGapSize > vertGapThreshold && - (minHorizChunkWidth > minChunk || - nVertGaps == 0)) { + ((horizGapSize > minGapSize * minFontSize && + nVertGaps == 0 && + splitLines->getLength() <= 1) || + (horizGapSize > gapThreshold && + minHorizChunkWidth > minChunk))) { doHorizSplit = gTrue; + smallSplit = horizGapSize < smallSplitThreshold; } else if (nVertGaps > 0) { doVertSplit = gTrue; - } else if (nLargeChars == 0 && nHorizGaps > 0) { - doHorizSplit = gTrue; - smallSplit = gTrue; + } else if (nLargeChars > 0) { + doLargeCharSplit = gTrue; + } else if (splitLines->getLength() > 1) { + doLineSplit = gTrue; } } else { - if (control.mode == textOutSimpleLayout) { + if (vertOnly) { + if (nVertGaps > 0 && + vertGapSize > minGapSize * minFontSize) { + doVertSplit = gTrue; + smallSplit = vertGapSize < smallSplitThreshold; + } + } else if (control.mode == textOutSimpleLayout) { if (nHorizGaps > 0) { doHorizSplit = gTrue; - } else if (nVertGaps > 0) { + } else if (nVertGaps > 0 && vertGapSize > minGapSize * minFontSize) { doVertSplit = gTrue; - smallSplit = vertGapSize <= vertGapThreshold; + smallSplit = vertGapSize < smallSplitThreshold; + } else if (splitLines->getLength() > 1) { + doLineSplit = gTrue; } } else if (nVertGaps > 0 && - (vertGapSize > horizGapSize || - control.mode == textOutTableLayout) && - vertGapSize > vertGapThreshold && - (minVertChunkWidth > minChunk || - nHorizGaps == 0)) { + ((vertGapSize > minGapSize * minFontSize && + nHorizGaps == 0 && + splitLines->getLength() <= 1) || + (vertGapSize > gapThreshold && + minVertChunkWidth > minChunk))) { doVertSplit = gTrue; + smallSplit = vertGapSize < smallSplitThreshold; } else if (nHorizGaps > 0) { doHorizSplit = gTrue; - } else if (nLargeChars == 0 && nVertGaps > 0) { - doVertSplit = gTrue; - smallSplit = gTrue; + } else if (nLargeChars > 0) { + doLargeCharSplit = gTrue; + } else if (splitLines->getLength() > 1) { + doLineSplit = gTrue; } } @@ -3662,13 +3847,13 @@ TextBlock *TextPage::split(GList *charsA, int rot) { if (vertGaps->getW(i) > vertGapSize - splitGapSlack * avgFontSize) { x1 = vertGaps->getX(i); chars2 = getChars(charsA, x0, yMin - 1, x1, yMax + 1); - blk->addChild(split(chars2, rot)); + blk->addChild(split(chars2, rot, vertOnly)); delete chars2; x0 = x1; } } chars2 = getChars(charsA, x0, yMin - 1, xMax + 1, yMax + 1); - blk->addChild(split(chars2, rot)); + blk->addChild(split(chars2, rot, vertOnly)); delete chars2; // split horizontally @@ -3689,17 +3874,17 @@ TextBlock *TextPage::split(GList *charsA, int rot) { if (horizGaps->getW(i) > horizGapSize - splitGapSlack * avgFontSize) { y1 = horizGaps->getX(i); chars2 = getChars(charsA, xMin - 1, y0, xMax + 1, y1); - blk->addChild(split(chars2, rot)); + blk->addChild(split(chars2, rot, gFalse)); delete chars2; y0 = y1; } } chars2 = getChars(charsA, xMin - 1, y0, xMax + 1, yMax + 1); - blk->addChild(split(chars2, rot)); + blk->addChild(split(chars2, rot, gFalse)); delete chars2; // split into larger and smaller chars - } else if (nLargeChars > 0) { + } else if (doLargeCharSplit) { #if 0 //~debug printf("large char split xMin=%g yMin=%g xMax=%g yMax=%g\n", xMin, pageHeight - yMax, xMax, pageHeight - yMin); @@ -3714,23 +3899,60 @@ TextBlock *TextPage::split(GList *charsA, int rot) { chars3->append(ch); } } - blk = split(chars3, rot); + blk = split(chars3, rot, gFalse); + chars2->sort((rot & 1) ? &TextChar::cmpY : &TextChar::cmpX); insertLargeChars(chars2, blk); delete chars2; delete chars3; + // split into lines -- this handles the case where lines overlap slightly + } else if (doLineSplit) { + mergeSplitLines(charsA, rot, splitLines); +#if 0 //~debug + printf("line split xMin=%g yMin=%g xMax=%g yMax=%g\n", + xMin, pageHeight - yMax, xMax, pageHeight - yMin); + for (i = 0; i < splitLines->getLength(); ++i) { + SplitLine *splitLine = (SplitLine *)splitLines->get(i); + printf(" yMin=%g yMax=%g\n", + pageHeight - splitLine->yMax, pageHeight - splitLine->yMin); + } +#endif + if (splitLines->getLength() == 1) { + // if merging sub/superscripts resulted in a single line, then + // it's a leaf + blk = new TextBlock(blkLeaf, rot); + SplitLine *splitLine = (SplitLine *)splitLines->get(0); + for (i = 0; i < splitLine->chars->getLength(); ++i) { + blk->addChild((TextChar *)splitLine->chars->get(i), gTrue); + } + } else { + blk = new TextBlock((rot & 1) ? blkVertSplit : blkHorizSplit, rot); + blk->smallSplit = gFalse; + for (i = 0; i < splitLines->getLength(); ++i) { + SplitLine *splitLine = (SplitLine *)splitLines->get(i); + blk->addChild(split(splitLine->chars, rot, singleLine)); + } + } + // create a leaf node } else { #if 0 //~debug printf("leaf xMin=%g yMin=%g xMax=%g yMax=%g\n", xMin, pageHeight - yMax, xMax, pageHeight - yMin); + printf(" "); + for (i = 0; i < charsA->getLength(); ++i) { + printf("%c", ((TextChar *)charsA->get(i))->c); + } + printf("\n"); #endif blk = new TextBlock(blkLeaf, rot); + charsA->sort((rot & 1) ? &TextChar::cmpY : &TextChar::cmpX); for (i = 0; i < charsA->getLength(); ++i) { blk->addChild((TextChar *)charsA->get(i), gTrue); } } + deleteGList(splitLines, SplitLine); delete horizGaps; delete vertGaps; @@ -3750,11 +3972,12 @@ GList *TextPage::getChars(GList *charsA, double xMin, double yMin, ret = new GList(); for (i = 0; i < charsA->getLength(); ++i) { ch = (TextChar *)charsA->get(i); - // because of {ascent,descent}AdjustFactor, the y coords (or x - // coords for rot 1,3) for the gaps will be a little bit tight -- - // so we use the center of the character here + // compute the center of the adjusted bbox, and check to see if + // that's inside the specified rectangle x = 0.5 * (ch->xMin + ch->xMax); - y = 0.5 * (ch->yMin + ch->yMax); + y = 0.5 * (ch->yMin + ch->yMax + + (ascentAdjustFactor - descentAdjustFactor) * + (ch->yMax - ch->yMin)); if (x > xMin && x < xMax && y > yMin && y < yMax) { ret->append(ch); } @@ -3765,22 +3988,15 @@ GList *TextPage::getChars(GList *charsA, double xMin, double yMin, void TextPage::findGaps(GList *charsA, int rot, double *xMinOut, double *yMinOut, double *xMaxOut, double *yMaxOut, - double *avgFontSizeOut, + double *avgFontSizeOut, double *minFontSizeOut, + GList *splitLines, TextGaps *horizGaps, TextGaps *vertGaps) { - TextChar *ch; - char *horizProfile, *vertProfile; - double xMin, yMin, xMax, yMax, w; - double minFontSize, avgFontSize, splitPrecision, invSplitPrecision; - double ascentAdjust, descentAdjust; - int xMinI, yMinI, xMaxI, yMaxI, xMinI2, yMinI2, xMaxI2, yMaxI2; - int start, x, y, i; - - //----- compute bbox, min font size, average font size, and split precision + //----- compute bbox, min font size, and average font size - xMin = yMin = xMax = yMax = 0; // make gcc happy - minFontSize = avgFontSize = 0; - for (i = 0; i < charsA->getLength(); ++i) { - ch = (TextChar *)charsA->get(i); + double xMin = 0, yMin = 0, xMax = 0, yMax = 0; + double avgFontSize = 0, minFontSize = 0; + for (int i = 0; i < charsA->getLength(); ++i) { + TextChar *ch = (TextChar *)charsA->get(i); if (i == 0 || ch->xMin < xMin) { xMin = ch->xMin; } @@ -3799,115 +4015,338 @@ void TextPage::findGaps(GList *charsA, int rot, } } avgFontSize /= charsA->getLength(); - splitPrecision = splitPrecisionMul * minFontSize; - if (splitPrecision < minSplitPrecision) { - splitPrecision = minSplitPrecision; - } - invSplitPrecision = 1 / splitPrecision; *xMinOut = xMin; *yMinOut = yMin; *xMaxOut = xMax; *yMaxOut = yMax; *avgFontSizeOut = avgFontSize; + *minFontSizeOut = minFontSize; - //----- compute the horizontal and vertical profiles + //----- find vertical gaps - if (xMin * invSplitPrecision < 0.5 * INT_MIN || - xMax * invSplitPrecision > 0.5 * INT_MAX || - yMin * invSplitPrecision < 0.5 * INT_MIN || - yMax * invSplitPrecision > 0.5 * INT_MAX) { - return; - } - // add some slack to the array bounds to avoid floating point - // precision problems - xMinI = (int)floor(xMin * invSplitPrecision) - 1; - yMinI = (int)floor(yMin * invSplitPrecision) - 1; - xMaxI = (int)floor(xMax * invSplitPrecision) + 1; - yMaxI = (int)floor(yMax * invSplitPrecision) + 1; - horizProfile = (char *)gmalloc(yMaxI - yMinI + 1); - vertProfile = (char *)gmalloc(xMaxI - xMinI + 1); - memset(horizProfile, 0, yMaxI - yMinI + 1); - memset(vertProfile, 0, xMaxI - xMinI + 1); - for (i = 0; i < charsA->getLength(); ++i) { - ch = (TextChar *)charsA->get(i); - // yMinI2 and yMaxI2 are adjusted to allow for slightly overlapping lines - switch (rot) { - case 0: - default: - xMinI2 = (int)floor(ch->xMin * invSplitPrecision); - xMaxI2 = (int)floor(ch->xMax * invSplitPrecision); - ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin); - yMinI2 = (int)floor((ch->yMin + ascentAdjust) * invSplitPrecision); - descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin); - yMaxI2 = (int)floor((ch->yMax - descentAdjust) * invSplitPrecision); - break; - case 1: - descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin); - xMinI2 = (int)floor((ch->xMin + descentAdjust) * invSplitPrecision); - ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin); - xMaxI2 = (int)floor((ch->xMax - ascentAdjust) * invSplitPrecision); - yMinI2 = (int)floor(ch->yMin * invSplitPrecision); - yMaxI2 = (int)floor(ch->yMax * invSplitPrecision); - break; - case 2: - xMinI2 = (int)floor(ch->xMin * invSplitPrecision); - xMaxI2 = (int)floor(ch->xMax * invSplitPrecision); - descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin); - yMinI2 = (int)floor((ch->yMin + descentAdjust) * invSplitPrecision); - ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin); - yMaxI2 = (int)floor((ch->yMax - ascentAdjust) * invSplitPrecision); - break; - case 3: - ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin); - xMinI2 = (int)floor((ch->xMin + ascentAdjust) * invSplitPrecision); - descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin); - xMaxI2 = (int)floor((ch->xMax - descentAdjust) * invSplitPrecision); - yMinI2 = (int)floor(ch->yMin * invSplitPrecision); - yMaxI2 = (int)floor(ch->yMax * invSplitPrecision); - break; - } - for (y = yMinI2; y <= yMaxI2; ++y) { - horizProfile[y - yMinI] = 1; + if (rot & 1) { + charsA->sort(&TextChar::cmpY); + double yyMax = 0; + for (int i = 0; i < charsA->getLength(); ++i) { + TextChar *ch = (TextChar *)charsA->get(i); + if (i == 0) { + yyMax = ch->yMax; + } else if (ch->yMin <= yyMax) { + if (ch->yMax > yyMax) { + yyMax = ch->yMax; + } + } else { + horizGaps->addGap(0.5 * (ch->yMin + yyMax), ch->yMin - yyMax); + yyMax = ch->yMax; + } } - for (x = xMinI2; x <= xMaxI2; ++x) { - vertProfile[x - xMinI] = 1; + } else { + charsA->sort(&TextChar::cmpX); + double xxMax = 0; + for (int i = 0; i < charsA->getLength(); ++i) { + TextChar *ch = (TextChar *)charsA->get(i); + if (i == 0) { + xxMax = ch->xMax; + } else if (ch->xMin <= xxMax) { + if (ch->xMax > xxMax) { + xxMax = ch->xMax; + } + } else { + vertGaps->addGap(0.5 * (ch->xMin + xxMax), ch->xMin - xxMax); + xxMax = ch->xMax; + } } } - //----- build the list of horizontal gaps + //----- partition into lines and find horizontal gaps - for (start = yMinI; start < yMaxI && !horizProfile[start - yMinI]; ++start) ; - for (y = start; y < yMaxI; ++y) { - if (horizProfile[y - yMinI]) { - if (!horizProfile[y + 1 - yMinI]) { - start = y; + if (rot & 1) { + charsA->sort(&TextChar::cmpX); + SplitLine *splitLine = NULL; + double xxMin = 0, xxMax = 0; + for (int i = 0; i < charsA->getLength(); ++i) { + TextChar *ch = (TextChar *)charsA->get(i); + if (i == 0 || + ch->xMin > xxMax - lineOverlapThreshold * (xxMax - xxMin)) { + if (i > 0 && ch->xMin > xxMax) { + vertGaps->addGap(0.5 * (ch->xMin + xxMax), ch->xMin - xxMax); + } + xxMin = ch->xMin; + xxMax = ch->xMax; + splitLine = new SplitLine(i, i, xxMin, xxMax); + splitLines->append(splitLine); + } else { + splitLine->lastCharIdx = i; + if (ch->xMax > xxMax) { + xxMax = ch->xMax; + splitLine->yMax = xxMax; + } } - } else { - if (horizProfile[y + 1 - yMinI]) { - w = (y - start) * splitPrecision; - horizGaps->addGap((start + 1) * splitPrecision + 0.5 * w, w); + } + } else { + charsA->sort(&TextChar::cmpY); + SplitLine *splitLine = NULL; + double yyMin = 0, yyMax = 0; + for (int i = 0; i < charsA->getLength(); ++i) { + TextChar *ch = (TextChar *)charsA->get(i); + if (i == 0 || + ch->yMin > yyMax - lineOverlapThreshold * (yyMax - yyMin)) { + if (i > 0 && ch->yMin > yyMax) { + horizGaps->addGap(0.5 * (ch->yMin + yyMax), ch->yMin - yyMax); + } + yyMin = ch->yMin; + yyMax = ch->yMax; + splitLine = new SplitLine(i, i, yyMin, yyMax); + splitLines->append(splitLine); + } else { + splitLine->lastCharIdx = i; + if (ch->yMax > yyMax) { + yyMax = ch->yMax; + splitLine->yMax = yyMax; + } } } } +} - //----- build the list of vertical gaps +void TextPage::mergeSplitLines(GList *charsA, int rot, GList *splitLines) { + if (rot & 1) { - for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ; - for (x = start; x < xMaxI; ++x) { - if (vertProfile[x - xMinI]) { - if (!vertProfile[x + 1 - xMinI]) { - start = x; + // build the list of chars in each line, sort by y + for (int i = 0; i < splitLines->getLength(); ++i) { + SplitLine *splitLine = (SplitLine *)splitLines->get(i); + splitLine->chars = new GList(); + for (int j = splitLine->firstCharIdx; j <= splitLine->lastCharIdx; ++j) { + splitLine->chars->append(charsA->get(j)); } - } else { - if (vertProfile[x + 1 - xMinI]) { - w = (x - start) * splitPrecision; - vertGaps->addGap((start + 1) * splitPrecision + 0.5 * w, w); + splitLine->chars->sort(&TextChar::cmpY); + } + + // look for sub/superscripts accidentally split into separate lines + for (int i = 0; i < splitLines->getLength(); ++i) { + SplitLine *splitLine = (SplitLine *)splitLines->get(i); + SplitLine *prevLine = NULL; + SplitLine *nextLine = NULL; + + // check for overlapping lines + GBool maybeSub = gFalse; + if (i > 0) { + prevLine = (SplitLine *)splitLines->get(i-1); + double minOverlap = minSubSuperscriptVertOverlap + * (prevLine->yMax - prevLine->yMin); + maybeSub = prevLine->yMax - splitLine->yMin > minOverlap; + } + GBool maybeSuper = gFalse; + if (i < splitLines->getLength() - 1) { + nextLine = (SplitLine *)splitLines->get(i+1); + double minOverlap = minSubSuperscriptVertOverlap + * (nextLine->yMax - nextLine->yMin); + maybeSuper = splitLine->yMax - nextLine->yMin > minOverlap; + } + if (!maybeSub && !maybeSuper) { + continue; + } + + // check whether each char in the line is a subscript or + // superscript: look at the two chars above-left and above-right + // the subscript (or below-left and below-right the superscript) + // and check that they are either to the left and right of the + // subscript (superscript), or above the subscript (below the + // superscript) to account for cases with both a subscript and + // superscript + int idxAbove = 0; + int idxBelow = 0; + GBool allSubSuper = gTrue; + for (int idx = 0; idx < splitLine->chars->getLength(); ++idx) { + TextChar *ch = (TextChar *)splitLine->chars->get(idx); + double maxOverlap = maxSubSuperscriptHorizOverlap + * (ch->xMax - ch->xMin); + GBool isSub = gFalse; + if (maybeSub) { + while (idxAbove < prevLine->chars->getLength() && + ((TextChar *)prevLine->chars->get(idxAbove))->yMin < ch->yMin) { + ++idxAbove; + } + isSub = ch->xMin < prevLine->yMax && + (idxAbove == 0 || + ch->yMin > ((TextChar *)prevLine->chars->get(idxAbove - 1))->yMax - maxOverlap || + ch->xMin > ((TextChar *)prevLine->chars->get(idxAbove - 1))->xMax) && + (idxAbove == prevLine->chars->getLength() || + ch->yMax < ((TextChar *)prevLine->chars->get(idxAbove))->yMin + maxOverlap || + ch->xMin > ((TextChar *)prevLine->chars->get(idxAbove))->xMax); + } + GBool isSuper = gFalse; + if (maybeSuper) { + while (idxBelow < nextLine->chars->getLength() && + ((TextChar *)nextLine->chars->get(idxBelow))->yMin < ch->yMin) { + ++idxBelow; + } + isSuper = ch->xMax > nextLine->yMin && + (idxBelow == 0 || + ch->yMin > ((TextChar *)nextLine->chars->get(idxBelow - 1))->yMax - maxOverlap || + ch->xMax < ((TextChar *)nextLine->chars->get(idxBelow - 1))->xMin) && + (idxBelow == nextLine->chars->getLength() || + ch->yMax < ((TextChar *)nextLine->chars->get(idxBelow))->yMin + maxOverlap || + ch->xMax < ((TextChar *)nextLine->chars->get(idxBelow))->xMin); + } + if (!isSub && !isSuper) { + allSubSuper = gFalse; + break; + } + } + + // merge sub/superscripts into correct lines + if (allSubSuper) { + for (int idx = 0; idx < splitLine->chars->getLength(); ++idx) { + TextChar *ch = (TextChar *)splitLine->chars->get(idx); + if (maybeSub && ch->xMin < prevLine->yMax) { + prevLine->chars->append(ch); + if (ch->xMin < prevLine->yMin) { + prevLine->yMin = ch->xMin; + } + if (ch->xMax > prevLine->yMax) { + prevLine->yMax = ch->xMax; + } + } else { + nextLine->chars->append(ch); + if (ch->xMin < nextLine->yMin) { + nextLine->yMin = ch->xMin; + } + if (ch->xMax > nextLine->yMax) { + nextLine->yMax = ch->xMax; + } + } + } + if (maybeSub) { + prevLine->chars->sort(&TextChar::cmpY); + } + if (maybeSuper) { + nextLine->chars->sort(&TextChar::cmpY); + } + delete splitLine; + splitLines->del(i); + } + } + + } else { + + // build the list of chars in each line, sort by x + for (int i = 0; i < splitLines->getLength(); ++i) { + SplitLine *splitLine = (SplitLine *)splitLines->get(i); + splitLine->chars = new GList(); + for (int j = splitLine->firstCharIdx; j <= splitLine->lastCharIdx; ++j) { + splitLine->chars->append(charsA->get(j)); + } + splitLine->chars->sort(&TextChar::cmpX); + } + + // look for sub/superscripts accidentally split into separate lines + for (int i = 0; i < splitLines->getLength(); ++i) { + SplitLine *splitLine = (SplitLine *)splitLines->get(i); + SplitLine *prevLine = NULL; + SplitLine *nextLine = NULL; + + // check for overlapping lines + GBool maybeSub = gFalse; + if (i > 0) { + prevLine = (SplitLine *)splitLines->get(i-1); + double minOverlap = minSubSuperscriptVertOverlap + * (prevLine->yMax - prevLine->yMin); + maybeSub = prevLine->yMax - splitLine->yMin > minOverlap; + } + GBool maybeSuper = gFalse; + if (i < splitLines->getLength() - 1) { + nextLine = (SplitLine *)splitLines->get(i+1); + double minOverlap = minSubSuperscriptVertOverlap + * (nextLine->yMax - nextLine->yMin); + maybeSuper = splitLine->yMax - nextLine->yMin > minOverlap; + } + if (!maybeSub && !maybeSuper) { + continue; + } + + // check whether each char in the line is a subscript or + // superscript: look at the two chars above-left and above-right + // the subscript (or below-left and below-right the superscript) + // and check that they are either to the left and right of the + // subscript (superscript), or above the subscript (below the + // superscript) to account for cases with both a subscript and + // superscript + int idxAbove = 0; + int idxBelow = 0; + GBool allSubSuper = gTrue; + for (int idx = 0; idx < splitLine->chars->getLength(); ++idx) { + TextChar *ch = (TextChar *)splitLine->chars->get(idx); + double maxOverlap = maxSubSuperscriptHorizOverlap + * (ch->yMax - ch->yMin); + GBool isSub = gFalse; + if (maybeSub) { + while (idxAbove < prevLine->chars->getLength() && + ((TextChar *)prevLine->chars->get(idxAbove))->xMin < ch->xMin) { + ++idxAbove; + } + isSub = ch->yMin < prevLine->yMax && + (idxAbove == 0 || + ch->xMin > ((TextChar *)prevLine->chars->get(idxAbove - 1))->xMax - maxOverlap || + ch->yMin > ((TextChar *)prevLine->chars->get(idxAbove - 1))->yMax) && + (idxAbove == prevLine->chars->getLength() || + ch->xMax < ((TextChar *)prevLine->chars->get(idxAbove))->xMin + maxOverlap || + ch->yMin > ((TextChar *)prevLine->chars->get(idxAbove))->yMax); + } + GBool isSuper = gFalse; + if (maybeSuper) { + while (idxBelow < nextLine->chars->getLength() && + ((TextChar *)nextLine->chars->get(idxBelow))->xMin < ch->xMin) { + ++idxBelow; + } + isSuper = ch->yMax > nextLine->yMin && + (idxBelow == 0 || + ch->xMin > ((TextChar *)nextLine->chars->get(idxBelow - 1))->xMax - maxOverlap || + ch->yMax < ((TextChar *)nextLine->chars->get(idxBelow - 1))->yMin) && + (idxBelow == nextLine->chars->getLength() || + ch->xMax < ((TextChar *)nextLine->chars->get(idxBelow))->xMin + maxOverlap || + ch->yMax < ((TextChar *)nextLine->chars->get(idxBelow))->yMin); + } + if (!isSub && !isSuper) { + allSubSuper = gFalse; + break; + } + } + + // merge sub/superscripts into correct lines + if (allSubSuper) { + for (int idx = 0; idx < splitLine->chars->getLength(); ++idx) { + TextChar *ch = (TextChar *)splitLine->chars->get(idx); + if (maybeSub && ch->yMin < prevLine->yMax) { + prevLine->chars->append(ch); + if (ch->yMin < prevLine->yMin) { + prevLine->yMin = ch->yMin; + } + if (ch->yMax > prevLine->yMax) { + prevLine->yMax = ch->yMax; + } + } else { + nextLine->chars->append(ch); + if (ch->yMin < nextLine->yMin) { + nextLine->yMin = ch->yMin; + } + if (ch->yMax > nextLine->yMax) { + nextLine->yMax = ch->yMax; + } + } + } + if (maybeSub) { + prevLine->chars->sort(&TextChar::cmpX); + } + if (maybeSuper) { + nextLine->chars->sort(&TextChar::cmpX); + } + delete splitLine; + splitLines->del(i); } } - } - gfree(horizProfile); - gfree(vertProfile); + } } // Decide whether this block is a line, column, or multiple columns: @@ -4668,7 +5107,12 @@ TextLine *TextPage::buildLine(GList *charsA, int rot, while (i < charsA->getLength()) { sp = wordSp - 1; spaceAfter = gFalse; - dir = getCharDirection((TextChar *)charsA->get(i)); + dir = getCharDirection( + (TextChar *)charsA->get(i), + (i > 0 && !spaceAfter) ? (TextChar *)charsA->get(i-1) + : (TextChar *)NULL, + (i < charsA->getLength() - 1) ? (TextChar *)charsA->get(i+1) + : (TextChar *)NULL); rotated = ((TextChar *)charsA->get(i))->rotated; for (j = i+1; j < charsA->getLength(); ++j) { ch = (TextChar *)charsA->get(j-1); @@ -4684,7 +5128,10 @@ TextLine *TextPage::buildLine(GList *charsA, int rot, spaceAfter = gTrue; break; } - dir2 = getCharDirection(ch2); + dir2 = getCharDirection( + ch2, ch, + (j < charsA->getLength() - 1) ? (TextChar *)charsA->get(j+1) + : (TextChar *)NULL); if (ch->font != ch2->font || fabs(ch->fontSize - ch2->fontSize) > 0.01 || (control.splitRotatedWords && ch2->rotated != rotated) || @@ -4830,7 +5277,7 @@ double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) { } } -// Check the characters direction: returns 1 for L or Num; -1 for R; 0 +// Check the character's direction: returns 1 for L or Num; -1 for R; 0 // for others. int TextPage::getCharDirection(TextChar *ch) { if (unicodeTypeL(ch->c) || unicodeTypeNum(ch->c)) { @@ -4842,6 +5289,46 @@ int TextPage::getCharDirection(TextChar *ch) { return 0; } +// Check the character's direction, in the context of the left and +// right neighboring characters (either of which may be NULL). +// Returns 1 for L or Num; -1 for R; 0 for others. +int TextPage::getCharDirection(TextChar *ch, TextChar *left, TextChar *right) { + GBool open; + Unicode opposite; + if (unicodeBracketInfo(ch->c, &open, &opposite)) { + // bracket takes the direction of the first char inside the bracket + Unicode first = 0; + if (open && right) { + first = right->c; + } else if (!open && left) { + first = left->c; + } + if (unicodeTypeL(first) || unicodeTypeNum(first)) { + return 1; + } + if (unicodeTypeR(first)) { + return -1; + } + return 0; + } else if (unicodeTypeNumSep(ch->c)) { + // numeric separator is numeric if either side is a digit; neutral + // otherwise + if ((left && unicodeTypeDigit(left->c)) || + (right && unicodeTypeDigit(right->c))) { + return 1; + } + return 0; + } else { + if (unicodeTypeL(ch->c) || unicodeTypeDigit(ch->c)) { + return 1; + } + if (unicodeTypeR(ch->c)) { + return -1; + } + return 0; + } +} + int TextPage::assignPhysLayoutPositions(GList *columns) { assignLinePhysPositions(columns); return assignColumnPhysPositions(columns); @@ -5130,11 +5617,11 @@ GBool TextPage::findText(Unicode *s, int len, buildFindCols(); - // convert the search string to uppercase + // convert the search string to lowercase if (!caseSensitive) { s2 = (Unicode *)gmallocn(len, sizeof(Unicode)); for (i = 0; i < len; ++i) { - s2[i] = unicodeToUpper(s[i]); + s2[i] = unicodeToLower(s[i]); } } else { s2 = s; @@ -5214,7 +5701,7 @@ GBool TextPage::findText(Unicode *s, int len, continue; } - // convert the line to uppercase + // convert the line to lowercase m = line->len; if (!caseSensitive) { if (m > txtSize) { @@ -5222,7 +5709,7 @@ GBool TextPage::findText(Unicode *s, int len, txtSize = m; } for (k = 0; k < m; ++k) { - txt[k] = unicodeToUpper(line->text[k]); + txt[k] = unicodeToLower(line->text[k]); } } else { txt = line->text; |