filters

FilterPage.cpp
1/*
2 * Copyright (c) 2002-2003 Nicolas HADACEK (hadacek@kde.org)
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "FilterPage.h"
21
22#include <math.h>
23
24#include <tdeglobal.h>
25#include <kdebug.h>
26
27#include "data.h"
28#include "transform.h"
29#include "dialog.h"
30
31#define TIME_START(str) { \
32 kdDebug(30516) << str << endl; \
33 _time.restart(); \
34}
35#define TIME_END { kdDebug(30516) << "elapsed=" << _time.elapsed() << endl; }
36
37
38namespace PDFImport
39{
40
41//-----------------------------------------------------------------------------
42Page::Page(Data &data)
43 : TextPage(false), _data(data), _lastStr(0), _rects(Nb_ParagraphTypes)
44{
45 _links.setAutoDelete(true);
46}
47
48void Page::clear()
49{
50 TextPage::clear();
51 _lastStr = 0;
52 _links.clear();
53 _pars.clear();
54}
55
56void Page::beginString(GfxState *state, double x0, double y0)
57{
58 // This check is needed because Type 3 characters can contain
59 // text-drawing operations.
60 if (curStr) {
61 ++nest;
62 return;
63 }
64
65// _data.checkTextFrameset();
66 curStr = new String(state, x0, y0, fontSize, _data.textIndex());
67// kdDebug(30516) << "---" << endl;
68}
69
70void Page::endString()
71{
72// kdDebug(30516) << "endString..." << " len=" << curStr->len
73// << " " << _lastStr
74// << " len=" << (_lastStr ? _lastStr->len : -1) << endl;
75 TextPage::endString();
76// kdDebug(30516) << " ...endString done" << endl;
77}
78
79void Page::addString(TextString *str)
80{
81// kdDebug(30516) << "addString..." << endl;
82// if ( str->len==0 ) kdDebug(30516) << "empty string !" << endl;
83 if (_lastStr) _lastStr->checkCombination(str);
84 _lastStr = (str->len==0 ? 0 : static_cast<String *>(str));
85// TQString s;
86// for (int i=0; i<str->len; i++) s += TQChar(str->text[i]);
87// kdDebug(30516) << "string: " << s << " ("
88// << (str->len>0 ? s[0].unicode() : 0) << ")" << endl;
89 TextPage::addString(str);
90// kdDebug(30516) << " ...addString done" << endl;
91}
92
93TextBlock *Page::block(TextLine *line, int index)
94{
95 uint k = 0;
96 if ( index<0 )
97 for (TextBlock *block = line->blocks; block; block = block->next) k++;
98 k += index;
99 uint i = 0;
100 for (TextBlock *block = line->blocks; block; block = block->next) {
101 if ( i==k ) return block;
102 i++;
103 }
104 return 0;
105}
106
107//-----------------------------------------------------------------------------
108bool Page::isLastParagraphLine(TextLine *line, const Paragraph &par)
109{
110 // last line of page
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);
116 // next line far below
117 if ( dy>0.5*ndy ) return true;
118 // image displayed before next line (?)
119 if ( str->frameIndex()!=nStr->frameIndex() ) return true;
120 if ( line->blocks==0 ) return false; // should not happen
121 // if contains one or more inside tabs
122 if (line->blocks->next) return true;
123 if ( line->next && line->next->blocks==0 ) return false;//should not happen
124 // if next line contains one or more inside tabs
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; // should not happen
128 TQChar c = TQChar(b->text[b->len-1]);
129 // last line char is not '.' or ':'
130 if ( c!='.' && c!=':' ) return false;
131 // if at line end and block aligned : same paragraph
132 return ( !equal(b->xMax, par.rect().right()) );
133}
134
135void Page::createParagraphs()
136{
137 TextLine *first = lines;
138 uint nbLines = 0;
139 for (TextLine *line = lines; line; line = line->next) {
140 nbLines++;
141 Paragraph par(first, nbLines);
142 if ( isLastParagraphLine(line, par) ) {
143 _pars.push_back(par);
144 nbLines = 0;
145 first = line->next;
146 }
147 }
148}
149
150void Page::checkHeader()
151{
152 uint s = _pars.size();
153 if ( s==0 ) return;
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);
160// kdDebug(30516) << "first: " << first->yMax << " (" << limit << ")" << endl;
161// if (second) kdDebug(30516) << "second: " << second->yMin << " "
162// << second->yMin-first->yMax << " (" << delta
163// << ")" << endl;
164 if ( first->yMax>limit ) return;
165 if ( second && (second->yMin-first->yMax)<delta ) return;
166 par.type = Header;
167 _rects[Header] = par.rect();
168}
169
170bool Page::hasHeader() const
171{
172 return (_pars.size()>0 ? _pars[0].type==Header : false);
173}
174
175void Page::checkFooter()
176{
177 uint s = _pars.size();
178 if ( s==0 ) return;
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);
185// kdDebug(30516) << "last: " << last->yMax << " (" << limit << ")" << endl;
186// if (blast) kdDebug(30516) << "blast: " << blast->yMin << " "
187// << last->yMin-blast->yMax << " (" << delta
188// << ")" << endl;
189 if ( last->yMin<limit ) return;
190 if ( blast && (last->yMin-blast->yMax)<delta ) return;
191 par.type = Footer;
192 _rects[Footer] = par.rect();
193}
194
195bool Page::hasFooter() const
196{
197 return (_pars.size()>0 ? _pars[_pars.size()-1].type==Footer
198 : false);
199}
200
201void Page::endPage()
202{
203 TIME_START("coalesce strings");
204 TextPage::coalesce();
205 TIME_END;
206
207 createParagraphs();
208
209 // check header and footer
210 checkHeader();
211// if ( hasHeader() ) kdDebug(30516) << "has header" << endl;
212 checkFooter();
213// if ( hasFooter() ) kdDebug(30516) << "has footer" << endl;
214
215 // compute body rect
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());
220}
221
222
223//-----------------------------------------------------------------------------
224void Page::initParagraph(Paragraph &par) const
225{
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;
230
231 TQValueList<TextLine *>::const_iterator it;
232 for (it = par.lines().begin(); it!=par.lines().end(); ++it) {
233
234 // compute tabulations
235 Tabulator tab;
236 for (TextBlock *blk = (*it)->blocks; blk; blk = blk->next) {
237 // if tabulated text is aligned on right edge: put a tab
238 // on right edge and the tab type will be right aligned...
239 double tabRightAligned = equal(blk->xMax, pright);
240 double dx = (tabRightAligned ? pright : blk->xMin) - pleft;
241 // #### if the tab is just at the frame edge:
242 // the text is sent to next line ???
243 if (tabRightAligned) dx -= 0.1;
244 int res = par.findTab(dx, *it);
245 if ( res==-1 ) {
246 tab.pos = dx;
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);
252 }
253 }
254 qHeapSort2(par.tabs);
255
256 // compute indents
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);
263 }
264
265 // compute alignment
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;
270// TQString text;
271// for (int i=0; i<kMin(4, (*it)->blocks->len); i++)
272// text += TQChar((*it)->blocks->text[i]);
273// kdDebug(30516) << text << " left=" << left
274// << " pleft=" << pleft + par.leftIndent
275// << " indent=" << par.leftIndent
276// << " findent=" << par.firstIndent << endl;
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;
281 leftAligned = false;
282 }
283 if ( rightAligned && (!par.isLast(*it) || par.hasOneLine())
284 && !equal(right, pright, 0.05) ) {
285 kdDebug(30516) << "not right aligned" << endl;
286 rightAligned = false;
287 }
288 }
289
290 // finalize alignment
291 if (rightAligned) par.align = (leftAligned ? AlignBlock : AlignRight);
292 else if (centered) par.align = AlignCenter;
293}
294
295void Page::fillParagraph(Paragraph &par, double &offset) const
296{
297 const double pleft = _rects[par.type].left();
298 const double pright = _rects[par.type].right();
299 par.offset = par.lines().first()->yMin - offset;
300// kdDebug(30516) << "offset=" << offset
301// << " yMin=" << par.lines().first()->yMin
302// << " paroffset=" << par.offset << endl;
303 if ( par.offset>0 ) offset += par.offset;
304
305 TQValueList<TextLine *>::const_iterator it;
306 for (it = par.lines().begin(); it!=par.lines().end(); ++it) {
307 // end of previous line (inside a paragraph)
308 if ( !par.isFirst(*it) ) {
309 bool hyphen = false;
310 if (_data.options().smart) {
311 // check hyphen
312 uint bi, pbi;
313 int si = par.charFromEnd(0, bi);
314 Q_ASSERT( si>=0 );
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())
321 << endl;
322 TextString *next =
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;
329 hyphen = true;
330 par.blocks[bi].text.remove(si, 1);
331 }
332 }
333 if ( !hyphen ) {
334 Block b;
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);
341 }
342 }
343
344 int lineHeight = 0;
345 TextBlock *prevBlk = 0;
346 for (TextBlock *blk = (*it)->blocks; blk; blk = blk->next) {
347
348 // tabulations
349 double tabRightAligned = equal(blk->xMax, pright);
350 double dx = (tabRightAligned ? pright : blk->xMin) - pleft;
351 int res = par.findTab(dx, *it);
352 if ( res>=0 ) {
353 if (prevBlk) {
354 double xMax = prevBlk->xMax - pleft;
355 res = par.findNbTabs(res, xMax);
356 if ( res==0 ) continue;
357 } else res++;
358 // no tabs for first block in AlignCenter and AlignRight
359 // if smart mode
360 if ( prevBlk || !_data.options().smart
361 || (par.align!=AlignCenter && par.align!=AlignRight) ) {
362 Block b;
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);
366 }
367 }
368
369 // text & format
370 for (TextString *str = blk->strings; str; str = str->next) {
371 Block b;
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();
377 b.link = fstr->link;
378 par.blocks.push_back(b);
379 lineHeight = kMax(lineHeight, b.font.height());
380 }
381
382 prevBlk = blk;
383 }
384
385 offset += lineHeight;
386 }
387}
388
389FontFamily Page::checkSpecial(TQChar &c, const Font &font) const
390{
391 Unicode res = 0;
392 switch ( PDFImport::checkSpecial(c.unicode(), res) ) {
393 case Bullet:
394 kdDebug(30516) << "found bullet" << endl;
395 // #### FIXME : if list, use a COUNTER
396 // temporarly replace by symbol
397 c = res;
398 return Symbol;
399 case SuperScript:
400 kdDebug(30516) << "found superscript" << endl;
401 // #### FIXME
402 break;
403 case LatexSpecial:
404 if ( !font.isLatex() ) break;
405 kdDebug(30516) << "found latex special" << endl;
406 return Times;
407 case SpecialSymbol:
408 kdDebug(30516) << "found symbol=" << c.unicode() << endl;
409 return Times;
410 //return Symbol;
411 default:
412 break;
413 }
414
415 return Nb_Family;
416}
417
418void Page::checkSpecialChars(Paragraph &par) const
419{
420 TQValueList<Block> blocks;
421 for (uint k=0; k<par.blocks.size(); k++) {
422 const Block &b = par.blocks[k];
423 TQString res;
424// kdDebug(30516) << "check \"" << b.text << "\"" << endl;
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;
429 else {
430 if ( !res.isEmpty() ) {
431 blocks.push_back(b);
432 blocks.back().text = res;
433 res = TQString();
434 }
435 blocks.push_back(b);
436 blocks.back().font.setFamily(family);
437 blocks.back().text = c;
438 }
439 }
440 if ( !res.isEmpty() ) {
441 blocks.push_back(b);
442 blocks.back().text = res;
443 }
444 }
445 par.blocks = blocks;
446}
447
448void Page::coalesce(Paragraph &par) const
449{
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);
457 }
458 par.blocks = blocks;
459}
460
461void Page::prepare()
462{
463 TIME_START("associate links");
464 for (Link *link=_links.first(); link; link=_links.next()) {
465 const DRect &r = link->rect();
466// kdDebug(30516) << "link " << r.toString() << endl;
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();
472// kdDebug(30516) << "str " << sr.toString() << " "
473// << r.isInside(sr) << endl;
474 if ( r.isInside(sr) ) fstr->link = link;
475 }
476 }
477 TIME_END;
478
479 TIME_START("init paragraphs");
480 for (uint i=0; i<_pars.size(); i++) {
481 initParagraph(_pars[i]);
482
483 // special case for wide and centered one liner without tab
484 if ( _pars[i].align==AlignBlock && _pars[i].hasOneLine()
485 && _pars[i].tabs.size()==0
486 && (_pars.size()==1
487 || (i!=0 && _pars[i-1].align==AlignCenter)
488 || ((i+1)!=_pars.size() && _pars[i+1].align==AlignCenter)) )
489 _pars[i].align = AlignCenter;
490 }
491 TIME_END;
492
493 TIME_START("fill paragraphs");
494 uint begin = 0;
495 if ( hasHeader() ) {
496 double offset = _rects[Header].top();
497 fillParagraph(_pars[0], offset);
498 begin++;
499 }
500 uint end = _pars.size();
501 if ( hasFooter() ) {
502 double offset = _rects[Footer].top();
503 end--;
504 fillParagraph(_pars[end], offset);
505 }
506 double offset = _rects[Body].top();
507 for (uint i=begin; i<end; i++)
508 fillParagraph(_pars[i], offset);
509 TIME_END;
510
511 TIME_START("check for special chars");
512 for (uint i=0; i<_pars.size(); i++)
513 checkSpecialChars(_pars[i]);
514 TIME_END;
515
516 // this is not really required...
517 TIME_START("coalesce formats");
518 for (uint i=0; i<_pars.size(); i++)
519 coalesce(_pars[i]);
520 TIME_END;
521
522 // if no paragraph : add an empty one
523 if ( _pars.size()==0 ) {
524 Block b;
525 Paragraph par(0, 0);
526 par.blocks.push_back(b);
527 _pars.push_back(par);
528 }
529}
530
531void Page::dump(const Paragraph &par)
532{
533 TQValueVector<TQDomElement> layouts;
534 TQValueVector<TQDomElement> formats;
535
536 // tabulations
537 for (uint k=0; k<par.tabs.size(); k++) {
538 TQDomElement element = par.tabs[k].createElement(_data);
539 layouts.push_back(element);
540 }
541
542 // indents
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);
549 }
550
551 // offset before
552 if ( par.offset>0 ) {
553 TQDomElement element = _data.createElement("OFFSETS");
554 element.setAttribute("before", par.offset);
555 layouts.push_back(element);
556 }
557
558 // flow
559 if (_data.options().smart) {
560 TQString flow;
561// kdDebug(30516) << "flow=" << par.align << endl;
562 switch (par.align) {
563 case AlignLeft: break;
564 case AlignRight: flow = "right"; break;
565 case AlignCenter: flow = "center"; break;
566 case AlignBlock: flow = "justify"; break;
567 }
568 if ( !flow.isEmpty() ) {
569 TQDomElement element = _data.createElement("FLOW");
570 element.setAttribute("align", TQString(flow.utf8()));
571 layouts.push_back(element);
572 }
573 }
574
575 // text and formats
576 TQString text;
577 uint pos = 0;
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);
587 pos += len;
588 }
589
590 _data.createParagraph(text, par.type, layouts, formats);
591}
592
593void Page::dump()
594{
595 prepare();
596
597 TIME_START("dump XML");
598 for (uint i=0; i<_pars.size(); i++)
599 dump(_pars[i]);
600 TIME_END;
601}
602
603} // namespace
The body is a frameset.
Definition body.h:38
The body is a frameset.
Definition paragraph.h:35