• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdegraphics API Reference
  • KDE Home
  • Contact Us
 

okular

  • sources
  • kde-4.12
  • kdegraphics
  • okular
  • core
textpage.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * Copyright (C) 2005 by Piotr Szymanski <niedakh@gmail.com> *
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 
10 #include "textpage.h"
11 #include "textpage_p.h"
12 
13 #include <kdebug.h>
14 
15 #include "area.h"
16 #include "debug_p.h"
17 #include "misc.h"
18 #include "page.h"
19 #include "page_p.h"
20 
21 #include <cstring>
22 
23 #include <QtAlgorithms>
24 #include <QVarLengthArray>
25 
26 using namespace Okular;
27 
28 class SearchPoint
29 {
30  public:
31  SearchPoint()
32  : offset_begin( -1 ), offset_end( -1 )
33  {
34  }
35 
37  TextList::ConstIterator it_begin;
38 
40  TextList::ConstIterator it_end;
41 
45  int offset_begin;
46 
50  int offset_end;
51 };
52 
53 /* text comparison functions */
54 
55 static bool CaseInsensitiveCmpFn( const QStringRef & from, const QStringRef & to )
56 {
57  return from.compare( to, Qt::CaseInsensitive ) == 0;
58 }
59 
60 static bool CaseSensitiveCmpFn( const QStringRef & from, const QStringRef & to )
61 {
62  return from.compare( to, Qt::CaseSensitive ) == 0;
63 }
64 
71 static bool doesConsumeY(const QRect& first, const QRect& second, int threshold)
72 {
73  // if one consumes another fully
74  if(first.top() <= second.top() && first.bottom() >= second.bottom())
75  return true;
76 
77  if(first.top() >= second.top() && first.bottom() <= second.bottom())
78  return true;
79 
80  // or if there is overlap of space by more than 80%
81  // there is overlap
82  if(second.bottom() >= first.top() && first.bottom() >= second.top())
83  {
84  const int overlap = (second.bottom() >= first.bottom()) ? first.bottom() - second.top()
85  : second.bottom() - first.top();
86  //we will divide by the smaller rectangle to calculate the overlap
87  const int percentage = (first.height() < second.height()) ? overlap * 100 / (first.bottom() - first.top())
88  : overlap * 100 / (second.bottom() - second.top());
89 
90  if(percentage >= threshold) return true;
91  }
92 
93  return false;
94 }
95 
96 /*
97  Rationale behind TinyTextEntity:
98 
99  instead of storing directly a QString for the text of an entity,
100  we store the UTF-16 data and their length. This way, we save about
101  4 int's wrt a QString, and we can create a new string from that
102  raw data (that's the only penalty of that).
103  Even better, if the string we need to store has at most
104  MaxStaticChars characters, then we store those in place of the QChar*
105  that would be used (with new[] + free[]) for the data.
106  */
107 class TinyTextEntity
108 {
109  static const int MaxStaticChars = sizeof( QChar * ) / sizeof( QChar );
110 
111  public:
112  TinyTextEntity( const QString &text, const NormalizedRect &rect )
113  : area( rect )
114  {
115  Q_ASSERT_X( !text.isEmpty(), "TinyTextEntity", "empty string" );
116  Q_ASSERT_X( sizeof( d ) == sizeof( QChar * ), "TinyTextEntity",
117  "internal storage is wider than QChar*, fix it!" );
118  length = text.length();
119  switch ( length )
120  {
121 #if QT_POINTER_SIZE >= 8
122  case 4:
123  d.qc[3] = text.at( 3 ).unicode();
124  // fall through
125  case 3:
126  d.qc[2] = text.at( 2 ).unicode();
127  // fall through
128 #endif
129  case 2:
130  d.qc[1] = text.at( 1 ).unicode();
131  // fall through
132  case 1:
133  d.qc[0] = text.at( 0 ).unicode();
134  break;
135  default:
136  d.data = new QChar[ length ];
137  std::memcpy( d.data, text.constData(), length * sizeof( QChar ) );
138  }
139  }
140 
141  ~TinyTextEntity()
142  {
143  if ( length > MaxStaticChars )
144  {
145  delete [] d.data;
146  }
147  }
148 
149  inline QString text() const
150  {
151  return length <= MaxStaticChars ? QString::fromRawData( ( const QChar * )&d.qc[0], length )
152  : QString::fromRawData( d.data, length );
153  }
154 
155  inline NormalizedRect transformedArea( const QTransform &matrix ) const
156  {
157  NormalizedRect transformed_area = area;
158  transformed_area.transform( matrix );
159  return transformed_area;
160  }
161 
162  NormalizedRect area;
163 
164  private:
165  Q_DISABLE_COPY( TinyTextEntity )
166 
167  union
168  {
169  QChar *data;
170  ushort qc[MaxStaticChars];
171  } d;
172  int length;
173 };
174 
175 
176 TextEntity::TextEntity( const QString &text, NormalizedRect *area )
177  : m_text( text ), m_area( area ), d( 0 )
178 {
179 }
180 
181 TextEntity::~TextEntity()
182 {
183  delete m_area;
184 }
185 
186 QString TextEntity::text() const
187 {
188  return m_text;
189 }
190 
191 NormalizedRect* TextEntity::area() const
192 {
193  return m_area;
194 }
195 
196 NormalizedRect TextEntity::transformedArea(const QTransform &matrix) const
197 {
198  NormalizedRect transformed_area = *m_area;
199  transformed_area.transform( matrix );
200  return transformed_area;
201 }
202 
203 
204 TextPagePrivate::TextPagePrivate()
205  : m_page( 0 )
206 {
207 }
208 
209 TextPagePrivate::~TextPagePrivate()
210 {
211  qDeleteAll( m_searchPoints );
212  qDeleteAll( m_words );
213 }
214 
215 
216 TextPage::TextPage()
217  : d( new TextPagePrivate() )
218 {
219 }
220 
221 TextPage::TextPage( const TextEntity::List &words )
222  : d( new TextPagePrivate() )
223 {
224  TextEntity::List::ConstIterator it = words.constBegin(), itEnd = words.constEnd();
225  for ( ; it != itEnd; ++it )
226  {
227  TextEntity *e = *it;
228  if ( !e->text().isEmpty() )
229  d->m_words.append( new TinyTextEntity( e->text(), *e->area() ) );
230  delete e;
231  }
232 }
233 
234 TextPage::~TextPage()
235 {
236  delete d;
237 }
238 
239 void TextPage::append( const QString &text, NormalizedRect *area )
240 {
241  if ( !text.isEmpty() )
242  d->m_words.append( new TinyTextEntity( text.normalized(QString::NormalizationForm_KC), *area ) );
243  delete area;
244 }
245 
246 struct WordWithCharacters
247 {
248  WordWithCharacters(TinyTextEntity *w, const TextList &c)
249  : word(w), characters(c)
250  {
251  }
252 
253  inline QString text() const
254  {
255  return word->text();
256  }
257 
258  inline const NormalizedRect &area() const
259  {
260  return word->area;
261  }
262 
263  TinyTextEntity *word;
264  TextList characters;
265 };
266 typedef QList<WordWithCharacters> WordsWithCharacters;
267 
273 class RegionText
274 {
275 
276 public:
277  RegionText()
278  {
279  };
280 
281  RegionText(const WordsWithCharacters &wordsWithCharacters, const QRect &area)
282  : m_region_wordWithCharacters(wordsWithCharacters), m_area(area)
283  {
284  }
285 
286  inline QString string() const
287  {
288  QString res;
289  foreach(const WordWithCharacters &word, m_region_wordWithCharacters)
290  res += word.text();
291  return res;
292  }
293 
294  inline WordsWithCharacters text() const
295  {
296  return m_region_wordWithCharacters;
297  }
298 
299  inline QRect area() const
300  {
301  return m_area;
302  }
303 
304  inline void setArea(const QRect &area)
305  {
306  m_area = area;
307  }
308 
309  inline void setText(const WordsWithCharacters &wordsWithCharacters)
310  {
311  m_region_wordWithCharacters = wordsWithCharacters;
312  }
313 
314 private:
315  WordsWithCharacters m_region_wordWithCharacters;
316  QRect m_area;
317 };
318 
319 RegularAreaRect * TextPage::textArea ( TextSelection * sel) const
320 {
321  if ( d->m_words.isEmpty() )
322  return new RegularAreaRect();
323 
338  RegularAreaRect * ret= new RegularAreaRect;
339 
340  const QTransform matrix = d->m_page ? d->m_page->rotationMatrix() : QTransform();
341 #if 0
342  int it = -1;
343  int itB = -1;
344  int itE = -1;
345 
346  // ending cursor is higher than start cursor, we need to find positions in reverse
347  NormalizedRect tmp;
348  NormalizedRect start;
349  NormalizedRect end;
350 
351  NormalizedPoint startC = sel->start();
352  double startCx = startC.x;
353  double startCy = startC.y;
354 
355  NormalizedPoint endC = sel->end();
356  double endCx = endC.x;
357  double endCy = endC.y;
358 
359  if ( sel->direction() == 1 || ( sel->itB() == -1 && sel->direction() == 0 ) )
360  {
361 #ifdef DEBUG_TEXTPAGE
362  kWarning() << "running first loop";
363 #endif
364  const int count = d->m_words.count();
365  for ( it = 0; it < count; it++ )
366  {
367  tmp = *d->m_words[ it ]->area();
368  if ( tmp.contains( startCx, startCy )
369  || ( tmp.top <= startCy && tmp.bottom >= startCy && tmp.left >= startCx )
370  || ( tmp.top >= startCy))
371  {
373  itB = it;
374 #ifdef DEBUG_TEXTPAGE
375  kWarning() << "start is" << itB << "count is" << d->m_words.count();
376 #endif
377  break;
378  }
379  }
380  sel->itB( itB );
381  }
382  itB = sel->itB();
383 #ifdef DEBUG_TEXTPAGE
384  kWarning() << "direction is" << sel->direction();
385  kWarning() << "reloaded start is" << itB << "against" << sel->itB();
386 #endif
387  if ( sel->direction() == 0 || ( sel->itE() == -1 && sel->direction() == 1 ) )
388  {
389 #ifdef DEBUG_TEXTPAGE
390  kWarning() << "running second loop";
391 #endif
392  for ( it = d->m_words.count() - 1; it >= itB; it-- )
393  {
394  tmp = *d->m_words[ it ]->area();
395  if ( tmp.contains( endCx, endCy )
396  || ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx )
397  || ( tmp.bottom <= endCy ) )
398  {
400  itE = it;
401 #ifdef DEBUG_TEXTPAGE
402  kWarning() << "ending is" << itE << "count is" << d->m_words.count();
403  kWarning() << "conditions" << tmp.contains( endCx, endCy ) << " "
404  << ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx ) << " " <<
405  ( tmp.top >= endCy);
406 #endif
407  break;
408  }
409  }
410  sel->itE( itE );
411  }
412 #ifdef DEBUG_TEXTPAGE
413  kWarning() << "reloaded ending is" << itE << "against" << sel->itE();
414 #endif
415 
416  if ( sel->itB() != -1 && sel->itE() != -1 )
417  {
418  start = *d->m_words[ sel->itB() ]->area();
419  end = *d->m_words[ sel->itE() ]->area();
420 
421  NormalizedRect first, second, third;
425  first = start;
426  second.top = start.bottom;
427  first.right = second.right = 1;
428  third = end;
429  third.left = second.left = 0;
430  second.bottom = end.top;
431  int selMax = qMax( sel->itB(), sel->itE() );
432  for ( it = qMin( sel->itB(), sel->itE() ); it <= selMax; ++it )
433  {
434  tmp = *d->m_words[ it ]->area();
435  if ( tmp.intersects( &first ) || tmp.intersects( &second ) || tmp.intersects( &third ) )
436  ret->appendShape( d->m_words.at( it )->transformedArea( matrix ) );
437  }
438  }
439 #else
440  const double scaleX = d->m_page->m_page->width();
441  const double scaleY = d->m_page->m_page->height();
442 
443  NormalizedPoint startC = sel->start();
444  NormalizedPoint endC = sel->end();
445  NormalizedPoint temp;
446 
447  // if startPoint is right to endPoint swap them
448  if(startC.x > endC.x)
449  {
450  temp = startC;
451  startC = endC;
452  endC = temp;
453  }
454 
455  // minX,maxX,minY,maxY gives the bounding rectangle coordinates of the document
456  const NormalizedRect boundingRect = d->m_page->m_page->boundingBox();
457  const QRect content = boundingRect.geometry(scaleX,scaleY);
458  const double minX = content.left();
459  const double maxX = content.right();
460  const double minY = content.top();
461  const double maxY = content.bottom();
462 
494  // we know that startC.x > endC.x, we need to decide which is top and which is bottom
495  const NormalizedRect start_end = (startC.y < endC.y) ? NormalizedRect(startC.x, startC.y, endC.x, endC.y)
496  : NormalizedRect(startC.x, endC.y, endC.x, startC.y);
497 
498  // Case 1(a)
499  if(!boundingRect.intersects(start_end)) return ret;
500 
501  // case 1(b)
507  else
508  {
509  // if start is left to content rect take it to content rect boundary
510  if(startC.x * scaleX < minX) startC.x = minX/scaleX;
511  if(endC.x * scaleX > maxX) endC.x = maxX/scaleX;
512 
513  // if start is top to end (selection type 01)
514  if(startC.y * scaleY < minY) startC.y = minY/scaleY;
515  if(endC.y * scaleY > maxY) endC.y = maxY/scaleY;
516 
517  // if start is bottom to end (selection type 02)
518  if(startC.y * scaleY > maxY) startC.y = maxY/scaleY;
519  if(endC.y * scaleY < minY) endC.y = minY/scaleY;
520  }
521 
522  TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd();
523  TextList::ConstIterator start = it, end = itEnd, tmpIt = it; //, tmpItEnd = itEnd;
524  const MergeSide side = d->m_page ? (MergeSide)d->m_page->m_page->totalOrientation() : MergeRight;
525 
526  NormalizedRect tmp;
527  //case 2(a)
528  for ( ; it != itEnd; ++it )
529  {
530  tmp = (*it)->area;
531  if(tmp.contains(startC.x,startC.y)){
532  start = it;
533  }
534  if(tmp.contains(endC.x,endC.y)){
535  end = it;
536  }
537  }
538 
539  //case 2(b)
540  it = tmpIt;
541  if(start == it && end == itEnd)
542  {
543  for ( ; it != itEnd; ++it )
544  {
545  // is there any text reactangle within the start_end rect
546  tmp = (*it)->area;
547  if(start_end.intersects(tmp))
548  break;
549  }
550 
551  // we have searched every text entities, but none is within the rectangle created by start and end
552  // so, no selection should be done
553  if(it == itEnd)
554  {
555  return ret;
556  }
557  }
558  it = tmpIt;
559  bool selection_two_start = false;
560 
561  //case 3.a
562  if(start == it)
563  {
564  bool flagV = false;
565  NormalizedRect rect;
566 
567  // selection type 01
568  if(startC.y <= endC.y)
569  {
570  for ( ; it != itEnd; ++it )
571  {
572  rect= (*it)->area;
573  rect.isBottom(startC) ? flagV = false: flagV = true;
574 
575  if(flagV && rect.isRight(startC))
576  {
577  start = it;
578  break;
579  }
580  }
581  }
582 
583  //selection type 02
584  else
585  {
586  selection_two_start = true;
587  int distance = scaleX + scaleY + 100;
588  int count = 0;
589 
590  for ( ; it != itEnd; ++it )
591  {
592  rect= (*it)->area;
593 
594  if(rect.isBottomOrLevel(startC) && rect.isRight(startC))
595  {
596  count++;
597  QRect entRect = rect.geometry(scaleX,scaleY);
598  int xdist, ydist;
599  xdist = entRect.center().x() - startC.x * scaleX;
600  ydist = entRect.center().y() - startC.y * scaleY;
601 
602  //make them positive
603  if(xdist < 0) xdist = -xdist;
604  if(ydist < 0) ydist = -ydist;
605 
606  if( (xdist + ydist) < distance)
607  {
608  distance = xdist+ ydist;
609  start = it;
610  }
611  }
612  }
613  }
614  }
615 
616  //case 3.b
617  if(end == itEnd)
618  {
619  it = tmpIt;
620  itEnd = itEnd-1;
621 
622  bool flagV = false;
623  NormalizedRect rect;
624 
625  if(startC.y <= endC.y)
626  {
627  for ( ; itEnd >= it; itEnd-- )
628  {
629  rect= (*itEnd)->area;
630  rect.isTop(endC) ? flagV = false: flagV = true;
631 
632  if(flagV && rect.isLeft(endC))
633  {
634  end = itEnd;
635  break;
636  }
637  }
638  }
639 
640  else
641  {
642  int distance = scaleX + scaleY + 100;
643  for ( ; itEnd >= it; itEnd-- )
644  {
645  rect= (*itEnd)->area;
646 
647  if(rect.isTopOrLevel(endC) && rect.isLeft(endC))
648  {
649  QRect entRect = rect.geometry(scaleX,scaleY);
650  int xdist, ydist;
651  xdist = entRect.center().x() - endC.x * scaleX;
652  ydist = entRect.center().y() - endC.y * scaleY;
653 
654  //make them positive
655  if(xdist < 0) xdist = -xdist;
656  if(ydist < 0) ydist = -ydist;
657 
658  if( (xdist + ydist) < distance)
659  {
660  distance = xdist+ ydist;
661  end = itEnd;
662  }
663 
664  }
665  }
666  }
667  }
668 
669  /* if start and end in selection 02 are in the same column, and we
670  start at an empty space we have to remove the selection of last
671  character
672  */
673  if(selection_two_start)
674  {
675  if(start > end)
676  {
677  start = start - 1;
678  }
679  }
680 
681  // if start is less than end swap them
682  if(start > end)
683  {
684  it = start;
685  start = end;
686  end = it;
687  }
688 
689  // removes the possibility of crash, in case none of 1 to 3 is true
690  if(end == d->m_words.constEnd()) end--;
691 
692  for( ;start <= end ; start++)
693  {
694  ret->appendShape( (*start)->transformedArea( matrix ), side );
695  }
696 
697 #endif
698 
699  return ret;
700 }
701 
702 
703 RegularAreaRect* TextPage::findText( int searchID, const QString &query, SearchDirection direct,
704  Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *area )
705 {
706  SearchDirection dir=direct;
707  // invalid search request
708  if ( d->m_words.isEmpty() || query.isEmpty() || ( area && area->isNull() ) )
709  return 0;
710  TextList::ConstIterator start;
711  int start_offset = 0;
712  TextList::ConstIterator end;
713  const QMap< int, SearchPoint* >::const_iterator sIt = d->m_searchPoints.constFind( searchID );
714  if ( sIt == d->m_searchPoints.constEnd() )
715  {
716  // if no previous run of this search is found, then set it to start
717  // from the beginning (respecting the search direction)
718  if ( dir == NextResult )
719  dir = FromTop;
720  else if ( dir == PreviousResult )
721  dir = FromBottom;
722  }
723  bool forward = true;
724  switch ( dir )
725  {
726  case FromTop:
727  start = d->m_words.constBegin();
728  start_offset = 0;
729  end = d->m_words.constEnd();
730  break;
731  case FromBottom:
732  start = d->m_words.constEnd();
733  start_offset = 0;
734  end = d->m_words.constBegin();
735  forward = false;
736  break;
737  case NextResult:
738  start = (*sIt)->it_end;
739  start_offset = (*sIt)->offset_end;
740  end = d->m_words.constEnd();
741  break;
742  case PreviousResult:
743  start = (*sIt)->it_begin;
744  start_offset = (*sIt)->offset_begin;
745  end = d->m_words.constBegin();
746  forward = false;
747  break;
748  };
749  RegularAreaRect* ret = 0;
750  const TextComparisonFunction cmpFn = caseSensitivity == Qt::CaseSensitive
751  ? CaseSensitiveCmpFn : CaseInsensitiveCmpFn;
752  if ( forward )
753  {
754  ret = d->findTextInternalForward( searchID, query, cmpFn, start, start_offset, end );
755  }
756  else
757  {
758  ret = d->findTextInternalBackward( searchID, query, cmpFn, start, start_offset, end );
759  }
760  return ret;
761 }
762 
763 // hyphenated '-' must be at the end of a word, so hyphenation means
764 // we have a '-' just followed by a '\n' character
765 // check if the string contains a '-' character
766 // if the '-' is the last entry
767 static int stringLengthAdaptedWithHyphen(const QString &str, const TextList::ConstIterator &it, const TextList::ConstIterator &textListEnd, PagePrivate *page)
768 {
769  int len = str.length();
770 
771  // hyphenated '-' must be at the end of a word, so hyphenation means
772  // we have a '-' just followed by a '\n' character
773  // check if the string contains a '-' character
774  // if the '-' is the last entry
775  if ( str.endsWith( '-' ) )
776  {
777  // validity chek of it + 1
778  if ( ( it + 1 ) != textListEnd )
779  {
780  // 1. if the next character is '\n'
781  const QString &lookahedStr = (*(it+1))->text();
782  if (lookahedStr.startsWith('\n'))
783  {
784  len -= 1;
785  }
786  else
787  {
788  // 2. if the next word is in a different line or not
789  const int pageWidth = page->m_page->width();
790  const int pageHeight = page->m_page->height();
791 
792  const QRect hyphenArea = (*it)->area.roundedGeometry(pageWidth, pageHeight);
793  const QRect lookaheadArea = (*(it + 1))->area.roundedGeometry(pageWidth, pageHeight);
794 
795  // lookahead to check whether both the '-' rect and next character rect overlap
796  if( !doesConsumeY( hyphenArea, lookaheadArea, 70 ) )
797  {
798  len -= 1;
799  }
800  }
801  }
802  }
803  // else if it is the second last entry - for example in pdf format
804  else if (str.endsWith("-\n"))
805  {
806  len -= 2;
807  }
808 
809  return len;
810 }
811 
812 RegularAreaRect* TextPagePrivate::searchPointToArea(const SearchPoint* sp)
813 {
814  const QTransform matrix = m_page ? m_page->rotationMatrix() : QTransform();
815  RegularAreaRect* ret=new RegularAreaRect;
816 
817  for (TextList::ConstIterator it = sp->it_begin; ; it++)
818  {
819  const TinyTextEntity* curEntity = *it;
820  ret->append( curEntity->transformedArea( matrix ) );
821 
822  if (it == sp->it_end) {
823  break;
824  }
825  }
826 
827  ret->simplify();
828  return ret;
829 }
830 
831 RegularAreaRect* TextPagePrivate::findTextInternalForward( int searchID, const QString &_query,
832  TextComparisonFunction comparer,
833  const TextList::ConstIterator &start,
834  int start_offset,
835  const TextList::ConstIterator &end)
836 {
837  // normalize query search all unicode (including glyphs)
838  const QString query = _query.normalized(QString::NormalizationForm_KC);
839 
840  // j is the current position in our query
841  // len is the length of the string in TextEntity
842  // queryLeft is the length of the query we have left
843  int j=0, queryLeft=query.length();
844 
845  TextList::ConstIterator it = start;
846  int offset = start_offset;
847 
848  TextList::ConstIterator it_begin = TextList::ConstIterator();
849  int offset_begin = 0; //dummy initial value to suppress compiler warnings
850 
851  while ( it != end )
852  {
853  const TinyTextEntity* curEntity = *it;
854  const QString& str = curEntity->text();
855  int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd(), m_page);
856 
857  if (offset >= len)
858  {
859  it++;
860  offset = 0;
861  continue;
862  }
863 
864  if ( it_begin == TextList::ConstIterator() )
865  {
866  it_begin = it;
867  offset_begin = offset;
868  }
869 
870  int min=qMin(queryLeft,len-offset);
871  {
872 #ifdef DEBUG_TEXTPAGE
873  kDebug(OkularDebug) << str.midRef(offset, min) << ":" << _query.midRef(j, min);
874 #endif
875  // we have equal (or less than) area of the query left as the length of the current
876  // entity
877 
878  if ( !comparer( str.midRef( offset, min ), query.midRef( j, min ) ) )
879  {
880  // we have not matched
881  // this means we do not have a complete match
882  // we need to get back to query start
883  // and continue the search from this place
884 #ifdef DEBUG_TEXTPAGE
885  kDebug(OkularDebug) << "\tnot matched";
886 #endif
887  j = 0;
888  queryLeft=query.length();
889  it = it_begin;
890  offset = offset_begin+1;
891  it_begin = TextList::ConstIterator();
892  }
893  else
894  {
895  // we have a match
896  // move the current position in the query
897  // to the position after the length of this string
898  // we matched
899  // subtract the length of the current entity from
900  // the left length of the query
901 #ifdef DEBUG_TEXTPAGE
902  kDebug(OkularDebug) << "\tmatched";
903 #endif
904  j += min;
905  queryLeft -= min;
906 
907  if (queryLeft==0)
908  {
909  // save or update the search point for the current searchID
910  QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID );
911  if ( sIt == m_searchPoints.end() )
912  {
913  sIt = m_searchPoints.insert( searchID, new SearchPoint );
914  }
915  SearchPoint* sp = *sIt;
916  sp->it_begin = it_begin;
917  sp->it_end = it;
918  sp->offset_begin = offset_begin;
919  sp->offset_end = offset + min;
920  return searchPointToArea(sp);
921  }
922 
923  it++;
924  offset = 0;
925  }
926  }
927  }
928  // end of loop - it means that we've ended the textentities
929 
930  const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID );
931  if ( sIt != m_searchPoints.end() )
932  {
933  SearchPoint* sp = *sIt;
934  m_searchPoints.erase( sIt );
935  delete sp;
936  }
937  return 0;
938 }
939 
940 RegularAreaRect* TextPagePrivate::findTextInternalBackward( int searchID, const QString &_query,
941  TextComparisonFunction comparer,
942  const TextList::ConstIterator &start,
943  int start_offset,
944  const TextList::ConstIterator &end)
945 {
946  // normalize query to search all unicode (including glyphs)
947  const QString query = _query.normalized(QString::NormalizationForm_KC);
948 
949  // j is the current position in our query
950  // len is the length of the string in TextEntity
951  // queryLeft is the length of the query we have left
952  int j=query.length(), queryLeft=query.length();
953 
954  TextList::ConstIterator it = start;
955  int offset = start_offset;
956 
957  TextList::ConstIterator it_begin = TextList::ConstIterator();
958  int offset_begin = 0; //dummy initial value to suppress compiler warnings
959 
960  while ( true )
961  {
962  if (offset <= 0)
963  {
964  if ( it == end )
965  {
966  break;
967  }
968  it--;
969  }
970 
971  const TinyTextEntity* curEntity = *it;
972  const QString& str = curEntity->text();
973  int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd(), m_page);
974 
975  if (offset <= 0)
976  {
977  offset = len;
978  }
979 
980  if ( it_begin == TextList::ConstIterator() )
981  {
982  it_begin = it;
983  offset_begin = offset;
984  }
985 
986  int min=qMin(queryLeft,offset);
987  {
988 #ifdef DEBUG_TEXTPAGE
989  kDebug(OkularDebug) << str.midRef(offset-min, min) << " : " << _query.midRef(j-min, min);
990 #endif
991  // we have equal (or less than) area of the query left as the length of the current
992  // entity
993 
994  // Note len is not str.length() so we can't use rightRef here
995  if ( !comparer( str.midRef(offset-min, min ), query.midRef( j - min, min ) ) )
996  {
997  // we have not matched
998  // this means we do not have a complete match
999  // we need to get back to query start
1000  // and continue the search from this place
1001 #ifdef DEBUG_TEXTPAGE
1002  kDebug(OkularDebug) << "\tnot matched";
1003 #endif
1004 
1005  j = query.length();
1006  queryLeft = query.length();
1007  it = it_begin;
1008  offset = offset_begin-1;
1009  it_begin = TextList::ConstIterator();
1010  }
1011  else
1012  {
1013  // we have a match
1014  // move the current position in the query
1015  // to the position after the length of this string
1016  // we matched
1017  // subtract the length of the current entity from
1018  // the left length of the query
1019 #ifdef DEBUG_TEXTPAGE
1020  kDebug(OkularDebug) << "\tmatched";
1021 #endif
1022  j -= min;
1023  queryLeft -= min;
1024 
1025  if ( queryLeft == 0 )
1026  {
1027  // save or update the search point for the current searchID
1028  QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID );
1029  if ( sIt == m_searchPoints.end() )
1030  {
1031  sIt = m_searchPoints.insert( searchID, new SearchPoint );
1032  }
1033  SearchPoint* sp = *sIt;
1034  sp->it_begin = it;
1035  sp->it_end = it_begin;
1036  sp->offset_begin = offset - min;
1037  sp->offset_end = offset_begin;
1038  return searchPointToArea(sp);
1039  }
1040 
1041  offset = 0;
1042  }
1043 
1044  }
1045 
1046  }
1047  // end of loop - it means that we've ended the textentities
1048 
1049  const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID );
1050  if ( sIt != m_searchPoints.end() )
1051  {
1052  SearchPoint* sp = *sIt;
1053  m_searchPoints.erase( sIt );
1054  delete sp;
1055  }
1056  return 0;
1057 }
1058 
1059 QString TextPage::text(const RegularAreaRect *area) const
1060 {
1061  return text(area, AnyPixelTextAreaInclusionBehaviour);
1062 }
1063 
1064 QString TextPage::text(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const
1065 {
1066  if ( area && area->isNull() )
1067  return QString();
1068 
1069  TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd();
1070  QString ret;
1071  if ( area )
1072  {
1073  for ( ; it != itEnd; ++it )
1074  {
1075  if (b == AnyPixelTextAreaInclusionBehaviour)
1076  {
1077  if ( area->intersects( (*it)->area ) )
1078  {
1079  ret += (*it)->text();
1080  }
1081  }
1082  else
1083  {
1084  NormalizedPoint center = (*it)->area.center();
1085  if ( area->contains( center.x, center.y ) )
1086  {
1087  ret += (*it)->text();
1088  }
1089  }
1090  }
1091  }
1092  else
1093  {
1094  for ( ; it != itEnd; ++it )
1095  ret += (*it)->text();
1096  }
1097  return ret;
1098 }
1099 
1100 static bool compareTinyTextEntityX(const WordWithCharacters &first, const WordWithCharacters &second)
1101 {
1102  QRect firstArea = first.area().roundedGeometry(1000,1000);
1103  QRect secondArea = second.area().roundedGeometry(1000,1000);
1104 
1105  return firstArea.left() < secondArea.left();
1106 }
1107 
1108 static bool compareTinyTextEntityY(const WordWithCharacters &first, const WordWithCharacters &second)
1109 {
1110  const QRect firstArea = first.area().roundedGeometry(1000,1000);
1111  const QRect secondArea = second.area().roundedGeometry(1000,1000);
1112 
1113  return firstArea.top() < secondArea.top();
1114 }
1115 
1119 void TextPagePrivate::setWordList(const TextList &list)
1120 {
1121  qDeleteAll(m_words);
1122  m_words = list;
1123 }
1124 
1129 static void removeSpace(TextList *words)
1130 {
1131  TextList::Iterator it = words->begin();
1132  const QString str(' ');
1133 
1134  while ( it != words->end() )
1135  {
1136  if((*it)->text() == str)
1137  {
1138  it = words->erase(it);
1139  }
1140  else
1141  {
1142  ++it;
1143  }
1144  }
1145 }
1146 
1154 static WordsWithCharacters makeWordFromCharacters(const TextList &characters, int pageWidth, int pageHeight)
1155 {
1167  WordsWithCharacters wordsWithCharacters;
1168 
1169  TextList::ConstIterator it = characters.begin(), itEnd = characters.end(), tmpIt;
1170  int newLeft,newRight,newTop,newBottom;
1171  int index = 0;
1172 
1173  for( ; it != itEnd ; it++)
1174  {
1175  QString textString = (*it)->text();
1176  QString newString;
1177  QRect lineArea = (*it)->area.roundedGeometry(pageWidth,pageHeight),elementArea;
1178  TextList wordCharacters;
1179  tmpIt = it;
1180  int space = 0;
1181 
1182  while (!space)
1183  {
1184  if (textString.length())
1185  {
1186  newString.append(textString);
1187 
1188  // when textString is the start of the word
1189  if (tmpIt == it)
1190  {
1191  NormalizedRect newRect(lineArea,pageWidth,pageHeight);
1192  wordCharacters.append(new TinyTextEntity(textString.normalized
1193  (QString::NormalizationForm_KC), newRect));
1194  }
1195  else
1196  {
1197  NormalizedRect newRect(elementArea,pageWidth,pageHeight);
1198  wordCharacters.append(new TinyTextEntity(textString.normalized
1199  (QString::NormalizationForm_KC), newRect));
1200  }
1201  }
1202 
1203  ++it;
1204 
1205  /*
1206  we must have to put this line before the if condition of it==itEnd
1207  otherwise the last character can be missed
1208  */
1209  if (it == itEnd) break;
1210  elementArea = (*it)->area.roundedGeometry(pageWidth,pageHeight);
1211  if (!doesConsumeY(elementArea, lineArea, 60))
1212  {
1213  --it;
1214  break;
1215  }
1216 
1217  const int text_y1 = elementArea.top() ,
1218  text_x1 = elementArea.left(),
1219  text_y2 = elementArea.y() + elementArea.height(),
1220  text_x2 = elementArea.x() + elementArea.width();
1221  const int line_y1 = lineArea.top() ,line_x1 = lineArea.left(),
1222  line_y2 = lineArea.y() + lineArea.height(),
1223  line_x2 = lineArea.x() + lineArea.width();
1224 
1225  space = elementArea.left() - lineArea.right();
1226 
1227  if (space != 0)
1228  {
1229  it--;
1230  break;
1231  }
1232 
1233  newLeft = text_x1 < line_x1 ? text_x1 : line_x1;
1234  newRight = line_x2 > text_x2 ? line_x2 : text_x2;
1235  newTop = text_y1 > line_y1 ? line_y1 : text_y1;
1236  newBottom = text_y2 > line_y2 ? text_y2 : line_y2;
1237 
1238  lineArea.setLeft (newLeft);
1239  lineArea.setTop (newTop);
1240  lineArea.setWidth( newRight - newLeft );
1241  lineArea.setHeight( newBottom - newTop );
1242 
1243  textString = (*it)->text();
1244  }
1245 
1246  // if newString is not empty, save it
1247  if (!newString.isEmpty())
1248  {
1249  const NormalizedRect newRect(lineArea, pageWidth, pageHeight);
1250  TinyTextEntity *word = new TinyTextEntity(newString.normalized(QString::NormalizationForm_KC), newRect);
1251  wordsWithCharacters.append(WordWithCharacters(word, wordCharacters));
1252 
1253  index++;
1254  }
1255 
1256  if(it == itEnd) break;
1257  }
1258 
1259  return wordsWithCharacters;
1260 }
1261 
1265 QList< QPair<WordsWithCharacters, QRect> > makeAndSortLines(const WordsWithCharacters &wordsTmp, int pageWidth, int pageHeight)
1266 {
1278  QList< QPair<WordsWithCharacters, QRect> > lines;
1279 
1280  /*
1281  Make a new copy of the TextList in the words, so that the wordsTmp and lines do
1282  not contain same pointers for all the TinyTextEntity.
1283  */
1284  QList<WordWithCharacters> words = wordsTmp;
1285 
1286  // Step 1
1287  qSort(words.begin(),words.end(),compareTinyTextEntityY);
1288 
1289  // Step 2
1290  QList<WordWithCharacters>::Iterator it = words.begin(), itEnd = words.end();
1291 
1292  //for every non-space texts(characters/words) in the textList
1293  for( ; it != itEnd ; it++)
1294  {
1295  const QRect elementArea = (*it).area().roundedGeometry(pageWidth,pageHeight);
1296  bool found = false;
1297 
1298  for( int i = 0 ; i < lines.length() ; i++)
1299  {
1300  /* the line area which will be expanded
1301  line_rects is only necessary to preserve the topmin and bottommax of all
1302  the texts in the line, left and right is not necessary at all
1303  */
1304  QRect &lineArea = lines[i].second;
1305  const int text_y1 = elementArea.top() ,
1306  text_y2 = elementArea.top() + elementArea.height() ,
1307  text_x1 = elementArea.left(),
1308  text_x2 = elementArea.left() + elementArea.width();
1309  const int line_y1 = lineArea.top() ,
1310  line_y2 = lineArea.top() + lineArea.height(),
1311  line_x1 = lineArea.left(),
1312  line_x2 = lineArea.left() + lineArea.width();
1313 
1314  /*
1315  if the new text and the line has y overlapping parts of more than 70%,
1316  the text will be added to this line
1317  */
1318  if(doesConsumeY(elementArea,lineArea,70))
1319  {
1320  WordsWithCharacters &line = lines[i].first;
1321  line.append(*it);
1322 
1323  const int newLeft = line_x1 < text_x1 ? line_x1 : text_x1;
1324  const int newRight = line_x2 > text_x2 ? line_x2 : text_x2;
1325  const int newTop = line_y1 < text_y1 ? line_y1 : text_y1;
1326  const int newBottom = text_y2 > line_y2 ? text_y2 : line_y2;
1327 
1328  lineArea = QRect( newLeft,newTop, newRight - newLeft, newBottom - newTop );
1329  found = true;
1330  }
1331 
1332  if(found) break;
1333  }
1334 
1335  /* when we have found a new line create a new TextList containing
1336  only one element and append it to the lines
1337  */
1338  if(!found)
1339  {
1340  WordsWithCharacters tmp;
1341  tmp.append((*it));
1342  lines.append(QPair<WordsWithCharacters, QRect>(tmp, elementArea));
1343  }
1344  }
1345 
1346  // Step 3
1347  for(int i = 0 ; i < lines.length() ; i++)
1348  {
1349  WordsWithCharacters &list = lines[i].first;
1350  qSort(list.begin(), list.end(), compareTinyTextEntityX);
1351  }
1352 
1353  return lines;
1354 }
1355 
1359 static void calculateStatisticalInformation(const QList<WordWithCharacters> &words, int pageWidth, int pageHeight, int *word_spacing, int *line_spacing, int *col_spacing)
1360 {
1371  const QList< QPair<WordsWithCharacters, QRect> > sortedLines = makeAndSortLines(words, pageWidth, pageHeight);
1372 
1376  QMap<int,int> line_space_stat;
1377  for(int i = 0 ; i < sortedLines.length(); i++)
1378  {
1379  const QRect rectUpper = sortedLines.at(i).second;
1380 
1381  if(i+1 == sortedLines.length()) break;
1382  const QRect rectLower = sortedLines.at(i+1).second;
1383 
1384  int linespace = rectLower.top() - (rectUpper.top() + rectUpper.height());
1385  if(linespace < 0) linespace =-linespace;
1386 
1387  if(line_space_stat.contains(linespace))
1388  line_space_stat[linespace]++;
1389  else line_space_stat[linespace] = 1;
1390  }
1391 
1392  *line_spacing = 0;
1393  int weighted_count = 0;
1394  QMapIterator<int, int> iterate_linespace(line_space_stat);
1395 
1396  while(iterate_linespace.hasNext())
1397  {
1398  iterate_linespace.next();
1399  *line_spacing += iterate_linespace.value() * iterate_linespace.key();
1400  weighted_count += iterate_linespace.value();
1401  }
1402  if (*line_spacing != 0)
1403  *line_spacing = (int) ( (double)*line_spacing / (double) weighted_count + 0.5);
1404 
1408  // We would like to use QMap instead of QHash as it will keep the keys sorted
1409  QMap<int,int> hor_space_stat;
1410  QMap<int,int> col_space_stat;
1411  QList< QList<QRect> > space_rects;
1412  QList<QRect> max_hor_space_rects;
1413 
1414  // Space in every line
1415  for(int i = 0 ; i < sortedLines.length() ; i++)
1416  {
1417  const WordsWithCharacters list = sortedLines.at(i).first;
1418  QList<QRect> line_space_rects;
1419  int maxSpace = 0, minSpace = pageWidth;
1420 
1421  // for every TinyTextEntity element in the line
1422  WordsWithCharacters::ConstIterator it = list.begin(), itEnd = list.end();
1423  QRect max_area1,max_area2;
1424  QString before_max, after_max;
1425 
1426  // for every line
1427  for( ; it != itEnd ; it++ )
1428  {
1429  const QRect area1 = (*it).area().roundedGeometry(pageWidth,pageHeight);
1430  if( it+1 == itEnd ) break;
1431 
1432  const QRect area2 = (*(it+1)).area().roundedGeometry(pageWidth,pageHeight);
1433  int space = area2.left() - area1.right();
1434 
1435  if(space > maxSpace)
1436  {
1437  max_area1 = area1;
1438  max_area2 = area2;
1439  maxSpace = space;
1440  before_max = (*it).text();
1441  after_max = (*(it+1)).text();
1442  }
1443 
1444  if(space < minSpace && space != 0) minSpace = space;
1445 
1446  //if we found a real space, whose length is not zero and also less than the pageWidth
1447  if(space != 0 && space != pageWidth)
1448  {
1449  // increase the count of the space amount
1450  if(hor_space_stat.contains(space)) hor_space_stat[space] = hor_space_stat[space]++;
1451  else hor_space_stat[space] = 1;
1452 
1453  int left,right,top,bottom;
1454 
1455  left = area1.right();
1456  right = area2.left();
1457 
1458  top = area2.top() < area1.top() ? area2.top() : area1.top();
1459  bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom();
1460 
1461  QRect rect(left,top,right-left,bottom-top);
1462  line_space_rects.append(rect);
1463  }
1464  }
1465 
1466  space_rects.append(line_space_rects);
1467 
1468  if(hor_space_stat.contains(maxSpace))
1469  {
1470  if(hor_space_stat[maxSpace] != 1)
1471  hor_space_stat[maxSpace] = hor_space_stat[maxSpace]--;
1472  else hor_space_stat.remove(maxSpace);
1473  }
1474 
1475  if(maxSpace != 0)
1476  {
1477  if (col_space_stat.contains(maxSpace))
1478  col_space_stat[maxSpace] = col_space_stat[maxSpace]++;
1479  else col_space_stat[maxSpace] = 1;
1480 
1481  //store the max rect of each line
1482  const int left = max_area1.right();
1483  const int right = max_area2.left();
1484  const int top = (max_area1.top() > max_area2.top()) ? max_area2.top() :
1485  max_area1.top();
1486  const int bottom = (max_area1.bottom() < max_area2.bottom()) ? max_area2.bottom() :
1487  max_area1.bottom();
1488 
1489  const QRect rect(left,top,right-left,bottom-top);
1490  max_hor_space_rects.append(rect);
1491  }
1492  else max_hor_space_rects.append(QRect(0,0,0,0));
1493  }
1494 
1495  // All the between word space counts are in hor_space_stat
1496  *word_spacing = 0;
1497  weighted_count = 0;
1498  QMapIterator<int, int> iterate(hor_space_stat);
1499 
1500  while (iterate.hasNext())
1501  {
1502  iterate.next();
1503 
1504  if(iterate.key() > 0)
1505  {
1506  *word_spacing += iterate.value() * iterate.key();
1507  weighted_count += iterate.value();
1508  }
1509  }
1510  if(weighted_count)
1511  *word_spacing = (int) ((double)*word_spacing / (double)weighted_count + 0.5);
1512 
1513  *col_spacing = 0;
1514  QMapIterator<int, int> iterate_col(col_space_stat);
1515 
1516  while (iterate_col.hasNext())
1517  {
1518  iterate_col.next();
1519  if(iterate_col.value() > *col_spacing) *col_spacing = iterate_col.value();
1520  }
1521  *col_spacing = col_space_stat.key(*col_spacing);
1522 
1523  // if there is just one line in a region, there is no point in dividing it
1524  if(sortedLines.length() == 1)
1525  *word_spacing = *col_spacing;
1526 }
1527 
1533 static RegionTextList XYCutForBoundingBoxes(const QList<WordWithCharacters> &wordsWithCharacters, const NormalizedRect &boundingBox, int pageWidth, int pageHeight)
1534 {
1535  RegionTextList tree;
1536  QRect contentRect(boundingBox.geometry(pageWidth,pageHeight));
1537  const RegionText root(wordsWithCharacters, contentRect);
1538 
1539  // start the tree with the root, it is our only region at the start
1540  tree.push_back(root);
1541 
1542  int i = 0;
1543 
1544  // while traversing the tree has not been ended
1545  while(i < tree.length())
1546  {
1547  const RegionText node = tree.at(i);
1548  QRect regionRect = node.area();
1549 
1553  // allocate the size of proj profiles and initialize with 0
1554  int size_proj_y = node.area().height();
1555  int size_proj_x = node.area().width();
1556  //dynamic memory allocation
1557  QVarLengthArray<int> proj_on_xaxis(size_proj_x);
1558  QVarLengthArray<int> proj_on_yaxis(size_proj_y);
1559 
1560  for( int j = 0 ; j < size_proj_y ; ++j ) proj_on_yaxis[j] = 0;
1561  for( int j = 0 ; j < size_proj_x ; ++j ) proj_on_xaxis[j] = 0;
1562 
1563  const QList<WordWithCharacters> list = node.text();
1564 
1565  // Calculate tcx and tcy locally for each new region
1566  int word_spacing, line_spacing, column_spacing;
1567  calculateStatisticalInformation(list, pageWidth, pageHeight, &word_spacing, &line_spacing, &column_spacing);
1568 
1569  const int tcx = word_spacing * 2;
1570  const int tcy = line_spacing * 2;
1571 
1572  int maxX = 0 , maxY = 0;
1573  int avgX = 0;
1574  int count;
1575 
1576  // for every text in the region
1577  for(int j = 0 ; j < list.length() ; ++j )
1578  {
1579  TinyTextEntity *ent = list.at(j).word;
1580  const QRect entRect = ent->area.geometry(pageWidth, pageHeight);
1581 
1582  // calculate vertical projection profile proj_on_xaxis1
1583  for(int k = entRect.left() ; k <= entRect.left() + entRect.width() ; ++k)
1584  {
1585  if( ( k-regionRect.left() ) < size_proj_x && ( k-regionRect.left() ) >= 0 )
1586  proj_on_xaxis[k - regionRect.left()] += entRect.height();
1587  }
1588 
1589  // calculate horizontal projection profile in the same way
1590  for(int k = entRect.top() ; k <= entRect.top() + entRect.height() ; ++k)
1591  {
1592  if( ( k-regionRect.top() ) < size_proj_y && ( k-regionRect.top() ) >= 0 )
1593  proj_on_yaxis[k - regionRect.top()] += entRect.width();
1594  }
1595  }
1596 
1597  for( int j = 0 ; j < size_proj_y ; ++j )
1598  {
1599  if (proj_on_yaxis[j] > maxY)
1600  maxY = proj_on_yaxis[j];
1601  }
1602 
1603  avgX = count = 0;
1604  for( int j = 0 ; j < size_proj_x ; ++j )
1605  {
1606  if(proj_on_xaxis[j] > maxX) maxX = proj_on_xaxis[j];
1607  if(proj_on_xaxis[j])
1608  {
1609  count++;
1610  avgX+= proj_on_xaxis[j];
1611  }
1612  }
1613  if(count) avgX /= count;
1614 
1615 
1619  int xbegin = 0, xend = size_proj_x - 1;
1620  int ybegin = 0, yend = size_proj_y - 1;
1621  while(xbegin < size_proj_x && proj_on_xaxis[xbegin] <= 0)
1622  xbegin++;
1623  while(xend >= 0 && proj_on_xaxis[xend] <= 0)
1624  xend--;
1625  while(ybegin < size_proj_y && proj_on_yaxis[ybegin] <= 0)
1626  ybegin++;
1627  while(yend >= 0 && proj_on_yaxis[yend] <= 0)
1628  yend--;
1629 
1630  //update the regionRect
1631  int old_left = regionRect.left(), old_top = regionRect.top();
1632  regionRect.setLeft(old_left + xbegin);
1633  regionRect.setRight(old_left + xend);
1634  regionRect.setTop(old_top + ybegin);
1635  regionRect.setBottom(old_top + yend);
1636 
1637  int tnx = (int)((double)avgX * 10.0 / 100.0 + 0.5), tny = 0;
1638  for( int j = 0 ; j < size_proj_x ; ++j )
1639  proj_on_xaxis[j] -= tnx;
1640  for( int j = 0 ; j < size_proj_y ; ++j )
1641  proj_on_yaxis[j] -= tny;
1642 
1646  int gap_hor = -1, pos_hor = -1;
1647  int begin = -1, end = -1;
1648 
1649  // find all hor_gaps and find the maximum between them
1650  for(int j = 1 ; j < size_proj_y ; ++j)
1651  {
1652  //transition from white to black
1653  if(begin >= 0 && proj_on_yaxis[j-1] <= 0
1654  && proj_on_yaxis[j] > 0)
1655  end = j;
1656 
1657  //transition from black to white
1658  if(proj_on_yaxis[j-1] > 0 && proj_on_yaxis[j] <= 0)
1659  begin = j;
1660 
1661  if(begin > 0 && end > 0 && end-begin > gap_hor)
1662  {
1663  gap_hor = end - begin;
1664  pos_hor = (end + begin) / 2;
1665  begin = -1;
1666  end = -1;
1667  }
1668  }
1669 
1670 
1671  begin = -1, end = -1;
1672  int gap_ver = -1, pos_ver = -1;
1673 
1674  //find all the ver_gaps and find the maximum between them
1675  for(int j = 1 ; j < size_proj_x ; ++j)
1676  {
1677  //transition from white to black
1678  if(begin >= 0 && proj_on_xaxis[j-1] <= 0
1679  && proj_on_xaxis[j] > 0){
1680  end = j;
1681  }
1682 
1683  //transition from black to white
1684  if(proj_on_xaxis[j-1] > 0 && proj_on_xaxis[j] <= 0)
1685  begin = j;
1686 
1687  if(begin > 0 && end > 0 && end-begin > gap_ver)
1688  {
1689  gap_ver = end - begin;
1690  pos_ver = (end + begin) / 2;
1691  begin = -1;
1692  end = -1;
1693  }
1694  }
1695 
1696  int cut_pos_x = pos_ver, cut_pos_y = pos_hor;
1697  int gap_x = gap_ver, gap_y = gap_hor;
1698 
1702  bool cut_hor = false, cut_ver = false;
1703 
1704  // For horizontal cut
1705  const int topHeight = cut_pos_y - (regionRect.top() - old_top);
1706  const QRect topRect(regionRect.left(),
1707  regionRect.top(),
1708  regionRect.width(),
1709  topHeight);
1710  const QRect bottomRect(regionRect.left(),
1711  regionRect.top() + topHeight,
1712  regionRect.width(),
1713  regionRect.height() - topHeight );
1714 
1715  // For vertical Cut
1716  const int leftWidth = cut_pos_x - (regionRect.left() - old_left);
1717  const QRect leftRect(regionRect.left(),
1718  regionRect.top(),
1719  leftWidth,
1720  regionRect.height());
1721  const QRect rightRect(regionRect.left() + leftWidth,
1722  regionRect.top(),
1723  regionRect.width() - leftWidth,
1724  regionRect.height());
1725 
1726  if(gap_y >= gap_x && gap_y >= tcy)
1727  cut_hor = true;
1728  else if(gap_y >= gap_x && gap_y <= tcy && gap_x >= tcx)
1729  cut_ver = true;
1730  else if(gap_x >= gap_y && gap_x >= tcx)
1731  cut_ver = true;
1732  else if(gap_x >= gap_y && gap_x <= tcx && gap_y >= tcy)
1733  cut_hor = true;
1734  // no cut possible
1735  else
1736  {
1737  // we can now update the node rectangle with the shrinked rectangle
1738  RegionText tmpNode = tree.at(i);
1739  tmpNode.setArea(regionRect);
1740  tree.replace(i,tmpNode);
1741  i++;
1742  continue;
1743  }
1744 
1745  WordsWithCharacters list1,list2;
1746 
1747  // horizontal cut, topRect and bottomRect
1748  if(cut_hor)
1749  {
1750  for( int j = 0 ; j < list.length() ; ++j )
1751  {
1752  const WordWithCharacters word = list.at(j);
1753  const QRect wordRect = word.area().geometry(pageWidth,pageHeight);
1754 
1755  if(topRect.intersects(wordRect))
1756  list1.append(word);
1757  else
1758  list2.append(word);
1759  }
1760 
1761  RegionText node1(list1,topRect);
1762  RegionText node2(list2,bottomRect);
1763 
1764  tree.replace(i,node1);
1765  tree.insert(i+1,node2);
1766  }
1767 
1768  //vertical cut, leftRect and rightRect
1769  else if(cut_ver)
1770  {
1771  for( int j = 0 ; j < list.length() ; ++j )
1772  {
1773  const WordWithCharacters word = list.at(j);
1774  const QRect wordRect = word.area().geometry(pageWidth,pageHeight);
1775 
1776  if(leftRect.intersects(wordRect))
1777  list1.append(word);
1778  else
1779  list2.append(word);
1780  }
1781 
1782  RegionText node1(list1,leftRect);
1783  RegionText node2(list2,rightRect);
1784 
1785  tree.replace(i,node1);
1786  tree.insert(i+1,node2);
1787  }
1788  }
1789 
1790  return tree;
1791 }
1792 
1796 WordsWithCharacters addNecessarySpace(RegionTextList tree, int pageWidth, int pageHeight)
1797 {
1804  // Only change the texts under RegionTexts, not the area
1805  for(int j = 0 ; j < tree.length() ; j++)
1806  {
1807  RegionText &tmpRegion = tree[j];
1808 
1809  // Step 01
1810  QList< QPair<WordsWithCharacters, QRect> > sortedLines = makeAndSortLines(tmpRegion.text(), pageWidth, pageHeight);
1811 
1812  // Step 02
1813  for(int i = 0 ; i < sortedLines.length() ; i++)
1814  {
1815  WordsWithCharacters &list = sortedLines[i].first;
1816  for(int k = 0 ; k < list.length() ; k++ )
1817  {
1818  const QRect area1 = list.at(k).area().roundedGeometry(pageWidth,pageHeight);
1819  if( k+1 >= list.length() ) break;
1820 
1821  const QRect area2 = list.at(k+1).area().roundedGeometry(pageWidth,pageHeight);
1822  const int space = area2.left() - area1.right();
1823 
1824  if(space != 0)
1825  {
1826  // Make a TinyTextEntity of string space and push it between it and it+1
1827  const int left = area1.right();
1828  const int right = area2.left();
1829  const int top = area2.top() < area1.top() ? area2.top() : area1.top();
1830  const int bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom();
1831 
1832  const QString spaceStr(" ");
1833  const QRect rect(QPoint(left,top),QPoint(right,bottom));
1834  const NormalizedRect entRect(rect,pageWidth,pageHeight);
1835  TinyTextEntity *ent1 = new TinyTextEntity(spaceStr, entRect);
1836  TinyTextEntity *ent2 = new TinyTextEntity(spaceStr, entRect);
1837  WordWithCharacters word(ent1, QList<TinyTextEntity*>() << ent2);
1838 
1839  list.insert(k+1, word);
1840 
1841  // Skip the space
1842  k++;
1843  }
1844  }
1845  }
1846 
1847  WordsWithCharacters tmpList;
1848  for(int i = 0 ; i < sortedLines.length() ; i++)
1849  {
1850  tmpList += sortedLines.at(i).first;
1851  }
1852  tmpRegion.setText(tmpList);
1853  }
1854 
1855  // Step 03
1856  WordsWithCharacters tmp;
1857  for(int i = 0 ; i < tree.length() ; i++)
1858  {
1859  tmp += tree.at(i).text();
1860  }
1861  return tmp;
1862 }
1863 
1867 void TextPagePrivate::correctTextOrder()
1868 {
1869  const int pageWidth = m_page->m_page->width();
1870  const int pageHeight = m_page->m_page->height();
1871 
1872  TextList characters = m_words;
1873 
1877  removeSpace(&characters);
1878 
1882  const QList<WordWithCharacters> wordsWithCharacters = makeWordFromCharacters(characters, pageWidth, pageHeight);
1883 
1887  const RegionTextList tree = XYCutForBoundingBoxes(wordsWithCharacters, m_page->m_page->boundingBox(), pageWidth, pageHeight);
1888 
1892  const WordsWithCharacters listWithWordsAndSpaces = addNecessarySpace(tree, pageWidth, pageHeight);
1893 
1897  TextList listOfCharacters;
1898  foreach(const WordWithCharacters &word, listWithWordsAndSpaces)
1899  {
1900  delete word.word;
1901  listOfCharacters.append(word.characters);
1902  }
1903  setWordList(listOfCharacters);
1904 }
1905 
1906 TextEntity::List TextPage::words(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const
1907 {
1908  if ( area && area->isNull() )
1909  return TextEntity::List();
1910 
1911  TextEntity::List ret;
1912  if ( area )
1913  {
1914  foreach (TinyTextEntity *te, d->m_words)
1915  {
1916  if (b == AnyPixelTextAreaInclusionBehaviour)
1917  {
1918  if ( area->intersects( te->area ) )
1919  {
1920  ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) );
1921  }
1922  }
1923  else
1924  {
1925  const NormalizedPoint center = te->area.center();
1926  if ( area->contains( center.x, center.y ) )
1927  {
1928  ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) );
1929  }
1930  }
1931  }
1932  }
1933  else
1934  {
1935  foreach (TinyTextEntity *te, d->m_words)
1936  {
1937  ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) );
1938  }
1939  }
1940  return ret;
1941 }
1942 
1943 RegularAreaRect * TextPage::wordAt( const NormalizedPoint &p, QString *word ) const
1944 {
1945  TextList::ConstIterator itBegin = d->m_words.constBegin(), itEnd = d->m_words.constEnd();
1946  TextList::ConstIterator it = itBegin;
1947  TextList::ConstIterator posIt = itEnd;
1948  for ( ; it != itEnd; ++it )
1949  {
1950  if ( (*it)->area.contains( p.x, p.y ) )
1951  {
1952  posIt = it;
1953  break;
1954  }
1955  }
1956  QString text;
1957  if ( posIt != itEnd )
1958  {
1959  if ( (*posIt)->text().simplified().isEmpty() )
1960  {
1961  return NULL;
1962  }
1963  // Find the first TinyTextEntity of the word
1964  while ( posIt != itBegin )
1965  {
1966  --posIt;
1967  const QString itText = (*posIt)->text();
1968  if ( itText.right(1).at(0).isSpace() )
1969  {
1970  if (itText.endsWith("-\n"))
1971  {
1972  // Is an hyphenated word
1973  // continue searching the start of the word back
1974  continue;
1975  }
1976 
1977  if (itText == "\n" && posIt != itBegin )
1978  {
1979  --posIt;
1980  if ((*posIt)->text().endsWith("-")) {
1981  // Is an hyphenated word
1982  // continue searching the start of the word back
1983  continue;
1984  }
1985  ++posIt;
1986  }
1987 
1988  ++posIt;
1989  break;
1990  }
1991  }
1992  RegularAreaRect *ret = new RegularAreaRect();
1993  for ( ; posIt != itEnd; ++posIt )
1994  {
1995  const QString itText = (*posIt)->text();
1996  if ( itText.simplified().isEmpty() )
1997  {
1998  break;
1999  }
2000 
2001  ret->appendShape( (*posIt)->area );
2002  text += (*posIt)->text();
2003  if (itText.right(1).at(0).isSpace())
2004  {
2005  if (!text.endsWith("-\n"))
2006  {
2007  break;
2008  }
2009  }
2010  }
2011 
2012  if (word)
2013  {
2014  *word = text;
2015  }
2016  return ret;
2017  }
2018  else
2019  {
2020  return NULL;
2021  }
2022 }
Okular::SearchDirection
SearchDirection
Describes the direction of searching.
Definition: global.h:33
Okular::NormalizedPoint
NormalizedPoint is a helper class which stores the coordinates of a normalized point.
Definition: area.h:47
Okular::TextEntity::area
NormalizedRect * area() const
Returns the bounding area of the text entity.
Definition: textpage.cpp:191
Okular::TextPagePrivate::m_words
TextList m_words
Definition: textpage_p.h:69
Okular::NextResult
Searching for the next result on the page, earlier result should be located so we search from the las...
Definition: global.h:37
Okular::TextPagePrivate::~TextPagePrivate
~TextPagePrivate()
Definition: textpage.cpp:209
Okular::PagePrivate
Definition: page_p.h:55
misc.h
Okular::TextSelection::direction
int direction() const
Returns the direction of the selection.
Definition: misc.cpp:66
Okular::RegionTextList
QList< RegionText > RegionTextList
A list of RegionText.
Definition: textpage_p.h:38
Okular::NormalizedRect::isRight
bool isRight(const NormalizedPoint &pt) const
Returns true if the point pt is located to the left of the right arm of rectangle.
Definition: area.h:276
Okular::TextPagePrivate::m_page
PagePrivate * m_page
Definition: textpage_p.h:71
Okular::MergeSide
MergeSide
The side(s) to be considered when merging areas.
Definition: global.h:64
Okular::TextPage::TextPage
TextPage()
Creates a new text page.
Definition: textpage.cpp:216
Okular::PagePrivate::m_page
Page * m_page
Definition: page_p.h:122
Okular::TextPagePrivate::correctTextOrder
void correctTextOrder()
Make necessary modifications in the TextList to make the text order correct, so that textselection wo...
Definition: textpage.cpp:1867
Okular::TextEntity::text
QString text() const
Returns the text of the text entity.
Definition: textpage.cpp:186
doesConsumeY
static bool doesConsumeY(const QRect &first, const QRect &second, int threshold)
If the vertical arm of one rectangle fully contains the other (example below) -----— -— --— first ...
Definition: textpage.cpp:71
Okular::NormalizedRect::transform
void transform(const QTransform &matrix)
Transforms the normalized rectangle with the operations defined by matrix.
Definition: area.cpp:259
Okular::Page::totalOrientation
Rotation totalOrientation() const
Returns the total orientation which is the original orientation plus the user defined rotation...
Definition: page.cpp:175
Okular::TextSelection::itE
void itE(int pos)
Definition: misc.cpp:56
Okular::RegularArea::contains
bool contains(double x, double y) const
Returns whether the regular area contains the normalized point x, y.
Definition: area.h:800
makeAndSortLines
QList< QPair< WordsWithCharacters, QRect > > makeAndSortLines(const WordsWithCharacters &wordsTmp, int pageWidth, int pageHeight)
Create Lines from the words and sort them.
Definition: textpage.cpp:1265
Okular::NormalizedRect::left
double left
The normalized left coordinate.
Definition: area.h:305
Okular::TextPagePrivate::findTextInternalBackward
RegularAreaRect * findTextInternalBackward(int searchID, const QString &query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end)
Definition: textpage.cpp:940
Okular::NormalizedRect
NormalizedRect is a helper class which stores the coordinates of a normalized rect, which is a rectangle of.
Definition: area.h:105
Okular::RegularArea::appendShape
void appendShape(const NormalizedShape &shape, MergeSide side=MergeAll)
Appends the given shape to the regular area.
Definition: area.h:725
debug_p.h
area.h
Okular::RegularAreaRect
Definition: area.h:860
Okular::MergeRight
Merge only if the right side of the first area intersect.
Definition: global.h:66
Okular::NormalizedPoint::y
double y
The normalized y coordinate.
Definition: area.h:97
page_p.h
Okular::RegularArea::intersects
bool intersects(const RegularArea< NormalizedShape, Shape > *area) const
Returns whether the regular area intersects with the given area.
Definition: area.h:690
Okular::TextSelection::start
NormalizedPoint start() const
Returns the start point of the selection.
Definition: misc.cpp:71
Okular::TextPagePrivate::findTextInternalForward
RegularAreaRect * findTextInternalForward(int searchID, const QString &query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end)
Definition: textpage.cpp:831
Okular::NormalizedRect::intersects
bool intersects(const NormalizedRect &other) const
Returns whether the normalized rectangle intersects the other normalized rectangle.
Definition: area.cpp:161
page.h
calculateStatisticalInformation
static void calculateStatisticalInformation(const QList< WordWithCharacters > &words, int pageWidth, int pageHeight, int *word_spacing, int *line_spacing, int *col_spacing)
Calculate Statistical information from the lines we made previously.
Definition: textpage.cpp:1359
Okular::FromTop
Searching from top of the page, next result is to be found, there was no earlier search result...
Definition: global.h:35
makeWordFromCharacters
static WordsWithCharacters makeWordFromCharacters(const TextList &characters, int pageWidth, int pageHeight)
We will read the TinyTextEntity from characters and try to create words from there.
Definition: textpage.cpp:1154
Okular::TextPagePrivate::TextPagePrivate
TextPagePrivate()
Definition: textpage.cpp:204
CaseSensitiveCmpFn
static bool CaseSensitiveCmpFn(const QStringRef &from, const QStringRef &to)
Definition: textpage.cpp:60
Okular::FromBottom
Searching from bottom of the page, next result is to be found, there was no earlier search result...
Definition: global.h:36
Okular::TextPagePrivate::setWordList
void setWordList(const TextList &list)
Copy a TextList to m_words, the pointers of list are adopted.
Definition: textpage.cpp:1119
Okular::TextEntity
Abstract textentity of Okular.
Definition: textpage.h:44
Okular::TextComparisonFunction
bool(* TextComparisonFunction)(const QStringRef &from, const QStringRef &to)
Returns whether the two strings match.
Definition: textpage_p.h:33
Okular::TextEntity::transformedArea
NormalizedRect transformedArea(const QTransform &matrix) const
Returns the transformed area of the text entity.
Definition: textpage.cpp:196
Okular::Page::height
double height() const
Returns the height of the page.
Definition: page.cpp:185
Okular::NormalizedRect::contains
bool contains(double x, double y) const
Returns whether the normalized rectangle contains the normalized coordinates x and y...
Definition: area.cpp:156
Okular::NormalizedRect::right
double right
The normalized right coordinate.
Definition: area.h:315
Okular::RegularArea::isNull
bool isNull() const
Returns whether the regular area is a null area.
Definition: area.h:656
Okular::PreviousResult
Searching for the previous result on the page, earlier result should be located so we search from the...
Definition: global.h:38
CaseInsensitiveCmpFn
static bool CaseInsensitiveCmpFn(const QStringRef &from, const QStringRef &to)
Definition: textpage.cpp:55
removeSpace
static void removeSpace(TextList *words)
Remove all the spaces in between texts.
Definition: textpage.cpp:1129
Okular::TextPage::words
TextEntity::List words(const RegularAreaRect *rect, TextAreaInclusionBehaviour b) const
Text entity extraction function.
Definition: textpage.cpp:1906
Okular::NormalizedRect::isTopOrLevel
bool isTopOrLevel(const NormalizedPoint &pt) const
Returns true if the point pt is located above the bottom of the rectangle.
Definition: area.h:258
OkularDebug
#define OkularDebug
Definition: debug_p.h:13
Okular::Page::boundingBox
NormalizedRect boundingBox() const
Returns the bounding box of the page content in normalized [0,1] coordinates, in terms of the upright...
Definition: page.cpp:195
Okular::TextSelection::end
void end(const NormalizedPoint &point)
Changes the end point of the selection to the given point.
Definition: misc.cpp:45
Okular::TextList
QList< TinyTextEntity * > TextList
Definition: textpage_p.h:26
Okular::TextPagePrivate::m_searchPoints
QMap< int, SearchPoint * > m_searchPoints
Definition: textpage_p.h:70
Okular::Page::width
double width() const
Returns the width of the page.
Definition: page.cpp:180
Okular::PagePrivate::rotationMatrix
QTransform rotationMatrix() const
Definition: page.cpp:119
Okular::TextEntity::~TextEntity
~TextEntity()
Destroys the text entity.
Definition: textpage.cpp:181
textpage.h
Okular::NormalizedRect::isBottom
bool isBottom(const NormalizedPoint &pt) const
Returns true if the point pt is located to the bottom of the rectangle.
Definition: area.h:231
Okular::NormalizedRect::geometry
QRect geometry(int xScale, int yScale) const
Returns the rectangle that accrues when the normalized rectangle is multiplyed with the scaling xScal...
Definition: area.cpp:239
Okular::TextSelection::itB
void itB(int pos)
Definition: misc.cpp:61
addNecessarySpace
WordsWithCharacters addNecessarySpace(RegionTextList tree, int pageWidth, int pageHeight)
Add spaces in between words in a line.
Definition: textpage.cpp:1796
Okular::NormalizedRect::isBottomOrLevel
bool isBottomOrLevel(const NormalizedPoint &pt) const
Returns true if the point pt is located under the top of the rectangle.
Definition: area.h:249
compareTinyTextEntityY
static bool compareTinyTextEntityY(const WordWithCharacters &first, const WordWithCharacters &second)
Definition: textpage.cpp:1108
Okular::NormalizedRect::top
double top
The normalized top coordinate.
Definition: area.h:310
stringLengthAdaptedWithHyphen
static int stringLengthAdaptedWithHyphen(const QString &str, const TextList::ConstIterator &it, const TextList::ConstIterator &textListEnd, PagePrivate *page)
Definition: textpage.cpp:767
Okular::TextPage::text
QString text(const RegularAreaRect *rect=0) const
Text extraction function.
Definition: textpage.cpp:1059
compareTinyTextEntityX
static bool compareTinyTextEntityX(const WordWithCharacters &first, const WordWithCharacters &second)
Definition: textpage.cpp:1100
Okular::TextPage::textArea
RegularAreaRect * textArea(TextSelection *selection) const
Returns the rectangular area of the given selection.
Definition: textpage.cpp:319
Okular::TextPage::wordAt
RegularAreaRect * wordAt(const NormalizedPoint &p, QString *word=0) const
Returns the area and text of the word at the given point Note that ownership of the returned area bel...
Definition: textpage.cpp:1943
Okular::NormalizedPoint::x
double x
The normalized x coordinate.
Definition: area.h:92
textpage_p.h
Okular::NormalizedRect::isTop
bool isTop(const NormalizedPoint &pt) const
Returns true if the point pt is located on the top of the rectangle.
Definition: area.h:240
Okular::TextPage::AnyPixelTextAreaInclusionBehaviour
A character is included into text() result if any pixel of his bounding box is in the given area...
Definition: textpage.h:104
Okular::TextPage::TextAreaInclusionBehaviour
TextAreaInclusionBehaviour
Defines the behaviour of adding characters to text() result.
Definition: textpage.h:102
Okular::RegularArea::simplify
void simplify()
Simplifies the regular area by merging its intersecting subareas.
Definition: area.h:628
Okular::TextPagePrivate
Definition: textpage_p.h:40
Okular::TextPage::findText
RegularAreaRect * findText(int id, const QString &text, SearchDirection direction, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *lastRect)
Returns the bounding rect of the text which matches the following criteria or 0 if the search is not ...
Definition: textpage.cpp:703
XYCutForBoundingBoxes
static RegionTextList XYCutForBoundingBoxes(const QList< WordWithCharacters > &wordsWithCharacters, const NormalizedRect &boundingBox, int pageWidth, int pageHeight)
Implements the XY Cut algorithm for textpage segmentation The resulting RegionTextList will contain R...
Definition: textpage.cpp:1533
Okular::NormalizedRect::bottom
double bottom
The normalized bottom coordinate.
Definition: area.h:320
Okular::NormalizedRect::isLeft
bool isLeft(const NormalizedPoint &pt) const
Returns true if the point pt is located to the right of the left arm of rectangle.
Definition: area.h:267
Okular::TextEntity::TextEntity
TextEntity(const QString &text, NormalizedRect *area)
Creates a new text entity with the given text and the given area.
Definition: textpage.cpp:176
Okular::TextPage::~TextPage
~TextPage()
Destroys the text page.
Definition: textpage.cpp:234
Okular::TextEntity::List
QList< TextEntity * > List
Definition: textpage.h:47
Okular::TextSelection
Wrapper around the information needed to generate the selection area There are two assumptions inside...
Definition: misc.h:36
Okular::TextPage::append
void append(const QString &text, NormalizedRect *area)
Appends the given text with the given area as new TextEntity to the page.
Definition: textpage.cpp:239
WordsWithCharacters
QList< WordWithCharacters > WordsWithCharacters
Definition: textpage.cpp:266
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:45:03 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

okular

Skip menu "okular"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdegraphics API Reference

Skip menu "kdegraphics API Reference"
  •     libkdcraw
  •     libkexiv2
  •     libkipi
  •     libksane
  • okular

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal