summaryrefslogtreecommitdiff
path: root/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc')
-rw-r--r--Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc1556
1 files changed, 1141 insertions, 415 deletions
diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc
index be1fad2a2e9..025c34c16c0 100644
--- a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc
+++ b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc
@@ -22,6 +22,7 @@
#include <io.h> // for setmode
#endif
#include "gmem.h"
+#include "gmempp.h"
#include "GString.h"
#include "GList.h"
#include "config.h"
@@ -97,7 +98,7 @@
// If the space between two lines is greater than
// paragraphSpacingThreshold * avgLineSpacing, start a new paragraph.
-#define paragraphSpacingThreshold 1.2
+#define paragraphSpacingThreshold 1.25
// If font size changes by at least this much (measured in points)
// between lines, start a new paragraph.
@@ -107,6 +108,10 @@
// (as a multiple of font size).
#define physLayoutSpaceWidth 0.33
+// In simple layout mode, lines are broken at gaps larger than this
+// value multiplied by font size.
+#define simpleLayoutGapThreshold 0.4
+
// Table cells (TextColumns) are allowed to overlap by this much
// in table layout mode (as a fraction of cell width or height).
#define tableCellOverlapSlack 0.05
@@ -142,6 +147,14 @@
// fraction of font size).
#define hyperlinkSlack 0.2
+// Text is considered diagonal if abs(tan(angle)) > diagonalThreshold.
+// (Or 1/tan(angle) for 90/270 degrees.)
+#define diagonalThreshold 0.1
+
+// This value is used as the ascent when computing selection
+// rectangles, in order to work around flakey ascent values in fonts.
+#define selectionAscent 0.8
+
//------------------------------------------------------------------------
// TextChar
//------------------------------------------------------------------------
@@ -165,6 +178,7 @@ public:
Guchar rot;
char clipped;
char invisible;
+ char spaceAfter;
TextFontInfo *font;
double fontSize;
double colorR,
@@ -194,9 +208,25 @@ TextChar::TextChar(Unicode cA, int charPosA, int charLenA,
if (yMin > yMax) {
t = yMin; yMin = yMax; yMax = t;
}
+ // TextPage::findGaps uses integer coordinates, so clip the char
+ // bbox to fit in a 32-bit int (this is generally only a problem in
+ // damaged PDF files)
+ if (xMin < -1e8) {
+ xMin = -1e8;
+ }
+ if (xMax > 1e8) {
+ xMax = 1e8;
+ }
+ if (yMin < -1e8) {
+ yMin = -1e8;
+ }
+ if (yMax > 1e8) {
+ yMax = 1e8;
+ }
rot = (Guchar)rotA;
clipped = (char)clippedA;
invisible = (char)invisibleA;
+ spaceAfter = (char)gFalse;
font = fontA;
fontSize = fontSizeA;
colorR = colorRA;
@@ -243,6 +273,7 @@ enum TextBlockType {
enum TextBlockTag {
blkTagMulticolumn,
blkTagColumn,
+ blkTagSuperLine,
blkTagLine
};
@@ -252,7 +283,7 @@ public:
TextBlock(TextBlockType typeA, int rotA);
~TextBlock();
void addChild(TextBlock *child);
- void addChild(TextChar *child);
+ void addChild(TextChar *child, GBool updateBox);
void prependChild(TextChar *child);
void updateBounds(int childIdx);
@@ -306,24 +337,26 @@ void TextBlock::addChild(TextBlock *child) {
children->append(child);
}
-void TextBlock::addChild(TextChar *child) {
- if (children->getLength() == 0) {
- xMin = child->xMin;
- yMin = child->yMin;
- xMax = child->xMax;
- yMax = child->yMax;
- } else {
- if (child->xMin < xMin) {
+void TextBlock::addChild(TextChar *child, GBool updateBox) {
+ if (updateBox) {
+ if (children->getLength() == 0) {
xMin = child->xMin;
- }
- if (child->yMin < yMin) {
yMin = child->yMin;
- }
- if (child->xMax > xMax) {
xMax = child->xMax;
- }
- if (child->yMax > yMax) {
yMax = child->yMax;
+ } else {
+ if (child->xMin < xMin) {
+ xMin = child->xMin;
+ }
+ if (child->yMin < yMin) {
+ yMin = child->yMin;
+ }
+ if (child->xMax > xMax) {
+ xMax = child->xMax;
+ }
+ if (child->yMax > yMax) {
+ yMax = child->yMax;
+ }
}
}
children->append(child);
@@ -371,6 +404,57 @@ void TextBlock::updateBounds(int childIdx) {
}
//------------------------------------------------------------------------
+// TextGap
+//------------------------------------------------------------------------
+
+class TextGap {
+public:
+
+ TextGap(double aXY, double aW): xy(aXY), w(aW) {}
+
+ double xy; // center of gap: x for vertical gaps,
+ // y for horizontal gaps
+ double w; // width of gap
+};
+
+//------------------------------------------------------------------------
+// TextSuperLine
+//------------------------------------------------------------------------
+
+class TextSuperLine {
+public:
+
+ TextSuperLine(GList *linesA);
+ ~TextSuperLine();
+
+ GList *lines; // [TextLine]
+ double yMin, yMax;
+ double fontSize;
+};
+
+TextSuperLine::TextSuperLine(GList *linesA) {
+ TextLine *line;
+ int i;
+
+ lines = linesA;
+ yMin = yMax = 0;
+ fontSize = ((TextLine *)lines->get(0))->fontSize;
+ for (i = 0; i < lines->getLength(); ++i) {
+ line = (TextLine *)lines->get(i);
+ if (i == 0 || line->yMin < yMin) {
+ yMin = line->yMin;
+ }
+ if (i == 0 || line->yMax > yMax) {
+ yMax = line->yMax;
+ }
+ }
+}
+
+TextSuperLine::~TextSuperLine() {
+ deleteGList(lines, TextLine);
+}
+
+//------------------------------------------------------------------------
// TextUnderline
//------------------------------------------------------------------------
@@ -417,6 +501,10 @@ TextOutputControl::TextOutputControl() {
fixedLineSpacing = 0;
html = gFalse;
clipText = gFalse;
+ discardDiagonalText = gFalse;
+ discardInvisibleText = gFalse;
+ discardClippedText = gFalse;
+ insertBOM = gFalse;
}
@@ -489,7 +577,7 @@ GBool TextFontInfo::matches(GfxState *state) {
// Build a TextWord object, using chars[start .. start+len-1].
// (If rot >= 2, the chars list is in reverse order.)
TextWord::TextWord(GList *chars, int start, int lenA,
- int rotA, GBool spaceAfterA) {
+ int rotA, int dirA, GBool spaceAfterA) {
TextChar *ch;
int i;
@@ -498,40 +586,20 @@ TextWord::TextWord(GList *chars, int start, int lenA,
text = (Unicode *)gmallocn(len, sizeof(Unicode));
edge = (double *)gmallocn(len + 1, sizeof(double));
charPos = (int *)gmallocn(len + 1, sizeof(int));
- switch (rot) {
- case 0:
- default:
- ch = (TextChar *)chars->get(start);
- xMin = ch->xMin;
- yMin = ch->yMin;
- yMax = ch->yMax;
- ch = (TextChar *)chars->get(start + len - 1);
- xMax = ch->xMax;
- break;
- case 1:
+ if (rot & 1) {
ch = (TextChar *)chars->get(start);
xMin = ch->xMin;
xMax = ch->xMax;
yMin = ch->yMin;
ch = (TextChar *)chars->get(start + len - 1);
yMax = ch->yMax;
- break;
- case 2:
+ } else {
ch = (TextChar *)chars->get(start);
- xMax = ch->xMax;
+ xMin = ch->xMin;
yMin = ch->yMin;
yMax = ch->yMax;
ch = (TextChar *)chars->get(start + len - 1);
- xMin = ch->xMin;
- break;
- case 3:
- ch = (TextChar *)chars->get(start);
- xMin = ch->xMin;
xMax = ch->xMax;
- yMax = ch->yMax;
- ch = (TextChar *)chars->get(start + len - 1);
- yMin = ch->yMin;
- break;
}
for (i = 0; i < len; ++i) {
ch = (TextChar *)chars->get(rot >= 2 ? start + len - 1 - i : start + i);
@@ -571,6 +639,7 @@ TextWord::TextWord(GList *chars, int start, int lenA,
ch = (TextChar *)chars->get(start);
font = ch->font;
fontSize = ch->fontSize;
+ dir = dirA;
spaceAfter = spaceAfterA;
underlined = gFalse;
link = NULL;
@@ -596,48 +665,6 @@ TextWord::~TextWord() {
gfree(charPos);
}
-// This is used to append a clipped character to a word.
-void TextWord::appendChar(TextChar *ch) {
- if (ch->xMin < xMin) {
- xMin = ch->xMin;
- }
- if (ch->xMax > xMax) {
- xMax = ch->xMax;
- }
- if (ch->yMin < yMin) {
- yMin = ch->yMin;
- }
- if (ch->yMax > yMax) {
- yMax = ch->yMax;
- }
- text = (Unicode *)greallocn(text, len + 1, sizeof(Unicode));
- edge = (double *)greallocn(edge, len + 2, sizeof(double));
- charPos = (int *)greallocn(charPos, len + 2, sizeof(int));
- text[len] = ch->c;
- charPos[len] = ch->charPos;
- charPos[len+1] = ch->charPos + ch->charLen;
- switch (rot) {
- case 0:
- default:
- edge[len] = ch->xMin;
- edge[len+1] = ch->xMax;
- break;
- case 1:
- edge[len] = ch->yMin;
- edge[len+1] = ch->yMax;
- break;
- case 2:
- edge[len] = ch->xMax;
- edge[len+1] = ch->xMin;
- break;
- case 3:
- edge[len] = ch->yMax;
- edge[len+1] = ch->yMin;
- break;
- }
- ++len;
-}
-
int TextWord::cmpYX(const void *p1, const void *p2) {
const TextWord *word1 = *(const TextWord **)p1;
const TextWord *word2 = *(const TextWord **)p2;
@@ -784,19 +811,19 @@ TextLine::~TextLine() {
}
double TextLine::getBaseline() {
- TextWord *word0;
+ return ((TextWord *)words->get(0))->getBaseline();
+}
- word0 = (TextWord *)words->get(0);
- switch (rot) {
- case 0:
- default:
- return yMax + fontSize * word0->font->descent;
- case 1:
- return xMin - fontSize * word0->font->descent;
- case 2:
- return yMin - fontSize * word0->font->descent;
- case 3:
- return xMax + fontSize * word0->font->descent;
+int TextLine::cmpX(const void *p1, const void *p2) {
+ const TextLine *line1 = *(const TextLine **)p1;
+ const TextLine *line2 = *(const TextLine **)p2;
+
+ if (line1->xMin < line2->xMin) {
+ return -1;
+ } else if (line1->xMin > line2->xMin) {
+ return 1;
+ } else {
+ return 0;
}
}
@@ -804,11 +831,12 @@ double TextLine::getBaseline() {
// TextParagraph
//------------------------------------------------------------------------
-TextParagraph::TextParagraph(GList *linesA) {
+TextParagraph::TextParagraph(GList *linesA, GBool dropCapA) {
TextLine *line;
int i;
lines = linesA;
+ dropCap = dropCapA;
xMin = yMin = xMax = yMax = 0;
for (i = 0; i < lines->getLength(); ++i) {
line = (TextLine *)lines->get(i);
@@ -850,6 +878,15 @@ TextColumn::~TextColumn() {
deleteGList(paragraphs, TextParagraph);
}
+int TextColumn::getRotation() {
+ TextParagraph *par;
+ TextLine *line;
+
+ par = (TextParagraph *)paragraphs->get(0);
+ line = (TextLine *)par->getLines()->get(0);
+ return line->getRotation();
+}
+
int TextColumn::cmpX(const void *p1, const void *p2) {
const TextColumn *col1 = *(const TextColumn **)p1;
const TextColumn *col2 = *(const TextColumn **)p2;
@@ -893,8 +930,9 @@ int TextColumn::cmpPX(const void *p1, const void *p2) {
// TextWordList
//------------------------------------------------------------------------
-TextWordList::TextWordList(GList *wordsA) {
+TextWordList::TextWordList(GList *wordsA, GBool primaryLRA) {
words = wordsA;
+ primaryLR = primaryLRA;
}
TextWordList::~TextWordList() {
@@ -913,6 +951,44 @@ TextWord *TextWordList::get(int idx) {
}
//------------------------------------------------------------------------
+// TextPosition
+//------------------------------------------------------------------------
+
+int TextPosition::operator==(TextPosition pos) {
+ return colIdx == pos.colIdx &&
+ parIdx == pos.parIdx &&
+ lineIdx == pos.lineIdx &&
+ charIdx == pos.charIdx;
+}
+
+int TextPosition::operator!=(TextPosition pos) {
+ return colIdx != pos.colIdx ||
+ parIdx != pos.parIdx ||
+ lineIdx != pos.lineIdx ||
+ charIdx != pos.charIdx;
+}
+
+int TextPosition::operator<(TextPosition pos) {
+ return colIdx < pos.colIdx ||
+ (colIdx == pos.colIdx &&
+ (parIdx < pos.parIdx ||
+ (parIdx == pos.parIdx &&
+ (lineIdx < pos.lineIdx ||
+ (lineIdx == pos.lineIdx &&
+ charIdx < pos.charIdx)))));
+}
+
+int TextPosition::operator>(TextPosition pos) {
+ return colIdx > pos.colIdx ||
+ (colIdx == pos.colIdx &&
+ (parIdx > pos.parIdx ||
+ (parIdx == pos.parIdx &&
+ (lineIdx > pos.lineIdx ||
+ (lineIdx == pos.lineIdx &&
+ charIdx > pos.charIdx)))));
+}
+
+//------------------------------------------------------------------------
// TextPage
//------------------------------------------------------------------------
@@ -923,6 +999,7 @@ TextPage::TextPage(TextOutputControl *controlA) {
curFont = NULL;
curFontSize = 0;
curRot = 0;
+ diagonal = gFalse;
nTinyChars = 0;
actualText = NULL;
actualTextLen = 0;
@@ -939,9 +1016,10 @@ TextPage::TextPage(TextOutputControl *controlA) {
links = new GList();
findCols = NULL;
- findLR = gTrue;
lastFindXMin = lastFindYMin = 0;
haveLastFind = gFalse;
+
+ problematic = gFalse;
}
TextPage::~TextPage() {
@@ -971,6 +1049,7 @@ void TextPage::clear() {
curFont = NULL;
curFontSize = 0;
curRot = 0;
+ diagonal = gFalse;
nTinyChars = 0;
gfree(actualText);
actualText = NULL;
@@ -989,9 +1068,10 @@ void TextPage::clear() {
deleteGList(findCols, TextColumn);
findCols = NULL;
}
- findLR = gTrue;
lastFindXMin = lastFindYMin = 0;
haveLastFind = gFalse;
+
+ problematic = gFalse;
}
void TextPage::updateFont(GfxState *state) {
@@ -1015,6 +1095,9 @@ void TextPage::updateFont(GfxState *state) {
if (!curFont) {
curFont = new TextFontInfo(state);
fonts->append(curFont);
+ if (state->getFont() && state->getFont()->problematicForUnicode()) {
+ problematic = gTrue;
+ }
}
// adjust the font size
@@ -1075,10 +1158,20 @@ void TextPage::updateFont(GfxState *state) {
m[2] = m2[2];
m[3] = m2[3];
}
- if (fabs(m[0] * m[3]) > fabs(m[1] * m[2])) {
- curRot = (m[0] > 0 || m[3] < 0) ? 0 : 2;
+ if (fabs(m[0]) >= fabs(m[1])) {
+ if (m[0] > 0) {
+ curRot = 0;
+ } else {
+ curRot = 2;
+ }
+ diagonal = fabs(m[1]) > diagonalThreshold * fabs(m[0]);
} else {
- curRot = (m[2] > 0) ? 1 : 3;
+ if (m[1] > 0) {
+ curRot = 1;
+ } else {
+ curRot = 3;
+ }
+ diagonal = fabs(m[0]) > diagonalThreshold * fabs(m[1]);
}
}
@@ -1086,9 +1179,10 @@ void TextPage::addChar(GfxState *state, double x, double y,
double dx, double dy,
CharCode c, int nBytes, Unicode *u, int uLen) {
double x1, y1, x2, y2, w1, h1, dx2, dy2, ascent, descent, sp;
- double xMin, yMin, xMax, yMax;
+ double xMin, yMin, xMax, yMax, xMid, yMid;
double clipXMin, clipYMin, clipXMax, clipYMax;
GfxRGB rgb;
+ double alpha;
GBool clipped, rtl;
int i, j;
@@ -1105,6 +1199,12 @@ void TextPage::addChar(GfxState *state, double x, double y,
return;
}
+ // throw away diagonal chars
+ if (control.discardDiagonalText && diagonal) {
+ charPos += nBytes;
+ return;
+ }
+
// subtract char and word spacing from the dx,dy values
sp = state->getCharSpace();
if (c == (CharCode)0x20) {
@@ -1134,20 +1234,16 @@ void TextPage::addChar(GfxState *state, double x, double y,
}
}
- // skip space characters
- if (uLen == 1 && u[0] == (Unicode)0x20) {
+ // skip space, tab, and non-breaking space characters
+ if (uLen == 1 && (u[0] == (Unicode)0x20 ||
+ u[0] == (Unicode)0x09 ||
+ u[0] == (Unicode)0xa0)) {
charPos += nBytes;
- return;
- }
-
- // check for clipping
- clipped = gFalse;
- if (control.clipText) {
- state->getClipBBox(&clipXMin, &clipYMin, &clipXMax, &clipYMax);
- if (x1 + 0.1 * w1 < clipXMin || x1 + 0.9 * w1 > clipXMax ||
- y1 + 0.1 * h1 < clipYMin || y1 + 0.9 * h1 > clipYMax) {
- clipped = gTrue;
+ if (chars->getLength() > 0) {
+ ((TextChar *)chars->get(chars->getLength() - 1))->spaceAfter =
+ (char)gTrue;
}
+ return;
}
// add the characters
@@ -1168,6 +1264,7 @@ void TextPage::addChar(GfxState *state, double x, double y,
rtl = gFalse;
}
+ // compute the bounding box
w1 /= uLen;
h1 /= uLen;
ascent = curFont->ascent * curFontSize;
@@ -1202,10 +1299,25 @@ void TextPage::addChar(GfxState *state, double x, double y,
yMax = y2;
break;
}
+
+ // check for clipping
+ clipped = gFalse;
+ if (control.clipText || control.discardClippedText) {
+ state->getClipBBox(&clipXMin, &clipYMin, &clipXMax, &clipYMax);
+ xMid = 0.5 * (xMin + xMax);
+ yMid = 0.5 * (yMin + yMax);
+ if (xMid < clipXMin || xMid > clipXMax ||
+ yMid < clipYMin || yMid > clipYMax) {
+ clipped = gTrue;
+ }
+ }
+
if ((state->getRender() & 3) == 1) {
state->getStrokeRGB(&rgb);
+ alpha = state->getStrokeOpacity();
} else {
state->getFillRGB(&rgb);
+ alpha = state->getFillOpacity();
}
if (rtl) {
j = uLen - 1 - i;
@@ -1214,7 +1326,7 @@ void TextPage::addChar(GfxState *state, double x, double y,
}
chars->append(new TextChar(u[j], charPos, nBytes, xMin, yMin, xMax, yMax,
curRot, clipped,
- state->getRender() == 3,
+ state->getRender() == 3 || alpha < 0.001,
curFont, curFontSize,
colToDbl(rgb.r), colToDbl(rgb.g),
colToDbl(rgb.b)));
@@ -1312,6 +1424,10 @@ void TextPage::write(void *outputStream, TextOutputFunc outputFunc) {
writePhysLayout(outputStream, outputFunc, uMap, space, spaceLen,
eol, eolLen);
break;
+ case textOutSimpleLayout:
+ writeSimpleLayout(outputStream, outputFunc, uMap, space, spaceLen,
+ eol, eolLen);
+ break;
case textOutLinePrinter:
writeLinePrinter(outputStream, outputFunc, uMap, space, spaceLen,
eol, eolLen);
@@ -1355,7 +1471,7 @@ void TextPage::writeReadingOrder(void *outputStream,
unrotateChars(chars, rot);
return;
}
- columns = buildColumns(tree);
+ columns = buildColumns(tree, primaryLR);
delete tree;
unrotateChars(chars, rot);
if (control.html) {
@@ -1395,14 +1511,20 @@ void TextPage::writeReadingOrder(void *outputStream,
GList *TextPage::makeColumns() {
TextBlock *tree;
GList *columns;
+ GBool primaryLR;
+ int rot;
- tree = splitChars(chars);
- if (!tree) {
+ rot = rotateChars(chars);
+ primaryLR = checkPrimaryLR(chars);
+ if ((tree = splitChars(chars))) {
+ columns = buildColumns(tree, primaryLR);
+ delete tree;
+ } else {
// no text
- return new GList();
+ columns = new GList();
}
- columns = buildColumns(tree);
- delete tree;
+ unrotateChars(chars, rot);
+ unrotateColumns(columns, rot);
if (control.html) {
generateUnderlinesAndLinks(columns);
}
@@ -1428,6 +1550,9 @@ void TextPage::writePhysLayout(void *outputStream,
#if 0 //~debug
dumpChars(chars);
#endif
+#if 0 //~debug
+ dumpUnderlines();
+#endif
rot = rotateChars(chars);
primaryLR = checkPrimaryLR(chars);
tree = splitChars(chars);
@@ -1439,7 +1564,8 @@ void TextPage::writePhysLayout(void *outputStream,
unrotateChars(chars, rot);
return;
}
- columns = buildColumns(tree);
+ //~ this doesn't correctly handle the right-to-left case
+ columns = buildColumns(tree, gTrue);
delete tree;
unrotateChars(chars, rot);
if (control.html) {
@@ -1501,6 +1627,66 @@ void TextPage::writePhysLayout(void *outputStream,
deleteGList(columns, TextColumn);
}
+void TextPage::writeSimpleLayout(void *outputStream,
+ TextOutputFunc outputFunc,
+ UnicodeMap *uMap,
+ char *space, int spaceLen,
+ char *eol, int eolLen) {
+ TextBlock *tree;
+ TextSuperLine *superLine0, *superLine1;
+ TextLine *line;
+ GList *superLines;
+ GString *out;
+ GBool primaryLR;
+ int rot, x, i, j;
+
+#if 0 //~debug
+ dumpChars(chars);
+#endif
+ rot = rotateChars(chars);
+ primaryLR = checkPrimaryLR(chars);
+ tree = splitChars(chars);
+#if 0 //~debug
+ dumpTree(tree);
+#endif
+ if (!tree) {
+ // no text
+ unrotateChars(chars, rot);
+ return;
+ }
+ superLines = new GList();
+ buildSuperLines(tree, superLines);
+ delete tree;
+ unrotateChars(chars, rot);
+ assignSimpleLayoutPositions(superLines, uMap);
+
+ for (i = 0; i < superLines->getLength(); ++i) {
+ superLine0 = (TextSuperLine *)superLines->get(i);
+ out = new GString();
+ x = 0;
+ for (j = 0; j < superLine0->lines->getLength(); ++j) {
+ line = (TextLine *)superLine0->lines->get(j);
+ while (x < line->px) {
+ out->append(space, spaceLen);
+ ++x;
+ }
+ encodeFragment(line->text, line->len, uMap, primaryLR, out);
+ x += line->pw;
+ }
+ (*outputFunc)(outputStream, out->getCString(), out->getLength());
+ delete out;
+ (*outputFunc)(outputStream, eol, eolLen);
+ if (i + 1 < superLines->getLength()) {
+ superLine1 = (TextSuperLine *)superLines->get(i + 1);
+ if (superLine1->yMin - superLine0->yMax > 1.0 * superLine0->fontSize) {
+ (*outputFunc)(outputStream, eol, eolLen);
+ }
+ }
+ }
+
+ deleteGList(superLines, TextSuperLine);
+}
+
void TextPage::writeLinePrinter(void *outputStream,
TextOutputFunc outputFunc,
UnicodeMap *uMap,
@@ -2027,7 +2213,7 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
TextParagraph *par;
TextLine *line;
TextWord *word;
- double xMin, yMin, xMax, yMax, t;
+ double xMin, yMin, xMax, yMax;
int colIdx, parIdx, lineIdx, wordIdx, i;
switch (rot) {
@@ -2036,9 +2222,8 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
// no transform
break;
case 1:
- t = pageWidth;
- pageWidth = pageHeight;
- pageHeight = t;
+ // NB: this is called after unrotateChars(), which will have
+ // swapped pageWidth and pageHeight already.
for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) {
col = (TextColumn *)columns->get(colIdx);
xMin = pageWidth - col->yMax;
@@ -2074,6 +2259,11 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
line->yMin = yMin;
line->yMax = yMax;
line->rot = (line->rot + 1) & 3;
+ if (!(line->rot & 1)) {
+ for (i = 0; i <= line->len; ++i) {
+ line->edge[i] = pageWidth - line->edge[i];
+ }
+ }
for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
word = (TextWord *)line->words->get(wordIdx);
xMin = pageWidth - word->yMax;
@@ -2085,6 +2275,11 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
word->yMin = yMin;
word->yMax = yMax;
word->rot = (word->rot + 1) & 3;
+ if (!(word->rot & 1)) {
+ for (i = 0; i <= word->len; ++i) {
+ word->edge[i] = pageWidth - word->edge[i];
+ }
+ }
}
}
}
@@ -2126,8 +2321,14 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
line->yMin = yMin;
line->yMax = yMax;
line->rot = (line->rot + 2) & 3;
- for (i = 0; i <= line->len; ++i) {
- line->edge[i] = pageWidth - line->edge[i];
+ if (line->rot & 1) {
+ for (i = 0; i <= line->len; ++i) {
+ line->edge[i] = pageHeight - line->edge[i];
+ }
+ } else {
+ for (i = 0; i <= line->len; ++i) {
+ line->edge[i] = pageWidth - line->edge[i];
+ }
}
for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
word = (TextWord *)line->words->get(wordIdx);
@@ -2140,8 +2341,14 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
word->yMin = yMin;
word->yMax = yMax;
word->rot = (word->rot + 2) & 3;
- for (i = 0; i <= word->len; ++i) {
- word->edge[i] = pageWidth - word->edge[i];
+ if (word->rot & 1) {
+ for (i = 0; i <= word->len; ++i) {
+ word->edge[i] = pageHeight - word->edge[i];
+ }
+ } else {
+ for (i = 0; i <= word->len; ++i) {
+ word->edge[i] = pageWidth - word->edge[i];
+ }
}
}
}
@@ -2149,9 +2356,8 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
}
break;
case 3:
- t = pageWidth;
- pageWidth = pageHeight;
- pageHeight = t;
+ // NB: this is called after unrotateChars(), which will have
+ // swapped pageWidth and pageHeight already.
for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) {
col = (TextColumn *)columns->get(colIdx);
xMin = col->yMin;
@@ -2187,8 +2393,10 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
line->yMin = yMin;
line->yMax = yMax;
line->rot = (line->rot + 3) & 3;
- for (i = 0; i <= line->len; ++i) {
- line->edge[i] = pageHeight - line->edge[i];
+ if (line->rot & 1) {
+ for (i = 0; i <= line->len; ++i) {
+ line->edge[i] = pageHeight - line->edge[i];
+ }
}
for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
word = (TextWord *)line->words->get(wordIdx);
@@ -2201,8 +2409,10 @@ void TextPage::unrotateColumns(GList *columns, int rot) {
word->yMin = yMin;
word->yMax = yMax;
word->rot = (word->rot + 3) & 3;
- for (i = 0; i <= word->len; ++i) {
- word->edge[i] = pageHeight - word->edge[i];
+ if (word->rot & 1) {
+ for (i = 0; i <= word->len; ++i) {
+ word->edge[i] = pageHeight - word->edge[i];
+ }
}
}
}
@@ -2234,6 +2444,11 @@ void TextPage::unrotateWords(GList *words, int rot) {
word->yMin = yMin;
word->yMax = yMax;
word->rot = (word->rot + 1) & 3;
+ if (!(word->rot & 1)) {
+ for (j = 0; j <= word->len; ++j) {
+ word->edge[j] = pageWidth - word->edge[j];
+ }
+ }
}
break;
case 2:
@@ -2248,8 +2463,14 @@ void TextPage::unrotateWords(GList *words, int rot) {
word->yMin = yMin;
word->yMax = yMax;
word->rot = (word->rot + 2) & 3;
- for (j = 0; j <= word->len; ++j) {
- word->edge[j] = pageWidth - word->edge[j];
+ if (word->rot & 1) {
+ for (j = 0; j <= word->len; ++j) {
+ word->edge[j] = pageHeight - word->edge[j];
+ }
+ } else {
+ for (j = 0; j <= word->len; ++j) {
+ word->edge[j] = pageWidth - word->edge[j];
+ }
}
}
break;
@@ -2265,8 +2486,10 @@ void TextPage::unrotateWords(GList *words, int rot) {
word->yMin = yMin;
word->yMax = yMax;
word->rot = (word->rot + 3) & 3;
- for (j = 0; j <= word->len; ++j) {
- word->edge[j] = pageHeight - word->edge[j];
+ if (word->rot & 1) {
+ for (j = 0; j <= word->len; ++j) {
+ word->edge[j] = pageHeight - word->edge[j];
+ }
}
}
break;
@@ -2313,6 +2536,9 @@ void TextPage::removeDuplicates(GList *charsA, int rot) {
fabs(ch2->xMin - ch->xMin) < xDelta &&
fabs(ch2->xMax - ch->xMax) < xDelta &&
fabs(ch2->yMax - ch->yMax) < yDelta) {
+ if (ch2->spaceAfter) {
+ ch->spaceAfter = (char)gTrue;
+ }
charsA->del(j);
} else {
++j;
@@ -2358,7 +2584,9 @@ TextBlock *TextPage::splitChars(GList *charsA) {
chars2 = new GList();
for (i = 0; i < charsA->getLength(); ++i) {
ch = (TextChar *)charsA->get(i);
- if (ch->rot == rot) {
+ if (ch->rot == rot &&
+ !(control.discardInvisibleText && ch->invisible) &&
+ !(control.discardClippedText && ch->clipped)) {
chars2->append(ch);
}
}
@@ -2388,6 +2616,14 @@ TextBlock *TextPage::splitChars(GList *charsA) {
// if the page contains no (unclipped) text, just leave an empty
// column list
if (!tree[0]) {
+ // normally tree[0] is empty only if there is no text at all, but
+ // if the caller didn't do rotation, the rotated trees may be
+ // non-empty, so we need to free them
+ for (rot = 1; rot < 4; ++rot) {
+ if (tree[rot]) {
+ delete tree[rot];
+ }
+ }
delete clippedChars;
return NULL;
}
@@ -2425,177 +2661,83 @@ TextBlock *TextPage::splitChars(GList *charsA) {
TextBlock *TextPage::split(GList *charsA, int rot) {
TextBlock *blk;
GList *chars2, *chars3;
- int *horizProfile, *vertProfile;
- double xMin, yMin, xMax, yMax;
- int xMinI, yMinI, xMaxI, yMaxI;
- int xMinI2, yMinI2, xMaxI2, yMaxI2;
+ GList *horizGaps, *vertGaps;
+ TextGap *gap;
TextChar *ch;
- double minFontSize, avgFontSize, splitPrecision;
- double nLines, vertGapThreshold, ascentAdjust, descentAdjust, minChunk;
- int horizGapSize, vertGapSize;
- double horizGapSize2, vertGapSize2;
- int minHorizChunkWidth, minVertChunkWidth, nHorizGaps, nVertGaps;
+ double xMin, yMin, xMax, yMax, avgFontSize;
+ double horizGapSize, vertGapSize, minHorizChunkWidth, minVertChunkWidth;
+ double nLines, vertGapThreshold, minChunk;
double largeCharSize;
- int nLargeChars;
+ double x0, x1, y0, y1;
+ int nHorizGaps, nVertGaps, nLargeChars;
+ int i;
GBool doHorizSplit, doVertSplit, smallSplit;
- int i, x, y, prev, start;
-
- //----- compute bbox, min font size, average font size, and
- // split precision for this block
-
- xMin = yMin = xMax = yMax = 0; // make gcc happy
- minFontSize = avgFontSize = 0;
- for (i = 0; i < charsA->getLength(); ++i) {
- ch = (TextChar *)charsA->get(i);
- if (i == 0 || ch->xMin < xMin) {
- xMin = ch->xMin;
- }
- if (i == 0 || ch->yMin < yMin) {
- yMin = ch->yMin;
- }
- if (i == 0 || ch->xMax > xMax) {
- xMax = ch->xMax;
- }
- if (i == 0 || ch->yMax > yMax) {
- yMax = ch->yMax;
- }
- avgFontSize += ch->fontSize;
- if (i == 0 || ch->fontSize < minFontSize) {
- minFontSize = ch->fontSize;
- }
- }
- avgFontSize /= charsA->getLength();
- splitPrecision = splitPrecisionMul * minFontSize;
- if (splitPrecision < minSplitPrecision) {
- splitPrecision = minSplitPrecision;
- }
- //----- compute the horizontal and vertical profiles
+ //----- find all horizontal and vertical gaps
- // add some slack to the array bounds to avoid floating point
- // precision problems
- xMinI = (int)floor(xMin / splitPrecision) - 1;
- yMinI = (int)floor(yMin / splitPrecision) - 1;
- xMaxI = (int)floor(xMax / splitPrecision) + 1;
- yMaxI = (int)floor(yMax / splitPrecision) + 1;
- horizProfile = (int *)gmallocn(yMaxI - yMinI + 1, sizeof(int));
- vertProfile = (int *)gmallocn(xMaxI - xMinI + 1, sizeof(int));
- memset(horizProfile, 0, (yMaxI - yMinI + 1) * sizeof(int));
- memset(vertProfile, 0, (xMaxI - xMinI + 1) * sizeof(int));
- 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 / splitPrecision);
- xMaxI2 = (int)floor(ch->xMax / splitPrecision);
- ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin);
- yMinI2 = (int)floor((ch->yMin + ascentAdjust) / splitPrecision);
- descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin);
- yMaxI2 = (int)floor((ch->yMax - descentAdjust) / splitPrecision);
- break;
- case 1:
- descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin);
- xMinI2 = (int)floor((ch->xMin + descentAdjust) / splitPrecision);
- ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin);
- xMaxI2 = (int)floor((ch->xMax - ascentAdjust) / splitPrecision);
- yMinI2 = (int)floor(ch->yMin / splitPrecision);
- yMaxI2 = (int)floor(ch->yMax / splitPrecision);
- break;
- case 2:
- xMinI2 = (int)floor(ch->xMin / splitPrecision);
- xMaxI2 = (int)floor(ch->xMax / splitPrecision);
- descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin);
- yMinI2 = (int)floor((ch->yMin + descentAdjust) / splitPrecision);
- ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin);
- yMaxI2 = (int)floor((ch->yMax - ascentAdjust) / splitPrecision);
- break;
- case 3:
- ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin);
- xMinI2 = (int)floor((ch->xMin + ascentAdjust) / splitPrecision);
- descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin);
- xMaxI2 = (int)floor((ch->xMax - descentAdjust) / splitPrecision);
- yMinI2 = (int)floor(ch->yMin / splitPrecision);
- yMaxI2 = (int)floor(ch->yMax / splitPrecision);
- break;
- }
- for (y = yMinI2; y <= yMaxI2; ++y) {
- ++horizProfile[y - yMinI];
- }
- for (x = xMinI2; x <= xMaxI2; ++x) {
- ++vertProfile[x - xMinI];
- }
- }
+ horizGaps = new GList();
+ vertGaps = new GList();
+ findGaps(charsA, rot, &xMin, &yMin, &xMax, &yMax, &avgFontSize,
+ horizGaps, vertGaps);
- //----- find the largest gaps in the horizontal and vertical profiles
+ //----- find the largest horizontal and vertical gaps
horizGapSize = 0;
- for (start = yMinI; start < yMaxI && !horizProfile[start - yMinI]; ++start) ;
- for (y = start; y < yMaxI; ++y) {
- if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) {
- start = y;
- } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) {
- if (y - start > horizGapSize) {
- horizGapSize = y - start;
- }
+ for (i = 0; i < horizGaps->getLength(); ++i) {
+ gap = (TextGap *)horizGaps->get(i);
+ if (gap->w > horizGapSize) {
+ horizGapSize = gap->w;
}
}
vertGapSize = 0;
- for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ;
- for (x = start; x < xMaxI; ++x) {
- if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) {
- start = x;
- } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) {
- if (x - start > vertGapSize) {
- vertGapSize = x - start;
- }
+ for (i = 0; i < vertGaps->getLength(); ++i) {
+ gap = (TextGap *)vertGaps->get(i);
+ if (gap->w > vertGapSize) {
+ vertGapSize = gap->w;
}
}
- horizGapSize2 = horizGapSize - splitGapSlack * avgFontSize / splitPrecision;
- if (horizGapSize2 < 0.99) {
- horizGapSize2 = 0.99;
- }
- vertGapSize2 = vertGapSize - splitGapSlack * avgFontSize / splitPrecision;
- if (vertGapSize2 < 0.99) {
- vertGapSize2 = 0.99;
- }
//----- count horiz/vert gaps equivalent to largest gaps
- minHorizChunkWidth = yMaxI - yMinI;
+ minHorizChunkWidth = yMax - yMin;
nHorizGaps = 0;
- for (start = yMinI; start < yMaxI && !horizProfile[start - yMinI]; ++start) ;
- prev = start - 1;
- for (y = start; y < yMaxI; ++y) {
- if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) {
- start = y;
- } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) {
- if (y - start > horizGapSize2) {
+ if (horizGaps->getLength() > 0) {
+ y0 = yMin;
+ for (i = 0; i < horizGaps->getLength(); ++i) {
+ gap = (TextGap *)horizGaps->get(i);
+ if (gap->w > horizGapSize - splitGapSlack * avgFontSize) {
++nHorizGaps;
- if (start - prev < minHorizChunkWidth) {
- minHorizChunkWidth = start - prev;
+ y1 = gap->xy - 0.5 * gap->w;
+ if (y1 - y0 < minHorizChunkWidth) {
+ minHorizChunkWidth = y1 - y0;
}
- prev = y;
+ y0 = y1 + gap->w;
}
}
+ y1 = yMax;
+ if (y1 - y0 < minHorizChunkWidth) {
+ minHorizChunkWidth = y1 - y0;
+ }
}
- minVertChunkWidth = xMaxI - xMinI;
+ minVertChunkWidth = xMax - xMin;
nVertGaps = 0;
- for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ;
- prev = start - 1;
- for (x = start; x < xMaxI; ++x) {
- if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) {
- start = x;
- } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) {
- if (x - start > vertGapSize2) {
+ if (vertGaps->getLength() > 0) {
+ x0 = xMin;
+ for (i = 0; i < vertGaps->getLength(); ++i) {
+ gap = (TextGap *)vertGaps->get(i);
+ if (gap->w > vertGapSize - splitGapSlack * avgFontSize) {
++nVertGaps;
- if (start - prev < minVertChunkWidth) {
- minVertChunkWidth = start - prev;
+ x1 = gap->xy - 0.5 * gap->w;
+ if (x1 - x0 < minVertChunkWidth) {
+ minVertChunkWidth = x1 - x0;
}
- prev = x;
+ x0 = x1 + gap->w;
}
}
+ x1 = xMax;
+ if (x1 - x0 < minVertChunkWidth) {
+ minVertChunkWidth = x1 - x0;
+ }
}
//----- compute splitting parameters
@@ -2617,19 +2759,21 @@ TextBlock *TextPage::split(GList *charsA, int rot) {
if (vertGapThreshold < vertGapThresholdTableMin) {
vertGapThreshold = vertGapThresholdTableMin;
}
+ } else if (control.mode == textOutSimpleLayout) {
+ vertGapThreshold = simpleLayoutGapThreshold;
} else {
vertGapThreshold = vertGapThresholdMax + vertGapThresholdSlope * nLines;
if (vertGapThreshold < vertGapThresholdMin) {
vertGapThreshold = vertGapThresholdMin;
}
}
- vertGapThreshold = vertGapThreshold * avgFontSize / splitPrecision;
+ vertGapThreshold = vertGapThreshold * avgFontSize;
// compute the minimum allowed chunk width
if (control.mode == textOutTableLayout) {
minChunk = 0;
} else {
- minChunk = vertSplitChunkThreshold * avgFontSize / splitPrecision;
+ minChunk = vertSplitChunkThreshold * avgFontSize;
}
// look for large chars
@@ -2651,10 +2795,19 @@ TextBlock *TextPage::split(GList *charsA, int rot) {
doHorizSplit = doVertSplit = gFalse;
smallSplit = gFalse;
if (rot & 1) {
- if (nHorizGaps > 0 &&
- (horizGapSize > vertGapSize || control.mode == textOutTableLayout) &&
- horizGapSize > vertGapThreshold &&
- minHorizChunkWidth > minChunk) {
+ if (control.mode == textOutSimpleLayout) {
+ if (nVertGaps > 0) {
+ doVertSplit = gTrue;
+ } else if (nHorizGaps > 0) {
+ doHorizSplit = gTrue;
+ smallSplit = horizGapSize <= vertGapThreshold;
+ }
+ } else if (nHorizGaps > 0 &&
+ (horizGapSize > vertGapSize ||
+ control.mode == textOutTableLayout) &&
+ horizGapSize > vertGapThreshold &&
+ (minHorizChunkWidth > minChunk ||
+ nVertGaps == 0)) {
doHorizSplit = gTrue;
} else if (nVertGaps > 0) {
doVertSplit = gTrue;
@@ -2663,10 +2816,19 @@ TextBlock *TextPage::split(GList *charsA, int rot) {
smallSplit = gTrue;
}
} else {
- if (nVertGaps > 0 &&
- (vertGapSize > horizGapSize || control.mode == textOutTableLayout) &&
- vertGapSize > vertGapThreshold &&
- minVertChunkWidth > minChunk) {
+ if (control.mode == textOutSimpleLayout) {
+ if (nHorizGaps > 0) {
+ doHorizSplit = gTrue;
+ } else if (nVertGaps > 0) {
+ doVertSplit = gTrue;
+ smallSplit = vertGapSize <= vertGapThreshold;
+ }
+ } else if (nVertGaps > 0 &&
+ (vertGapSize > horizGapSize ||
+ control.mode == textOutTableLayout) &&
+ vertGapSize > vertGapThreshold &&
+ (minVertChunkWidth > minChunk ||
+ nHorizGaps == 0)) {
doVertSplit = gTrue;
} else if (nHorizGaps > 0) {
doHorizSplit = gTrue;
@@ -2683,56 +2845,68 @@ TextBlock *TextPage::split(GList *charsA, int rot) {
// split vertically
if (doVertSplit) {
+#if 0 //~debug
+ printf("vert split xMin=%g yMin=%g xMax=%g yMax=%g small=%d\n",
+ xMin, pageHeight - yMax, xMax, pageHeight - yMin, smallSplit);
+ for (i = 0; i < vertGaps->getLength(); ++i) {
+ gap = (TextGap *)vertGaps->get(i);
+ if (gap->w > vertGapSize - splitGapSlack * avgFontSize) {
+ printf(" x=%g\n", gap->xy);
+ }
+ }
+#endif
blk = new TextBlock(blkVertSplit, rot);
blk->smallSplit = smallSplit;
- for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ;
- prev = start - 1;
- for (x = start; x < xMaxI; ++x) {
- if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) {
- start = x;
- } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) {
- if (x - start > vertGapSize2) {
- chars2 = getChars(charsA, (prev + 0.5) * splitPrecision, yMin - 1,
- (start + 1.5) * splitPrecision, yMax + 1);
- blk->addChild(split(chars2, rot));
- delete chars2;
- prev = x;
- }
+ x0 = xMin - 1;
+ for (i = 0; i < vertGaps->getLength(); ++i) {
+ gap = (TextGap *)vertGaps->get(i);
+ if (gap->w > vertGapSize - splitGapSlack * avgFontSize) {
+ x1 = gap->xy;
+ chars2 = getChars(charsA, x0, yMin - 1, x1, yMax + 1);
+ blk->addChild(split(chars2, rot));
+ delete chars2;
+ x0 = x1;
}
}
- chars2 = getChars(charsA, (prev + 0.5) * splitPrecision, yMin - 1,
- xMax + 1, yMax + 1);
+ chars2 = getChars(charsA, x0, yMin - 1, xMax + 1, yMax + 1);
blk->addChild(split(chars2, rot));
delete chars2;
// split horizontally
} else if (doHorizSplit) {
+#if 0 //~debug
+ printf("horiz split xMin=%g yMin=%g xMax=%g yMax=%g small=%d\n",
+ xMin, pageHeight - yMax, xMax, pageHeight - yMin, smallSplit);
+ for (i = 0; i < horizGaps->getLength(); ++i) {
+ gap = (TextGap *)horizGaps->get(i);
+ if (gap->w > horizGapSize - splitGapSlack * avgFontSize) {
+ printf(" y=%g\n", pageHeight - gap->xy);
+ }
+ }
+#endif
blk = new TextBlock(blkHorizSplit, rot);
blk->smallSplit = smallSplit;
- for (start = yMinI;
- start < yMaxI && !horizProfile[start - yMinI];
- ++start) ;
- prev = start - 1;
- for (y = start; y < yMaxI; ++y) {
- if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) {
- start = y;
- } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) {
- if (y - start > horizGapSize2) {
- chars2 = getChars(charsA, xMin - 1, (prev + 0.5) * splitPrecision,
- xMax + 1, (start + 1.5) * splitPrecision);
- blk->addChild(split(chars2, rot));
- delete chars2;
- prev = y;
- }
+ y0 = yMin - 1;
+ for (i = 0; i < horizGaps->getLength(); ++i) {
+ gap = (TextGap *)horizGaps->get(i);
+ if (gap->w > horizGapSize - splitGapSlack * avgFontSize) {
+ y1 = gap->xy;
+ chars2 = getChars(charsA, xMin - 1, y0, xMax + 1, y1);
+ blk->addChild(split(chars2, rot));
+ delete chars2;
+ y0 = y1;
}
}
- chars2 = getChars(charsA, xMin - 1, (prev + 0.5) * splitPrecision,
- xMax + 1, yMax + 1);
+ chars2 = getChars(charsA, xMin - 1, y0, xMax + 1, yMax + 1);
blk->addChild(split(chars2, rot));
delete chars2;
// split into larger and smaller chars
} else if (nLargeChars > 0) {
+#if 0 //~debug
+ printf("large char split xMin=%g yMin=%g xMax=%g yMax=%g\n",
+ xMin, pageHeight - yMax, xMax, pageHeight - yMin);
+#endif
chars2 = new GList();
chars3 = new GList();
for (i = 0; i < charsA->getLength(); ++i) {
@@ -2750,14 +2924,18 @@ TextBlock *TextPage::split(GList *charsA, int rot) {
// 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);
+#endif
blk = new TextBlock(blkLeaf, rot);
for (i = 0; i < charsA->getLength(); ++i) {
- blk->addChild((TextChar *)charsA->get(i));
+ blk->addChild((TextChar *)charsA->get(i), gTrue);
}
}
- gfree(horizProfile);
- gfree(vertProfile);
+ deleteGList(horizGaps, TextGap);
+ deleteGList(vertGaps, TextGap);
tagBlock(blk);
@@ -2787,6 +2965,148 @@ GList *TextPage::getChars(GList *charsA, double xMin, double yMin,
return ret;
}
+void TextPage::findGaps(GList *charsA, int rot,
+ double *xMinOut, double *yMinOut,
+ double *xMaxOut, double *yMaxOut,
+ double *avgFontSizeOut,
+ GList *horizGaps, GList *vertGaps) {
+ TextChar *ch;
+ int *horizProfile, *vertProfile;
+ double xMin, yMin, xMax, yMax, w;
+ double minFontSize, avgFontSize, splitPrecision, 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
+
+ xMin = yMin = xMax = yMax = 0; // make gcc happy
+ minFontSize = avgFontSize = 0;
+ for (i = 0; i < charsA->getLength(); ++i) {
+ ch = (TextChar *)charsA->get(i);
+ if (i == 0 || ch->xMin < xMin) {
+ xMin = ch->xMin;
+ }
+ if (i == 0 || ch->yMin < yMin) {
+ yMin = ch->yMin;
+ }
+ if (i == 0 || ch->xMax > xMax) {
+ xMax = ch->xMax;
+ }
+ if (i == 0 || ch->yMax > yMax) {
+ yMax = ch->yMax;
+ }
+ avgFontSize += ch->fontSize;
+ if (i == 0 || ch->fontSize < minFontSize) {
+ minFontSize = ch->fontSize;
+ }
+ }
+ avgFontSize /= charsA->getLength();
+ splitPrecision = splitPrecisionMul * minFontSize;
+ if (splitPrecision < minSplitPrecision) {
+ splitPrecision = minSplitPrecision;
+ }
+ *xMinOut = xMin;
+ *yMinOut = yMin;
+ *xMaxOut = xMax;
+ *yMaxOut = yMax;
+ *avgFontSizeOut = avgFontSize;
+
+ //----- compute the horizontal and vertical profiles
+
+ // add some slack to the array bounds to avoid floating point
+ // precision problems
+ xMinI = (int)floor(xMin / splitPrecision) - 1;
+ yMinI = (int)floor(yMin / splitPrecision) - 1;
+ xMaxI = (int)floor(xMax / splitPrecision) + 1;
+ yMaxI = (int)floor(yMax / splitPrecision) + 1;
+ horizProfile = (int *)gmallocn(yMaxI - yMinI + 1, sizeof(int));
+ vertProfile = (int *)gmallocn(xMaxI - xMinI + 1, sizeof(int));
+ memset(horizProfile, 0, (yMaxI - yMinI + 1) * sizeof(int));
+ memset(vertProfile, 0, (xMaxI - xMinI + 1) * sizeof(int));
+ 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 / splitPrecision);
+ xMaxI2 = (int)floor(ch->xMax / splitPrecision);
+ ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin);
+ yMinI2 = (int)floor((ch->yMin + ascentAdjust) / splitPrecision);
+ descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin);
+ yMaxI2 = (int)floor((ch->yMax - descentAdjust) / splitPrecision);
+ break;
+ case 1:
+ descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin);
+ xMinI2 = (int)floor((ch->xMin + descentAdjust) / splitPrecision);
+ ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin);
+ xMaxI2 = (int)floor((ch->xMax - ascentAdjust) / splitPrecision);
+ yMinI2 = (int)floor(ch->yMin / splitPrecision);
+ yMaxI2 = (int)floor(ch->yMax / splitPrecision);
+ break;
+ case 2:
+ xMinI2 = (int)floor(ch->xMin / splitPrecision);
+ xMaxI2 = (int)floor(ch->xMax / splitPrecision);
+ descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin);
+ yMinI2 = (int)floor((ch->yMin + descentAdjust) / splitPrecision);
+ ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin);
+ yMaxI2 = (int)floor((ch->yMax - ascentAdjust) / splitPrecision);
+ break;
+ case 3:
+ ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin);
+ xMinI2 = (int)floor((ch->xMin + ascentAdjust) / splitPrecision);
+ descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin);
+ xMaxI2 = (int)floor((ch->xMax - descentAdjust) / splitPrecision);
+ yMinI2 = (int)floor(ch->yMin / splitPrecision);
+ yMaxI2 = (int)floor(ch->yMax / splitPrecision);
+ break;
+ }
+ for (y = yMinI2; y <= yMaxI2; ++y) {
+ ++horizProfile[y - yMinI];
+ }
+ for (x = xMinI2; x <= xMaxI2; ++x) {
+ ++vertProfile[x - xMinI];
+ }
+ }
+
+ //----- build the list of 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;
+ }
+ } else {
+ if (horizProfile[y + 1 - yMinI]) {
+ w = (y - start) * splitPrecision;
+ horizGaps->append(new TextGap((start + 1) * splitPrecision + 0.5 * w,
+ w));
+ }
+ }
+ }
+
+ //----- build the list of vertical gaps
+
+ 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;
+ }
+ } else {
+ if (vertProfile[x + 1 - xMinI]) {
+ w = (x - start) * splitPrecision;
+ vertGaps->append(new TextGap((start + 1) * splitPrecision + 0.5 * w,
+ w));
+ }
+ }
+ }
+
+ gfree(horizProfile);
+ gfree(vertProfile);
+}
+
// Decide whether this block is a line, column, or multiple columns:
// - all leaf nodes are lines
// - horiz split nodes whose children are lines or columns are columns
@@ -2798,6 +3118,11 @@ GList *TextPage::getChars(GList *charsA, double xMin, double yMin,
// - all leaf nodes are lines
// - vert split nodes, with small gaps, whose children are lines are lines
// - everything else is multiple columns
+// In simple layout mode:
+// - all leaf nodes are lines
+// - vert split nodes with small gaps are lines
+// - vert split nodes with large gaps are super-lines
+// - horiz split nodes are columns
void TextPage::tagBlock(TextBlock *blk) {
TextBlock *child;
int i;
@@ -2821,6 +3146,17 @@ void TextPage::tagBlock(TextBlock *blk) {
return;
}
+ if (control.mode == textOutSimpleLayout) {
+ if (blk->type == blkLeaf) {
+ blk->tag = blkTagLine;
+ } else if (blk->type == ((blk->rot & 1) ? blkHorizSplit : blkVertSplit)) {
+ blk->tag = blk->smallSplit ? blkTagLine : blkTagSuperLine;
+ } else {
+ blk->tag = blkTagColumn;
+ }
+ return;
+ }
+
if (blk->type == blkLeaf) {
blk->tag = blkTagLine;
@@ -2855,33 +3191,23 @@ void TextPage::tagBlock(TextBlock *blk) {
void TextPage::insertLargeChars(GList *largeChars, TextBlock *blk) {
TextChar *ch, *ch2;
GBool singleLine;
- double xLimit, yLimit, minOverlap;
+ double minOverlap;
int i;
//~ this currently works only for characters in the primary rotation
- // check to see if the large chars are a single line, in the
- // upper-left corner of blk (this is just a rough estimate)
- xLimit = blk->xMin + 0.5 * (blk->xMin + blk->xMax);
- yLimit = blk->yMin + 0.5 * (blk->yMin + blk->yMax);
+ // check to see if the large chars are a single line
singleLine = gTrue;
- // note: largeChars are already sorted by x
- for (i = 0; i < largeChars->getLength(); ++i) {
+ for (i = 1; i < largeChars->getLength(); ++i) {
+ ch = (TextChar *)largeChars->get(i-1);
ch2 = (TextChar *)largeChars->get(i);
- if (ch2->xMax > xLimit || ch2->yMax > yLimit) {
+ minOverlap = 0.5 * (ch->fontSize < ch2->fontSize ? ch->fontSize
+ : ch2->fontSize);
+ if (ch->yMax - ch2->yMin < minOverlap ||
+ ch2->yMax - ch->yMin < minOverlap) {
singleLine = gFalse;
break;
}
- if (i > 0) {
- ch = (TextChar *)largeChars->get(i-1);
- minOverlap = 0.5 * (ch->fontSize < ch2->fontSize ? ch->fontSize
- : ch2->fontSize);
- if (ch->yMax - ch2->yMin < minOverlap ||
- ch2->yMax - ch->yMin < minOverlap) {
- singleLine = gFalse;
- break;
- }
- }
}
if (singleLine) {
@@ -3043,7 +3369,7 @@ void TextPage::insertClippedChars(GList *clippedChars, TextBlock *tree) {
if (!(leaf = findClippedCharLeaf(ch, tree))) {
continue;
}
- leaf->addChild(ch);
+ leaf->addChild(ch, gFalse);
i = 0;
while (i < clippedChars->getLength()) {
ch2 = (TextChar *)clippedChars->get(i);
@@ -3053,7 +3379,7 @@ void TextPage::insertClippedChars(GList *clippedChars, TextBlock *tree) {
y = 0.5 * (ch2->yMin + ch2->yMax);
if (y > leaf->yMin && y < leaf->yMax) {
ch2 = (TextChar *)clippedChars->del(i);
- leaf->addChild(ch2);
+ leaf->addChild(ch2, gFalse);
ch = ch2;
} else {
++i;
@@ -3091,28 +3417,39 @@ TextBlock *TextPage::findClippedCharLeaf(TextChar *ch, TextBlock *tree) {
}
// Convert the tree of TextBlocks into a list of TextColumns.
-GList *TextPage::buildColumns(TextBlock *tree) {
+GList *TextPage::buildColumns(TextBlock *tree, GBool primaryLR) {
GList *columns;
columns = new GList();
- buildColumns2(tree, columns);
+ buildColumns2(tree, columns, primaryLR);
return columns;
}
-void TextPage::buildColumns2(TextBlock *blk, GList *columns) {
+void TextPage::buildColumns2(TextBlock *blk, GList *columns, GBool primaryLR) {
TextColumn *col;
int i;
switch (blk->tag) {
+ case blkTagSuperLine: // should never happen
case blkTagLine:
case blkTagColumn:
col = buildColumn(blk);
columns->append(col);
break;
case blkTagMulticolumn:
- for (i = 0; i < blk->children->getLength(); ++i) {
- buildColumns2((TextBlock *)blk->children->get(i), columns);
+#if 0 //~tmp
+ if (!primaryLR && blk->type == blkVertSplit) {
+ for (i = blk->children->getLength() - 1; i >= 0; --i) {
+ buildColumns2((TextBlock *)blk->children->get(i), columns, primaryLR);
+ }
+ } else {
+#endif
+ for (i = 0; i < blk->children->getLength(); ++i) {
+ buildColumns2((TextBlock *)blk->children->get(i), columns, primaryLR);
+ }
+#if 0 //~tmp
}
+#endif
break;
}
}
@@ -3121,6 +3458,7 @@ TextColumn *TextPage::buildColumn(TextBlock *blk) {
GList *lines, *parLines;
GList *paragraphs;
TextLine *line0, *line1;
+ GBool dropCap;
double spaceThresh, indent0, indent1, fontSize0, fontSize1;
int i;
@@ -3132,6 +3470,9 @@ TextColumn *TextPage::buildColumn(TextBlock *blk) {
//~ could look for bulleted lists here: look for the case where
//~ all out-dented lines start with the same char
+ //~ this doesn't handle right-to-left scripts (need to look for indents
+ //~ on the right instead of left, etc.)
+
// build the paragraphs
paragraphs = new GList();
i = 0;
@@ -3139,6 +3480,7 @@ TextColumn *TextPage::buildColumn(TextBlock *blk) {
// get the first line of the paragraph
parLines = new GList();
+ dropCap = gFalse;
line0 = (TextLine *)lines->get(i);
parLines->append(line0);
++i;
@@ -3177,6 +3519,7 @@ TextColumn *TextPage::buildColumn(TextBlock *blk) {
} else if (fontSize0 > largeCharThreshold * fontSize1 &&
indent1 - indent0 > minParagraphIndent * fontSize1 &&
getLineSpacing(line0, line1) < 0) {
+ dropCap = gTrue;
parLines->append(line1);
fontSize0 = fontSize1;
for (++i; i < lines->getLength(); ++i) {
@@ -3232,7 +3575,7 @@ TextColumn *TextPage::buildColumn(TextBlock *blk) {
}
}
- paragraphs->append(new TextParagraph(parLines));
+ paragraphs->append(new TextParagraph(parLines, dropCap));
}
delete lines;
@@ -3302,6 +3645,7 @@ void TextPage::buildLines(TextBlock *blk, GList *lines) {
lines->append(line);
}
break;
+ case blkTagSuperLine:
case blkTagColumn:
case blkTagMulticolumn: // multicolumn should never happen here
for (i = 0; i < blk->children->getLength(); ++i) {
@@ -3317,7 +3661,8 @@ TextLine *TextPage::buildLine(TextBlock *blk) {
TextChar *ch, *ch2;
TextWord *word;
double wordSp, lineFontSize, sp;
- GBool spaceAfter, spaceAfter2;
+ int dir, dir2;
+ GBool spaceAfter, spaceBefore;
int i, j;
charsA = new GList();
@@ -3327,27 +3672,42 @@ TextLine *TextPage::buildLine(TextBlock *blk) {
words = new GList();
lineFontSize = 0;
- spaceAfter = gFalse;
+ spaceBefore = gFalse;
i = 0;
while (i < charsA->getLength()) {
sp = wordSp - 1;
+ spaceAfter = gFalse;
+ dir = getCharDirection((TextChar *)charsA->get(i));
for (j = i+1; j < charsA->getLength(); ++j) {
ch = (TextChar *)charsA->get(j-1);
ch2 = (TextChar *)charsA->get(j);
sp = (blk->rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax);
- if (sp > wordSp ||
- ch->font != ch2->font ||
+ if (sp > wordSp) {
+ spaceAfter = gTrue;
+ break;
+ }
+ // look for significant overlaps, which can happen with clipped
+ // characters (among other things)
+ if (sp < -ch->fontSize) {
+ spaceAfter = gTrue;
+ break;
+ }
+ dir2 = getCharDirection(ch2);
+ if (ch->font != ch2->font ||
fabs(ch->fontSize - ch2->fontSize) > 0.01 ||
+ (dir && dir2 && dir2 != dir) ||
(control.mode == textOutRawOrder &&
ch2->charPos != ch->charPos + ch->charLen)) {
break;
}
+ if (!dir && dir2) {
+ dir = dir2;
+ }
sp = wordSp - 1;
}
- spaceAfter2 = spaceAfter;
- spaceAfter = sp > wordSp;
- word = new TextWord(charsA, i, j - i, blk->rot,
- (blk->rot >= 2) ? spaceAfter2 : spaceAfter);
+ word = new TextWord(charsA, i, j - i, blk->rot, dir,
+ (blk->rot >= 2) ? spaceBefore : spaceAfter);
+ spaceBefore = spaceAfter;
i = j;
if (blk->rot >= 2) {
words->insert(0, word);
@@ -3382,47 +3742,96 @@ void TextPage::getLineChars(TextBlock *blk, GList *charsA) {
// spaces.
double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) {
TextChar *ch, *ch2;
- double avgFontSize, minSp, maxSp, sp;
+ double avgFontSize;
+ double minAdjGap, maxAdjGap, minSpGap, maxSpGap, minGap, maxGap, gap, gap2;
int i;
avgFontSize = 0;
- minSp = maxSp = 0;
+ minGap = maxGap = 0;
+ minAdjGap = minSpGap = 1;
+ maxAdjGap = maxSpGap = 0;
for (i = 0; i < charsA->getLength(); ++i) {
ch = (TextChar *)charsA->get(i);
avgFontSize += ch->fontSize;
if (i < charsA->getLength() - 1) {
ch2 = (TextChar *)charsA->get(i+1);
- sp = (rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax);
- if (i == 0 || sp < minSp) {
- minSp = sp;
+ gap = (rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax);
+ if (ch->spaceAfter) {
+ if (minSpGap > maxSpGap) {
+ minSpGap = maxSpGap = gap;
+ } else if (gap < minSpGap) {
+ minSpGap = gap;
+ } else if (gap > maxSpGap) {
+ maxSpGap = gap;
+ }
+ } else {
+ if (minAdjGap > maxAdjGap) {
+ minAdjGap = maxAdjGap = gap;
+ } else if (gap < minAdjGap) {
+ minAdjGap = gap;
+ } else if (gap > maxAdjGap) {
+ maxAdjGap = gap;
+ }
}
- if (sp > maxSp) {
- maxSp = sp;
+ if (i == 0 || gap < minGap) {
+ minGap = gap;
+ }
+ if (gap > maxGap) {
+ maxGap = gap;
}
}
}
avgFontSize /= charsA->getLength();
- if (minSp < 0) {
- minSp = 0;
+ if (minGap < 0) {
+ minGap = 0;
}
- // if spacing is completely uniform, assume it's a single word
- // (technically it could be either "ABC" or "A B C", but it's
+ // if spacing is nearly uniform (minGap is close to maxGap), use the
+ // SpGap/AdjGap values if available, otherwise assume it's a single
+ // word (technically it could be either "ABC" or "A B C", but it's
// essentially impossible to tell)
- if (maxSp - minSp < uniformSpacing * avgFontSize) {
- return maxSp + 1;
+ if (maxGap - minGap < uniformSpacing * avgFontSize) {
+ if (minAdjGap <= maxAdjGap &&
+ minSpGap <= maxSpGap &&
+ minSpGap - maxAdjGap > 0.01) {
+ return 0.5 * (maxAdjGap + minSpGap);
+ } else {
+ return maxGap + 1;
+ }
// if there is some variation in spacing, but it's small, assume
// there are some inter-word spaces
- } else if (maxSp - minSp < wordSpacing * avgFontSize) {
- return 0.5 * (minSp + maxSp);
-
- // otherwise, assume a reasonable threshold for inter-word spacing
- // (we can't use something like 0.5*(minSp+maxSp) here because there
- // can be outliers at the high end)
+ } else if (maxGap - minGap < wordSpacing * avgFontSize) {
+ return 0.5 * (minGap + maxGap);
+
+ // if there is a large variation in spacing, use the SpGap/AdjGap
+ // values if they look reasonable, otherwise, assume a reasonable
+ // threshold for inter-word spacing (we can't use something like
+ // 0.5*(minGap+maxGap) here because there can be outliers at the
+ // high end)
} else {
- return minSp + wordSpacing * avgFontSize;
+ if (minAdjGap <= maxAdjGap &&
+ minSpGap <= maxSpGap &&
+ minSpGap - maxAdjGap > uniformSpacing * avgFontSize) {
+ gap = wordSpacing * avgFontSize;
+ gap2 = 0.5 * (minSpGap - minGap);
+ return minGap + (gap < gap2 ? gap : gap2);
+ } else {
+ return minGap + wordSpacing * avgFontSize;
+ }
+ }
+}
+
+// Check the characters 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)) {
+ return 1;
+ }
+ if (unicodeTypeR(ch->c)) {
+ return -1;
}
+ return 0;
}
int TextPage::assignPhysLayoutPositions(GList *columns) {
@@ -3567,6 +3976,66 @@ int TextPage::assignColumnPhysPositions(GList *columns) {
return ph;
}
+void TextPage::buildSuperLines(TextBlock *blk, GList *superLines) {
+ GList *lines;
+ int i;
+
+ if (blk->tag == blkTagLine) {
+ lines = new GList();
+ buildLines(blk, lines);
+ superLines->append(new TextSuperLine(lines));
+ } else if (blk->tag == blkTagSuperLine) {
+ lines = new GList();
+ for (i = 0; i < blk->children->getLength(); ++i) {
+ buildLines((TextBlock *)blk->children->get(i), lines);
+ }
+ superLines->append(new TextSuperLine(lines));
+ } else {
+ for (i = 0; i < blk->children->getLength(); ++i) {
+ buildSuperLines((TextBlock *)blk->children->get(i), superLines);
+ }
+ }
+}
+
+void TextPage::assignSimpleLayoutPositions(GList *superLines,
+ UnicodeMap *uMap) {
+ GList *lines;
+ TextLine *line0, *line1;
+ double xMin;
+ int px, sp, i, j;
+
+ // build a list of lines and sort by x
+ lines = new GList();
+ for (i = 0; i < superLines->getLength(); ++i) {
+ lines->append(((TextSuperLine *)superLines->get(i))->lines);
+ }
+ lines->sort(&TextLine::cmpX);
+
+ // assign positions
+ xMin = ((TextLine *)lines->get(0))->xMin;
+ for (i = 0; i < lines->getLength(); ++i) {
+ line0 = (TextLine *)lines->get(i);
+ computeLinePhysWidth(line0, uMap);
+ line0->px = (int)((line0->xMin - xMin) / (0.5 * line0->fontSize));
+ for (j = 0; j < i; ++j) {
+ line1 = (TextLine *)lines->get(j);
+ if (line0->xMin > line1->xMax) {
+ sp = (int)((line0->xMin - line1->xMax) /
+ (0.5 * line0->fontSize) + 0.5);
+ if (sp < 1) {
+ sp = 1;
+ }
+ px = line1->px + line1->pw + sp;
+ if (px > line0->px) {
+ line0->px = px;
+ }
+ }
+ }
+ }
+
+ delete lines;
+}
+
void TextPage::generateUnderlinesAndLinks(GList *columns) {
TextColumn *col;
TextParagraph *par;
@@ -3639,7 +4108,6 @@ GBool TextPage::findText(Unicode *s, int len,
GBool wholeWord,
double *xMin, double *yMin,
double *xMax, double *yMax) {
- TextBlock *tree;
TextColumn *column;
TextParagraph *par;
TextLine *line;
@@ -3649,22 +4117,12 @@ GBool TextPage::findText(Unicode *s, int len,
double xMin0, yMin0, xMax0, yMax0;
double xMin1, yMin1, xMax1, yMax1;
GBool found;
- int txtSize, m, rot, colIdx, parIdx, lineIdx, i, j, k;
+ int txtSize, m, colIdx, parIdx, lineIdx, i, j, k;
//~ need to handle right-to-left text
+ //~ - pass primaryLR to buildColumns
- if (!findCols) {
- rot = rotateChars(chars);
- if ((tree = splitChars(chars))) {
- findCols = buildColumns(tree);
- delete tree;
- } else {
- // no text
- findCols = new GList();
- }
- unrotateChars(chars, rot);
- unrotateColumns(findCols, rot);
- }
+ buildFindCols();
// convert the search string to uppercase
if (!caseSensitive) {
@@ -3870,7 +4328,7 @@ GBool TextPage::findText(Unicode *s, int len,
}
GString *TextPage::getText(double xMin, double yMin,
- double xMax, double yMax) {
+ double xMax, double yMax, GBool forceEOL) {
UnicodeMap *uMap;
char space[8], eol[16];
int spaceLen, eolLen;
@@ -3933,7 +4391,7 @@ GString *TextPage::getText(double xMin, double yMin,
#if 0 //~debug
dumpTree(tree);
#endif
- columns = buildColumns(tree);
+ columns = buildColumns(tree, primaryLR);
delete tree;
ph = assignPhysLayoutPositions(columns);
#if 0 //~debug
@@ -3984,7 +4442,7 @@ GString *TextPage::getText(double xMin, double yMin,
ret->append(out[i]);
delete out[i];
}
- if (ph > 1) {
+ if (ph > 1 || forceEOL) {
ret->append(eol, eolLen);
}
}
@@ -4039,6 +4497,216 @@ GBool TextPage::findCharRange(int pos, int length,
return gTrue;
}
+GBool TextPage::findPointInside(double x, double y, TextPosition *pos) {
+ TextColumn *col;
+ int colIdx;
+
+ buildFindCols();
+
+ //~ this doesn't handle RtL, vertical, or rotated text
+ //~ this doesn't handle drop caps
+
+ for (colIdx = 0; colIdx < findCols->getLength(); ++colIdx) {
+ col = (TextColumn *)findCols->get(colIdx);
+ if (col->getRotation() != 0) {
+ continue;
+ }
+ if (x >= col->getXMin() && x <= col->getXMax() &&
+ y >= col->getYMin() && y <= col->getYMax()) {
+ pos->colIdx = colIdx;
+ findPointInColumn(col, x, y, pos);
+ return gTrue;
+ }
+ }
+ return gFalse;
+}
+
+GBool TextPage::findPointNear(double x, double y, TextPosition *pos) {
+ TextColumn *col;
+ double nearestDist, dx, dy;
+ int nearestColIdx, colIdx;
+
+ buildFindCols();
+
+ //~ this doesn't handle RtL, vertical, or rotated text
+ //~ this doesn't handle drop caps
+
+ nearestColIdx = -1;
+ nearestDist = 0;
+ for (colIdx = 0; colIdx < findCols->getLength(); ++colIdx) {
+ col = (TextColumn *)findCols->get(colIdx);
+ if (col->getRotation() != 0) {
+ continue;
+ }
+ if (x < col->getXMin()) {
+ dx = col->getXMin() - x;
+ } else if (x > col->getXMax()) {
+ dx = x - col->getXMax();
+ } else {
+ dx = 0;
+ }
+ if (y < col->getYMin()) {
+ dy = col->getYMin() - y;
+ } else if (y > col->getYMax()) {
+ dy = y - col->getYMax();
+ } else {
+ dy = 0;
+ }
+ if (nearestColIdx < 0 || dx + dy < nearestDist) {
+ nearestColIdx = colIdx;
+ nearestDist = dx + dy;
+ }
+ }
+ if (nearestColIdx < 0) {
+ return gFalse;
+ }
+ pos->colIdx = nearestColIdx;
+ col = (TextColumn *)findCols->get(nearestColIdx);
+ findPointInColumn(col, x, y, pos);
+ return gTrue;
+}
+
+// Find the position in [col] corresponding to [x],[y]. The column,
+// [col], was found by findPointInside() or findPointNear().
+void TextPage::findPointInColumn(TextColumn *col, double x, double y,
+ TextPosition *pos) {
+ TextParagraph *par;
+ TextLine *line;
+ GList *pars, *lines;
+ int parIdx, lineIdx, charIdx;
+
+ //~ this doesn't handle RtL, vertical, or rotated text
+ //~ this doesn't handle drop caps
+
+ pars = col->getParagraphs();
+ //~ could use a binary search here
+ for (parIdx = 0; parIdx < pars->getLength() - 1; ++parIdx) {
+ par = (TextParagraph *)pars->get(parIdx);
+ if (y <= par->getYMax()) {
+ break;
+ }
+ }
+ par = (TextParagraph *)pars->get(parIdx);
+
+ lines = par->getLines();
+ //~ could use a binary search here
+ for (lineIdx = 0; lineIdx < lines->getLength() - 1; ++lineIdx) {
+ line = (TextLine *)lines->get(lineIdx);
+ if (y <= line->getYMax()) {
+ break;
+ }
+ }
+ line = (TextLine *)lines->get(lineIdx);
+
+ //~ could use a binary search here
+ for (charIdx = 0; charIdx < line->getLength(); ++charIdx) {
+ if (x <= 0.5 * (line->getEdge(charIdx) + line->getEdge(charIdx + 1))) {
+ break;
+ }
+ }
+
+ pos->parIdx = parIdx;
+ pos->lineIdx = lineIdx;
+ pos->charIdx = charIdx;
+}
+
+void TextPage::convertPosToPointUpper(TextPosition *pos,
+ double *x, double *y) {
+ TextColumn *col;
+ TextParagraph *par;
+ TextLine *line;
+
+ buildFindCols();
+ col = (TextColumn *)findCols->get(pos->colIdx);
+ par = (TextParagraph *)col->getParagraphs()->get(pos->parIdx);
+ line = (TextLine *)par->getLines()->get(pos->lineIdx);
+ *x = line->getEdge(pos->charIdx);
+ *y = line->getBaseline() - selectionAscent * line->fontSize;
+}
+
+void TextPage::convertPosToPointLower(TextPosition *pos,
+ double *x, double *y) {
+ TextColumn *col;
+ TextParagraph *par;
+ TextLine *line;
+
+ buildFindCols();
+ col = (TextColumn *)findCols->get(pos->colIdx);
+ par = (TextParagraph *)col->getParagraphs()->get(pos->parIdx);
+ line = (TextLine *)par->getLines()->get(pos->lineIdx);
+ *x = line->getEdge(pos->charIdx);
+ *y = line->getYMax();
+}
+
+void TextPage::convertPosToPointLeftEdge(TextPosition *pos,
+ double *x, double *y) {
+ TextColumn *col;
+ TextParagraph *par;
+ TextLine *line;
+
+ buildFindCols();
+ col = (TextColumn *)findCols->get(pos->colIdx);
+ par = (TextParagraph *)col->getParagraphs()->get(pos->parIdx);
+ line = (TextLine *)par->getLines()->get(pos->lineIdx);
+ *x = col->getXMin();
+ *y = line->getBaseline() - selectionAscent * line->fontSize;
+}
+
+void TextPage::convertPosToPointRightEdge(TextPosition *pos,
+ double *x, double *y) {
+ TextColumn *col;
+ TextParagraph *par;
+ TextLine *line;
+
+ buildFindCols();
+ col = (TextColumn *)findCols->get(pos->colIdx);
+ par = (TextParagraph *)col->getParagraphs()->get(pos->parIdx);
+ line = (TextLine *)par->getLines()->get(pos->lineIdx);
+ *x = col->getXMax();
+ *y = line->getYMax();
+}
+
+void TextPage::getColumnUpperRight(int colIdx, double *x, double *y) {
+ TextColumn *col;
+ TextParagraph *par0;
+ TextLine *line0;
+
+ buildFindCols();
+ col = (TextColumn *)findCols->get(colIdx);
+ *x = col->getXMax();
+ par0 = (TextParagraph *)col->paragraphs->get(0);
+ line0 = (TextLine *)par0->lines->get(0);
+ *y = line0->getBaseline() - selectionAscent * line0->fontSize;
+}
+
+void TextPage::getColumnLowerLeft(int colIdx, double *x, double *y) {
+ TextColumn *col;
+
+ buildFindCols();
+ col = (TextColumn *)findCols->get(colIdx);
+ *x = col->getXMin();
+ *y = col->getYMax();
+}
+
+void TextPage::buildFindCols() {
+ TextBlock *tree;
+ int rot;
+
+ if (findCols) {
+ return;
+ }
+ rot = rotateChars(chars);
+ if ((tree = splitChars(chars))) {
+ findCols = buildColumns(tree, gFalse);
+ delete tree;
+ } else {
+ // no text
+ findCols = new GList();
+ }
+ unrotateChars(chars, rot);
+ unrotateColumns(findCols, rot);
+}
+
TextWordList *TextPage::makeWordList() {
TextBlock *tree;
GList *columns;
@@ -4047,16 +4715,21 @@ TextWordList *TextPage::makeWordList() {
TextLine *line;
TextWord *word;
GList *words;
+ GBool primaryLR;
int rot, colIdx, parIdx, lineIdx, wordIdx;
rot = rotateChars(chars);
+ primaryLR = checkPrimaryLR(chars);
tree = splitChars(chars);
if (!tree) {
// no text
unrotateChars(chars, rot);
- return new TextWordList(new GList());
+ return new TextWordList(new GList(), gTrue);
}
- columns = buildColumns(tree);
+ columns = buildColumns(tree, primaryLR);
+#if 0 //~debug
+ dumpColumns(columns, gTrue);
+#endif
delete tree;
unrotateChars(chars, rot);
if (control.html) {
@@ -4084,6 +4757,7 @@ TextWordList *TextPage::makeWordList() {
// already in reading order
break;
case textOutPhysLayout:
+ case textOutSimpleLayout:
case textOutTableLayout:
case textOutLinePrinter:
words->sort(&TextWord::cmpYX);
@@ -4094,12 +4768,15 @@ TextWordList *TextPage::makeWordList() {
}
// this has to be done after sorting with cmpYX
- unrotateColumns(columns, rot);
unrotateWords(words, rot);
deleteGList(columns, TextColumn);
- return new TextWordList(words);
+ return new TextWordList(words, primaryLR);
+}
+
+GBool TextPage::primaryDirectionIsLR() {
+ return checkPrimaryLR(chars);
}
//------------------------------------------------------------------------
@@ -4114,9 +4791,9 @@ void TextPage::dumpChars(GList *charsA) {
for (i = 0; i < charsA->getLength(); ++i) {
ch = (TextChar *)charsA->get(i);
- printf("char: U+%04x '%c' xMin=%g yMin=%g xMax=%g yMax=%g fontSize=%g rot=%d\n",
+ printf("char: U+%04x '%c' xMin=%g yMin=%g xMax=%g yMax=%g fontSize=%g rot=%d charPos=%d charLen=%d\n",
ch->c, ch->c & 0xff, ch->xMin, ch->yMin, ch->xMax, ch->yMax,
- ch->fontSize, ch->rot);
+ ch->fontSize, ch->rot, ch->charPos, ch->charLen);
}
}
@@ -4147,11 +4824,12 @@ void TextPage::dumpTree(TextBlock *tree, int indent) {
}
}
-void TextPage::dumpColumns(GList *columns) {
+void TextPage::dumpColumns(GList *columns, GBool dumpWords) {
TextColumn *col;
TextParagraph *par;
TextLine *line;
- int colIdx, parIdx, lineIdx, i;
+ TextWord *word;
+ int colIdx, parIdx, lineIdx, wordIdx, i;
for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) {
col = (TextColumn *)columns->get(colIdx);
@@ -4166,16 +4844,40 @@ void TextPage::dumpColumns(GList *columns) {
printf(" line: xMin=%g yMin=%g xMax=%g yMax=%g px=%d pw=%d rot=%d\n",
line->xMin, line->yMin, line->xMax, line->yMax,
line->px, line->pw, line->rot);
- printf(" ");
- for (i = 0; i < line->len; ++i) {
- printf("%c", line->text[i] & 0xff);
+ if (dumpWords) {
+ for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
+ word = (TextWord *)line->words->get(wordIdx);
+ printf(" word: xMin=%g yMin=%g xMax=%g yMax=%g\n",
+ word->xMin, word->yMin, word->xMax, word->yMax);
+ printf(" ");
+ for (i = 0; i < word->len; ++i) {
+ printf("%c", word->text[i] & 0xff);
+ }
+ printf("\n");
+ }
+ } else {
+ printf(" ");
+ for (i = 0; i < line->len; ++i) {
+ printf("%c", line->text[i] & 0xff);
+ }
+ printf("\n");
}
- printf("\n");
}
}
}
}
+void TextPage::dumpUnderlines() {
+ TextUnderline *u;
+ int i;
+
+ printf("underlines:\n");
+ for (i = 0; i < underlines->getLength(); ++i) {
+ u = (TextUnderline *)underlines->get(i);
+ printf(" horiz=%d x0=%g y0=%g x1=%g y1=%g\n",
+ u->horiz, u->x0, u->y0, u->x1, u->y1);
+ }
+}
#endif //~debug
//------------------------------------------------------------------------
@@ -4215,6 +4917,7 @@ TextOutputDev::TextOutputDev(char *fileName, TextOutputControl *controlA,
// set up text object
text = new TextPage(&control);
+ generateBOM();
}
TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream,
@@ -4224,6 +4927,7 @@ TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream,
needClose = gFalse;
control = *controlA;
text = new TextPage(&control);
+ generateBOM();
ok = gTrue;
}
@@ -4236,6 +4940,22 @@ TextOutputDev::~TextOutputDev() {
}
}
+void TextOutputDev::generateBOM() {
+ UnicodeMap *uMap;
+ char bom[8];
+ int bomLen;
+
+ // insert Unicode BOM
+ if (control.insertBOM && outputStream) {
+ if (!(uMap = globalParams->getTextEncoding())) {
+ return;
+ }
+ bomLen = uMap->mapUnicode(0xfeff, bom, sizeof(bom));
+ uMap->decRefCnt();
+ (*outputFunc)(outputStream, bom, bomLen);
+ }
+}
+
void TextOutputDev::startPage(int pageNum, GfxState *state) {
text->startPage(state);
}
@@ -4282,7 +5002,7 @@ void TextOutputDev::endActualText(GfxState *state) {
void TextOutputDev::stroke(GfxState *state) {
GfxPath *path;
GfxSubpath *subpath;
- double x[2], y[2];
+ double x[2], y[2], t;
if (!control.html) {
return;
@@ -4300,6 +5020,12 @@ void TextOutputDev::stroke(GfxState *state) {
// look for a vertical or horizontal line
if (x[0] == x[1] || y[0] == y[1]) {
+ if (x[0] > x[1]) {
+ t = x[0]; x[0] = x[1]; x[1] = t;
+ }
+ if (y[0] > y[1]) {
+ t = y[0]; y[0] = y[1]; y[1] = t;
+ }
text->addUnderline(x[0], y[0], x[1], y[1]);
}
}