20#include "FilterPage.h"
31#define TIME_START(str) { \
32 kdDebug(30516) << str << endl; \
35#define TIME_END { kdDebug(30516) << "elapsed=" << _time.elapsed() << endl; }
43 : TextPage(false), _data(data), _lastStr(0), _rects(Nb_ParagraphTypes)
45 _links.setAutoDelete(
true);
56void Page::beginString(GfxState *state,
double x0,
double y0)
66 curStr =
new String(state, x0, y0, fontSize, _data.textIndex());
75 TextPage::endString();
79void Page::addString(TextString *str)
83 if (_lastStr) _lastStr->checkCombination(str);
84 _lastStr = (str->len==0 ? 0 :
static_cast<String *
>(str));
89 TextPage::addString(str);
93TextBlock *Page::block(TextLine *line,
int index)
97 for (TextBlock *block = line->blocks; block; block = block->next) k++;
100 for (TextBlock *block = line->blocks; block; block = block->next) {
101 if ( i==k )
return block;
108bool Page::isLastParagraphLine(TextLine *line,
const Paragraph &par)
111 if ( line->next==0 )
return true;
112 double dy = line->next->yMin - line->yMax;
113 double ndy = line->next->yMax - line->next->yMin;
114 String *str =
static_cast<String *
>(line->blocks->strings);
115 String *nStr =
static_cast<String *
>(line->next->blocks->strings);
117 if ( dy>0.5*ndy )
return true;
119 if ( str->frameIndex()!=nStr->frameIndex() )
return true;
120 if ( line->blocks==0 )
return false;
122 if (line->blocks->next)
return true;
123 if ( line->next && line->next->blocks==0 )
return false;
125 if ( line->next && line->next->blocks->next )
return true;
126 TextBlock *b = block(line, -1);
127 if ( b==0 || b->len==0 )
return false;
128 TQChar c = TQChar(b->text[b->len-1]);
130 if ( c!=
'.' && c!=
':' )
return false;
132 return ( !equal(b->xMax, par.rect().right()) );
135void Page::createParagraphs()
137 TextLine *first = lines;
139 for (TextLine *line = lines; line; line = line->next) {
141 Paragraph par(first, nbLines);
142 if ( isLastParagraphLine(line, par) ) {
143 _pars.push_back(par);
150void Page::checkHeader()
152 uint s = _pars.size();
154 Paragraph &par = _pars[0];
155 if ( par.lines().count()!=1 )
return;
156 const TextLine *first = par.lines().first();
157 const TextLine *second = (s>1 ? _pars[1].lines().first() : 0);
158 double limit = 0.2 * _data.pageRect().height();
159 double delta = 2 * kMin(first->yMax - first->yMin, 12.0);
164 if ( first->yMax>limit )
return;
165 if ( second && (second->yMin-first->yMax)<delta )
return;
167 _rects[Header] = par.rect();
170bool Page::hasHeader()
const
172 return (_pars.size()>0 ? _pars[0].type==Header :
false);
175void Page::checkFooter()
177 uint s = _pars.size();
179 Paragraph &par = _pars[s-1];
180 if ( par.lines().count()!=1 )
return;
181 const TextLine *last = par.lines().first();
182 const TextLine *blast = (s>1 ? _pars[s-2].lines().last() : 0);
183 double limit = 0.8 * _data.pageRect().height();
184 double delta = 2 * kMin(last->yMax-last->yMin, 12.0);
189 if ( last->yMin<limit )
return;
190 if ( blast && (last->yMin-blast->yMax)<delta )
return;
192 _rects[Footer] = par.rect();
195bool Page::hasFooter()
const
197 return (_pars.size()>0 ? _pars[_pars.size()-1].type==Footer
203 TIME_START(
"coalesce strings");
204 TextPage::coalesce();
216 uint begin = (hasHeader() ? 1 : 0);
217 uint end = _pars.size() - (hasFooter() ? 1 : 0);
218 for (uint i=begin; i<end; i++)
219 _rects[Body].unite(_pars[i].rect());
224void Page::initParagraph(
Paragraph &par)
const
226 bool rightAligned =
true, centered =
true, leftAligned =
true;
227 const double pleft = _rects[par.type].left();
228 const double pright = _rects[par.type].right();
229 const double pmean = (pleft + pright) / 2;
231 TQValueList<TextLine *>::const_iterator it;
232 for (it = par.lines().begin(); it!=par.lines().end(); ++it) {
236 for (TextBlock *blk = (*it)->blocks; blk; blk = blk->next) {
239 double tabRightAligned = equal(blk->xMax, pright);
240 double dx = (tabRightAligned ? pright : blk->xMin) - pleft;
243 if (tabRightAligned) dx -= 0.1;
244 int res = par.findTab(dx, *it);
247 if (tabRightAligned) {
248 tab.alignment = Tabulator::Right;
249 kdDebug(30516) <<
"tabulated text right aligned.." << endl;
250 }
else tab.alignment = Tabulator::Left;
251 par.tabs.push_back(tab);
254 qHeapSort2(par.tabs);
257 double left = (*it)->blocks->xMin - pleft;
258 if ( par.isFirst(*it) ) {
259 par.firstIndent = left;
260 par.leftIndent = left;
261 }
else if ( par.isSecond(*it) ) par.leftIndent = left;
262 else par.leftIndent = kMin(par.leftIndent, left);
266 for (it = par.lines().begin(); it!=par.lines().end(); ++it) {
267 double left = (*it)->blocks->xMin;
268 double right = block(*it, -1)->xMax;
269 double mean = (left + right) / 2;
277 if ( centered && !equal(mean, pmean) ) centered =
false;
278 if ( leftAligned && (!par.isFirst(*it) || par.hasOneLine())
279 && !equal(left, pleft + par.leftIndent, 0.05) ) {
280 kdDebug(30516) <<
"not left aligned" << endl;
283 if ( rightAligned && (!par.isLast(*it) || par.hasOneLine())
284 && !equal(right, pright, 0.05) ) {
285 kdDebug(30516) <<
"not right aligned" << endl;
286 rightAligned =
false;
291 if (rightAligned) par.align = (leftAligned ? AlignBlock : AlignRight);
292 else if (centered) par.align = AlignCenter;
295void Page::fillParagraph(
Paragraph &par,
double &offset)
const
297 const double pleft = _rects[par.type].left();
298 const double pright = _rects[par.type].right();
299 par.offset = par.lines().first()->yMin - offset;
303 if ( par.offset>0 ) offset += par.offset;
305 TQValueList<TextLine *>::const_iterator it;
306 for (it = par.lines().begin(); it!=par.lines().end(); ++it) {
308 if ( !par.isFirst(*it) ) {
310 if (_data.options().smart) {
313 int si = par.charFromEnd(0, bi);
315 TQChar c = par.blocks[bi].text[si];
316 int psi = par.charFromEnd(1, pbi);
317 TQChar prev = (psi<0 ? TQChar::null : par.blocks[pbi].text[psi]);
318 if ( !prev.isNull() && type(c.unicode())==Hyphen )
319 kdDebug(30516) <<
"hyphen ? " << TQString(prev)
320 <<
" type=" << type(prev.unicode())
323 ((*it)->next ? (*it)->next->blocks->strings : 0);
324 if ( !prev.isNull() && type(c.unicode())==Hyphen
325 && isLetter( type(prev.unicode()) )
326 && next && next->len>0
327 && isLetter( type(next->text[next->len-1]) ) ) {
328 kdDebug(30516) <<
"found hyphen" << endl;
330 par.blocks[bi].text.remove(si, 1);
335 bool remove = _data.options().smart;
336 if ( remove && par.align!=AlignBlock )
337 remove = ( par.rect().right()>0.9*pright );
338 b.text = (remove ?
' ' :
'\n');
339 b.font =
static_cast<String *
>((*it)->blocks->strings)->font();
340 par.blocks.push_back(b);
345 TextBlock *prevBlk = 0;
346 for (TextBlock *blk = (*it)->blocks; blk; blk = blk->next) {
349 double tabRightAligned = equal(blk->xMax, pright);
350 double dx = (tabRightAligned ? pright : blk->xMin) - pleft;
351 int res = par.findTab(dx, *it);
354 double xMax = prevBlk->xMax - pleft;
355 res = par.findNbTabs(res, xMax);
356 if ( res==0 )
continue;
360 if ( prevBlk || !_data.options().smart
361 || (par.align!=AlignCenter && par.align!=AlignRight) ) {
363 b.font =
static_cast<String *
>(blk->strings)->font();
364 for (uint k=0; k<(uint)res; k++) b.text +=
'\t';
365 par.blocks.push_back(b);
370 for (TextString *str = blk->strings; str; str = str->next) {
372 for (uint k = 0; k<uint(str->len); k++)
373 b.text += TQChar(str->text[k]);
374 if (str->spaceAfter) b.text +=
' ';
375 String *fstr =
static_cast<String *
>(str);
376 b.font = fstr->font();
378 par.blocks.push_back(b);
379 lineHeight = kMax(lineHeight, b.font.height());
385 offset += lineHeight;
389FontFamily Page::checkSpecial(TQChar &c,
const Font &font)
const
392 switch ( PDFImport::checkSpecial(c.unicode(), res) ) {
394 kdDebug(30516) <<
"found bullet" << endl;
400 kdDebug(30516) <<
"found superscript" << endl;
404 if ( !font.isLatex() )
break;
405 kdDebug(30516) <<
"found latex special" << endl;
408 kdDebug(30516) <<
"found symbol=" << c.unicode() << endl;
418void Page::checkSpecialChars(
Paragraph &par)
const
420 TQValueList<Block> blocks;
421 for (uint k=0; k<par.blocks.size(); k++) {
422 const Block &b = par.blocks[k];
425 for (uint l=0; l<b.text.length(); l++) {
426 TQChar c = b.text[l];
427 FontFamily family = checkSpecial(c, b.font);
428 if ( family==Nb_Family ) res += c;
430 if ( !res.isEmpty() ) {
432 blocks.back().text = res;
436 blocks.back().font.setFamily(family);
437 blocks.back().text = c;
440 if ( !res.isEmpty() ) {
442 blocks.back().text = res;
450 TQValueList<Block> blocks;
451 blocks.push_back(par.blocks[0]);
452 for (uint k=1; k<par.blocks.size(); k++) {
453 const Block &b = par.blocks[k];
454 if ( b.link==blocks.back().link && b.font==blocks.back().font )
455 blocks.back().text += b.text;
456 else blocks.push_back(b);
463 TIME_START(
"associate links");
464 for (Link *link=_links.first(); link; link=_links.next()) {
465 const DRect &r = link->rect();
467 for (TextLine *line = lines; line; line = line->next)
468 for (TextBlock *blk = line->blocks; blk; blk = blk->next)
469 for (TextString *str = blk->strings; str; str = str->next) {
470 String *fstr =
static_cast<String *
>(str);
471 DRect sr = fstr->rect();
474 if ( r.isInside(sr) ) fstr->link = link;
479 TIME_START(
"init paragraphs");
480 for (uint i=0; i<_pars.size(); i++) {
481 initParagraph(_pars[i]);
484 if ( _pars[i].align==AlignBlock && _pars[i].hasOneLine()
485 && _pars[i].tabs.size()==0
487 || (i!=0 && _pars[i-1].align==AlignCenter)
488 || ((i+1)!=_pars.size() && _pars[i+1].align==AlignCenter)) )
489 _pars[i].align = AlignCenter;
493 TIME_START(
"fill paragraphs");
496 double offset = _rects[Header].top();
497 fillParagraph(_pars[0], offset);
500 uint end = _pars.size();
502 double offset = _rects[Footer].top();
504 fillParagraph(_pars[end], offset);
506 double offset = _rects[Body].top();
507 for (uint i=begin; i<end; i++)
508 fillParagraph(_pars[i], offset);
511 TIME_START(
"check for special chars");
512 for (uint i=0; i<_pars.size(); i++)
513 checkSpecialChars(_pars[i]);
517 TIME_START(
"coalesce formats");
518 for (uint i=0; i<_pars.size(); i++)
523 if ( _pars.size()==0 ) {
526 par.blocks.push_back(b);
527 _pars.push_back(par);
533 TQValueVector<TQDomElement> layouts;
534 TQValueVector<TQDomElement> formats;
537 for (uint k=0; k<par.tabs.size(); k++) {
538 TQDomElement element = par.tabs[k].createElement(_data);
539 layouts.push_back(element);
543 if ( !_data.options().smart || par.align!=AlignCenter ) {
544 TQDomElement element = _data.createElement(
"INDENTS");
545 element.setAttribute(
"left", par.leftIndent);
546 double delta = par.firstIndent - par.leftIndent;
547 if ( !equal(delta, 0) ) element.setAttribute(
"first", delta);
548 layouts.push_back(element);
552 if ( par.offset>0 ) {
553 TQDomElement element = _data.createElement(
"OFFSETS");
554 element.setAttribute(
"before", par.offset);
555 layouts.push_back(element);
559 if (_data.options().smart) {
563 case AlignLeft:
break;
564 case AlignRight: flow =
"right";
break;
565 case AlignCenter: flow =
"center";
break;
566 case AlignBlock: flow =
"justify";
break;
568 if ( !flow.isEmpty() ) {
569 TQDomElement element = _data.createElement(
"FLOW");
570 element.setAttribute(
"align", TQString(flow.utf8()));
571 layouts.push_back(element);
578 for (uint k=0; k<par.blocks.size(); k++) {
579 const Block &b = par.blocks[k];
580 text += (b.link ?
"#" : b.text);
581 uint len = (b.link ? 1 : b.text.length());
582 TQDomElement element = _data.createElement(
"FORMAT");
583 TQDomDocument document = _data.document();
584 bool r = b.font.format(document, element, pos, len);
585 if (b.link) b.link->format(document, element, pos, b.text);
586 if ( r || b.link ) formats.push_back(element);
590 _data.createParagraph(text, par.type, layouts, formats);
597 TIME_START(
"dump XML");
598 for (uint i=0; i<_pars.size(); i++)