~ubuntu-branches/ubuntu/utopic/texworks/utopic-proposed

« back to all changes in this revision

Viewing changes to modules/QtPDF/src/PDFBackend.cpp

  • Committer: Package Import Robot
  • Author(s): Atsuhito KOHDA
  • Date: 2013-06-04 14:11:27 UTC
  • mfrom: (1.1.17)
  • Revision ID: package-import@ubuntu.com-20130604141127-mjaxh41lyfkv3a29
Tags: 0.5~svn1288-1
* New Upstream Release (rev 1288).
* Fixed rules clean target.  obj -> objs
* Fixed control file.  Added zlib1g-dev to Build-Depends field.
  (Closes: #710102)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * Copyright (C) 2011-2012  Charlie Sharpsteen, Stefan Löffler
 
3
 *
 
4
 * This program is free software; you can redistribute it and/or modify it
 
5
 * under the terms of the GNU General Public License as published by the Free
 
6
 * Software Foundation; either version 2, or (at your option) any later
 
7
 * version.
 
8
 *
 
9
 * This program is distributed in the hope that it will be useful, but WITHOUT
 
10
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
11
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 
12
 * more details.
 
13
 */
 
14
 
 
15
#include <PDFBackend.h>
 
16
#include <QPainter>
 
17
#include <QApplication>
 
18
 
 
19
namespace QtPDF {
 
20
 
 
21
namespace Backend {
 
22
 
 
23
// TODO: Find a better place to put this
 
24
QBrush * pageDummyBrush = NULL;
 
25
 
 
26
QDateTime fromPDFDate(QString pdfDate)
 
27
{
 
28
  QDate date;
 
29
  QTime time;
 
30
  QString format;
 
31
  QDateTime retVal;
 
32
  int sign = 0;
 
33
  int hourOffset, minuteOffset;
 
34
  bool ok;
 
35
 
 
36
  // "D:" prefix is strongly recommended, but optional; we don't need it here
 
37
  if (pdfDate.startsWith(QString::fromUtf8("D:")))
 
38
    pdfDate.remove(0, 2);
 
39
 
 
40
  // Parse the date
 
41
  if (pdfDate.length() < 4)
 
42
    return QDateTime();
 
43
  format = QString::fromUtf8("yyyy");
 
44
  if (pdfDate.length() >= 6)
 
45
    format += QString::fromUtf8("MM");
 
46
  if (pdfDate.length() >= 8)
 
47
    format += QString::fromUtf8("dd");
 
48
  date = QDate::fromString(pdfDate.left(format.length()), format);
 
49
  pdfDate.remove(0, format.length());
 
50
 
 
51
  // Parse the time
 
52
  if (pdfDate.length() < 2)
 
53
    return QDateTime(date, time);
 
54
  format = QString::fromUtf8("hh");
 
55
  if (pdfDate.length() >= 4)
 
56
    format += QString::fromUtf8("mm");
 
57
  if (pdfDate.length() >= 6)
 
58
    format += QString::fromUtf8("ss");
 
59
  time = QTime::fromString(pdfDate.left(format.length()), format);
 
60
  pdfDate.remove(0, format.length());
 
61
 
 
62
  // Parse time zone data
 
63
  if (pdfDate.length() == 0)
 
64
    return QDateTime(date, time);
 
65
  switch (pdfDate[0].toAscii()) {
 
66
    case 'Z':
 
67
      return QDateTime(date, time, Qt::UTC).toLocalTime();
 
68
    case '+':
 
69
      // Note: A `+` signifies that pdfDate is later than UTC. Since we will
 
70
      // specify the QDateTime in UTC below, we have to _subtract_ the offset
 
71
      sign = -1;
 
72
      break;
 
73
    case '-':
 
74
      sign = +1;
 
75
      break;
 
76
    default:
 
77
      return QDateTime(date, time);
 
78
  }
 
79
  pdfDate.remove(0, 1);
 
80
  if (pdfDate.length() < 3 || pdfDate[2] != QChar::fromAscii('\''))
 
81
    return QDateTime(date, time);
 
82
  hourOffset = pdfDate.left(2).toInt(&ok);
 
83
  if (!ok)
 
84
    return QDateTime(date, time);
 
85
  pdfDate.remove(0, 3);
 
86
  if (pdfDate.length() >= 3 && pdfDate[2] ==  QChar::fromAscii('\''))
 
87
    minuteOffset = pdfDate.left(2).toInt();
 
88
  return QDateTime(date, time, Qt::UTC).addSecs(sign * (hourOffset * 3600 + minuteOffset * 60)).toLocalTime();
 
89
}
 
90
 
 
91
#ifdef DEBUG
 
92
void PDFPageProcessingThread::dumpWorkStack(const QStack<PageProcessingRequest*> & ws)
 
93
{
 
94
  int i;
 
95
  QStringList strList;
 
96
  for (i = 0; i < ws.size(); ++i) {
 
97
    PageProcessingRequest * request = ws[i];
 
98
    if (!request)
 
99
      strList << QString::fromUtf8("NULL");
 
100
    else {
 
101
      strList << *request;
 
102
    }
 
103
  }
 
104
  qDebug() << strList;
 
105
}
 
106
#endif
 
107
 
 
108
 
 
109
 
 
110
 
 
111
// Fonts
 
112
// =================
 
113
 
 
114
PDFFontDescriptor::PDFFontDescriptor(const QString fontName /* = QString() */) :
 
115
  _name(fontName),
 
116
  _stretch(FontStretch_Normal),
 
117
  _weight(400),
 
118
  _italicAngle(0),
 
119
  _ascent(0),
 
120
  _descent(0),
 
121
  _leading(0),
 
122
  _capHeight(0),
 
123
  _xHeight(0),
 
124
  _stemV(0),
 
125
  _stemH(0),
 
126
  _avgWidth(0),
 
127
  _maxWidth(0),
 
128
  _missingWidth(0)
 
129
{
 
130
}
 
131
 
 
132
bool PDFFontDescriptor::isSubset() const
 
133
{
 
134
  // Subset fonts have a tag of 6 upper-case letters, followed by a '+',
 
135
  // prefixed to the font name
 
136
  if (_name.length() < 7 || _name[6] != QChar::fromAscii('+'))
 
137
    return false;
 
138
  for (int i = 0; i < 6; ++i) {
 
139
    if (!_name[i].isUpper())
 
140
      return false;
 
141
  }
 
142
  return true;
 
143
}
 
144
 
 
145
QString PDFFontDescriptor::pureName() const
 
146
{
 
147
  if (!isSubset())
 
148
    return _name;
 
149
  else
 
150
    return _name.mid(7);
 
151
}
 
152
 
 
153
 
 
154
// Backend Rendering
 
155
// =================
 
156
// The `PDFPageProcessingThread` is a thread that processes background jobs.
 
157
// Each job is represented by a subclass of `PageProcessingRequest` and
 
158
// contains an `execute` method that performs the actual work.
 
159
PDFPageProcessingThread::PDFPageProcessingThread() :
 
160
  _idle(true),
 
161
  _quit(false)
 
162
{
 
163
}
 
164
 
 
165
PDFPageProcessingThread::~PDFPageProcessingThread()
 
166
{
 
167
  _mutex.lock();
 
168
  _quit = true;
 
169
  _waitCondition.wakeAll();
 
170
  _mutex.unlock();
 
171
  wait();
 
172
}
 
173
 
 
174
void PDFPageProcessingThread::addPageProcessingRequest(PageProcessingRequest * request)
 
175
{
 
176
 
 
177
  if (!request)
 
178
    return;
 
179
 
 
180
  // `request` must live in the main (GUI) thread, or else destroying it later
 
181
  // on will fail
 
182
  Q_ASSERT(request->thread() == QApplication::instance()->thread());
 
183
 
 
184
  QMutexLocker locker(&(this->_mutex));
 
185
  // Note: Commenting the "remove identical requests in the stack" code for now.
 
186
  // This should be handled by the caching routine elsewhere automatically. If
 
187
  // in doubt, it's better to render a tile twice than to not render it at all
 
188
  // (thereby leaving the dummy image in the cache indefinitely)
 
189
/*
 
190
  // remove any instances of the given request type before adding the new one to
 
191
  // avoid processing it several times
 
192
  // **TODO:** Could it be that we require several concurrent versions of the
 
193
  //           same page?
 
194
  int i;
 
195
  for (i = _workStack.size() - 1; i >= 0; --i) {
 
196
    if (*(_workStack[i]) == *request) {
 
197
      // Using deleteLater() doesn't work because we have no event queue in this
 
198
      // thread. However, since the object is still on the stack, it is still
 
199
      // sleeping and directly deleting it should therefore be safe.
 
200
      delete _workStack[i];
 
201
      _workStack.remove(i);
 
202
    }
 
203
  }
 
204
*/
 
205
 
 
206
  _workStack.push(request);
 
207
#ifdef DEBUG
 
208
  qDebug() << "new request:" << *request;
 
209
#endif
 
210
 
 
211
  locker.unlock();
 
212
  if (!isRunning())
 
213
    start();
 
214
  else
 
215
    _waitCondition.wakeOne();
 
216
}
 
217
 
 
218
void PDFPageProcessingThread::run()
 
219
{
 
220
  PageProcessingRequest * workItem;
 
221
 
 
222
  _mutex.lock();
 
223
  _idle = false;
 
224
  while (!_quit) {
 
225
    // mutex must be locked at start of loop
 
226
    if (_workStack.size() > 0) {
 
227
      workItem = _workStack.pop();
 
228
      _mutex.unlock();
 
229
 
 
230
#ifdef DEBUG
 
231
      qDebug() << "processing work item" << *workItem << "; remaining items:" << _workStack.size();
 
232
      _renderTimer.start();
 
233
#endif
 
234
      workItem->execute();
 
235
#ifdef DEBUG
 
236
      QString jobDesc;
 
237
      switch (workItem->type()) {
 
238
        case PageProcessingRequest::LoadLinks:
 
239
          jobDesc = QString::fromUtf8("loading links");
 
240
          break;
 
241
        case PageProcessingRequest::PageRendering:
 
242
          jobDesc = QString::fromUtf8("rendering page");
 
243
          break;
 
244
      }
 
245
      qDebug() << "finished " << jobDesc << "for page" << workItem->page->pageNum() << ". Time elapsed: " << _renderTimer.elapsed() << " ms.";
 
246
#endif
 
247
 
 
248
      // Delete the work item as it has fulfilled its purpose
 
249
      // Note that we can't delete it here or we might risk that some emitted
 
250
      // signals are invalidated; to ensure they reach their destination, we
 
251
      // need to call deleteLater().
 
252
      // Note: workItem *must* live in the main (GUI) thread for this!
 
253
      Q_ASSERT(workItem->thread() == QApplication::instance()->thread());
 
254
      workItem->deleteLater();
 
255
 
 
256
      _mutex.lock();
 
257
    }
 
258
    else {
 
259
#ifdef DEBUG
 
260
      qDebug() << "going to sleep";
 
261
      _idle = true;
 
262
      _idleCondition.wakeAll();
 
263
#endif
 
264
      _waitCondition.wait(&_mutex);
 
265
      _idle = false;
 
266
#ifdef DEBUG
 
267
      qDebug() << "waking up";
 
268
#endif
 
269
    }
 
270
  }
 
271
}
 
272
 
 
273
void PDFPageProcessingThread::clearWorkStack()
 
274
{
 
275
  _mutex.lock();
 
276
 
 
277
  foreach(PageProcessingRequest * workItem, _workStack) {
 
278
    if (!workItem)
 
279
      continue;
 
280
    Q_ASSERT(workItem->thread() == QApplication::instance()->thread());
 
281
    workItem->deleteLater();
 
282
  }
 
283
  _workStack.clear();
 
284
 
 
285
  if (!_idle) {
 
286
    // Wait until the current operation finishes
 
287
    _idleCondition.wait(&_mutex);
 
288
  }
 
289
  _mutex.unlock();
 
290
}
 
291
 
 
292
 
 
293
// Asynchronous Page Operations
 
294
// ----------------------------
 
295
//
 
296
// The `execute` functions here are called by the processing theread to perform
 
297
// background jobs such as page rendering or link loading. This alows the GUI
 
298
// thread to stay unblocked and responsive. The results of background jobs are
 
299
// posted as events to a `listener` which can be any subclass of `QObject`. The
 
300
// `listener` will need a custom `event` function that is capable of picking up
 
301
// on these events.
 
302
 
 
303
bool PageProcessingRequest::operator==(const PageProcessingRequest & r) const
 
304
{
 
305
  // TODO: Should we care about the listener here as well?
 
306
  return (type() == r.type() && page == r.page);
 
307
}
 
308
 
 
309
bool PageProcessingRenderPageRequest::operator==(const PageProcessingRequest & r) const
 
310
{
 
311
  if (!PageProcessingRequest::operator==(r))
 
312
    return false;
 
313
  const PageProcessingRenderPageRequest * rr = static_cast<const PageProcessingRenderPageRequest*>(&r);
 
314
  // TODO: Should we care about the listener here as well?
 
315
  return (xres == rr->xres && yres == rr->yres && render_box == rr->render_box && cache == rr->cache);
 
316
}
 
317
 
 
318
#ifdef DEBUG
 
319
PageProcessingRenderPageRequest::operator QString() const
 
320
{
 
321
  return QString::fromUtf8("RP:%1.%2_%3").arg(page->pageNum()).arg(render_box.topLeft().x()).arg(render_box.topLeft().y());
 
322
}
 
323
#endif
 
324
 
 
325
// ### Custom Event Types
 
326
// These are the events posted by `execute` functions.
 
327
const QEvent::Type PDFPageRenderedEvent::PageRenderedEvent = static_cast<QEvent::Type>( QEvent::registerEventType() );
 
328
const QEvent::Type PDFLinksLoadedEvent::LinksLoadedEvent = static_cast<QEvent::Type>( QEvent::registerEventType() );
 
329
 
 
330
bool PageProcessingRenderPageRequest::execute()
 
331
{
 
332
  // TODO: Aborting renders doesn't really work right now---the backend knows
 
333
  // nothing about the PDF scenes.
 
334
  //
 
335
  // Idea: Perhaps allow page render requests to provide a pointer to a function
 
336
  // that returns a `bool` value indicating if the request is still valid? Then
 
337
  // the `PDFPageGraphicsItem` could have a function that indicates if the item
 
338
  // is anywhere near a viewport.
 
339
  QImage rendered_page = page->renderToImage(xres, yres, render_box, cache);
 
340
  QCoreApplication::postEvent(listener, new PDFPageRenderedEvent(xres, yres, render_box, rendered_page));
 
341
 
 
342
  return true;
 
343
}
 
344
 
 
345
bool PageProcessingLoadLinksRequest::execute()
 
346
{
 
347
  QCoreApplication::postEvent(listener, new PDFLinksLoadedEvent(page->loadLinks()));
 
348
  return true;
 
349
}
 
350
 
 
351
#ifdef DEBUG
 
352
PageProcessingLoadLinksRequest::operator QString() const
 
353
{
 
354
  return QString::fromUtf8("LL:%1").arg(page->pageNum());
 
355
}
 
356
#endif
 
357
 
 
358
#ifdef DEBUG
 
359
PDFPageTile::operator QString() const
 
360
{
 
361
  return QString::fromUtf8("p%1,%2x%3,r%4|%5x%6|%7").arg(page_num).arg(xres).arg(yres).arg(render_box.x()).arg(render_box.y()).arg(render_box.width()).arg(render_box.height());
 
362
}
 
363
#endif
 
364
 
 
365
// Taken from Qt 4.7.2 sources (<Qt>/src/corelib/tools/qhash.cpp)
 
366
static uint hash(const uchar *p, int n)
 
367
{
 
368
  uint h = 0;
 
369
 
 
370
  while (n--) {
 
371
    h = (h << 4) + *p++;
 
372
    h ^= (h & 0xf0000000) >> 23;
 
373
    h &= 0x0fffffff;
 
374
  }
 
375
  return h;
 
376
}
 
377
 
 
378
inline uint qHash(const QRect &key) {
 
379
  return qHash(
 
380
        QPair< QPair< int, int >, QPair< int, int > >(
 
381
          QPair< int, int >(key.x(), key.y()),
 
382
          QPair< int, int >(key.width(), key.height())
 
383
        )
 
384
        );
 
385
}
 
386
 
 
387
inline uint qHash(const double &d)
 
388
{
 
389
  // We interpret the double as an array of bytes and use the hash() function on
 
390
  // it.
 
391
  // NOTE: Due to rounding errors, this is not 100% reliable - two doubles that
 
392
  // _look_ the same may actually differ in their bit representations (e.g., if
 
393
  // the same value was calculated in two different ways). So this function may
 
394
  // report different hashes for doubles that look the same (which should not be
 
395
  // a problem in our case, however).
 
396
  // Note also that the QDataStream approach used previously also works on the
 
397
  // binary representation of doubles internally and so the same problem would
 
398
  // occur there as well.
 
399
  return hash((const uchar*)&d, sizeof(d));
 
400
}
 
401
 
 
402
// ### Cache for Rendered Images
 
403
inline uint qHash(const PDFPageTile &tile)
 
404
{
 
405
  uint h1 = qHash(QPair<uint, uint>(qHash(tile.xres), qHash(tile.yres)));
 
406
  uint h2 = qHash(QPair<uint,int>(qHash(tile.render_box), tile.page_num));
 
407
  return qHash(QPair<uint, uint>(h1, h2));
 
408
}
 
409
 
 
410
QSharedPointer<QImage> PDFPageCache::getImage(const PDFPageTile & tile) const
 
411
{
 
412
  _lock.lockForRead();
 
413
  QSharedPointer<QImage> * retVal = object(tile);
 
414
  _lock.unlock();
 
415
  if (retVal)
 
416
    return *retVal;
 
417
  return QSharedPointer<QImage>();
 
418
}
 
419
 
 
420
QSharedPointer<QImage> PDFPageCache::setImage(const PDFPageTile & tile, QImage * image, const bool overwrite /* = true */)
 
421
{
 
422
  _lock.lockForWrite();
 
423
  QSharedPointer<QImage> retVal;
 
424
  if (contains(tile))
 
425
    retVal = *object(tile);
 
426
  // If the key is not in the cache yet add it. Otherwise overwrite the cached
 
427
  // image but leave the pointer intact as that can be held/used elsewhere
 
428
  if (!retVal) {
 
429
    QSharedPointer<QImage> * toInsert = new QSharedPointer<QImage>(image);
 
430
    insert(tile, toInsert, (image ? image->byteCount() : 0));
 
431
    retVal = *toInsert;
 
432
  }
 
433
  else if(overwrite) {
 
434
    // TODO: overwriting an image with a different one can change its size (and
 
435
    // therefore its cost in the cache). There doesn't seem to be a method to
 
436
    // hande that in QCache, though, and since we only use one tile size this
 
437
    // shouldn't pose a problem.
 
438
    if (image)
 
439
      *retVal = *image;
 
440
    else {
 
441
      QSharedPointer<QImage> * toInsert = new QSharedPointer<QImage>;
 
442
      insert(tile, toInsert, 0);
 
443
      retVal = *toInsert;
 
444
    }
 
445
  }
 
446
  _lock.unlock();
 
447
  return retVal;
 
448
}
 
449
 
 
450
 
 
451
// PDF ABCs
 
452
// ========
 
453
 
 
454
// Document Class
 
455
// --------------
 
456
//
 
457
// This class is thread-safe. Data access is governed by the QReadWriteLock
 
458
// _docLock.
 
459
Document::Document(QString fileName):
 
460
  _numPages(-1),
 
461
  _fileName(fileName),
 
462
  _meta_trapped(Trapped_Unknown),
 
463
  _docLock(new QReadWriteLock(QReadWriteLock::Recursive))
 
464
{
 
465
  Q_ASSERT(_docLock != NULL);
 
466
 
 
467
#ifdef DEBUG
 
468
//  qDebug() << "Document::Document(" << fileName << ")";
 
469
#endif
 
470
 
 
471
  // Set cache for rendered pages to be 1GB. This is enough for 256 RGBA tiles
 
472
  // (1024 x 1024 pixels x 4 bytes per pixel).
 
473
  //
 
474
  // NOTE: The application seems to exceed 1 GB---usage plateaus at around 2GB. No idea why. Perhaps freed
 
475
  // blocks are not garbage collected?? Perhaps my math is off??
 
476
  _pageCache.setMaxSize(1024 * 1024 * 1024);
 
477
}
 
478
 
 
479
Document::~Document()
 
480
{
 
481
#ifdef DEBUG
 
482
//  qDebug() << "Document::~Document()";
 
483
#endif
 
484
  QWriteLocker docLocker(_docLock.data());
 
485
  clearPages();
 
486
}
 
487
 
 
488
int Document::numPages() { QReadLocker docLocker(_docLock.data()); return _numPages; }
 
489
PDFPageProcessingThread &Document::processingThread() { QReadLocker docLocker(_docLock.data()); return _processingThread; }
 
490
PDFPageCache &Document::pageCache() { QReadLocker docLocker(_docLock.data()); return _pageCache; }
 
491
 
 
492
QList<SearchResult> Document::search(QString searchText, int startPage)
 
493
{
 
494
  QReadLocker docLocker(_docLock.data());
 
495
  QList<SearchResult> results;
 
496
  int i;
 
497
 
 
498
  for (i = startPage; i < _numPages; ++i) {
 
499
    QSharedPointer<Page> page(_pages[i]);
 
500
    if (!page)
 
501
      continue;
 
502
    results << page->search(searchText);
 
503
  }
 
504
  for (i = 0; i < startPage; ++i) {
 
505
    QSharedPointer<Page> page(_pages[i]);
 
506
    if (!page)
 
507
      continue;
 
508
    results << page->search(searchText);
 
509
  }
 
510
 
 
511
  return results;
 
512
}
 
513
 
 
514
void Document::clearPages()
 
515
{
 
516
  QWriteLocker docLocker(_docLock.data());
 
517
  foreach(QSharedPointer<Page> page, _pages) {
 
518
    if (page.isNull())
 
519
      continue;
 
520
    page->detachFromParent();
 
521
  }
 
522
  // Note: clear() releases all QSharedPointer to pages, thereby destroying them
 
523
  // (if they are not used elsewhere)
 
524
  _pages.clear();
 
525
}
 
526
 
 
527
void Document::clearMetaData()
 
528
{
 
529
  QWriteLocker docLocker(_docLock.data());
 
530
  _meta_title = QString();
 
531
  _meta_author = QString();
 
532
  _meta_subject = QString();
 
533
  _meta_keywords = QString();
 
534
  _meta_creator = QString();
 
535
  _meta_producer = QString();
 
536
 
 
537
  _meta_creationDate = QDateTime();
 
538
  _meta_modDate = QDateTime();
 
539
  _meta_trapped = Trapped_Unknown;
 
540
  _meta_other.clear();
 
541
}
 
542
 
 
543
// Page Class
 
544
// ----------
 
545
//
 
546
// This class is thread-safe. Data access is governed by the QReadWriteLock
 
547
// _pageLock. When accessing the parent document directly (i.e., not via public
 
548
// member functions), the QSharedPointer<QReadWriteLock> _docLock must also be
 
549
// acquired. Note that if _docLock and _pageLock are to be acquired, _docLock
 
550
// must be acquired first.
 
551
// Note that the Page may exist in a detached state, i.e., _parent == NULL. This
 
552
// is typically the case when the document discarded the page object but some
 
553
// other object (typically in another thread) still holds a QSharedPointer to it.
 
554
Page::Page(Document *parent, int at, QSharedPointer<QReadWriteLock> docLock):
 
555
  _parent(parent),
 
556
  _n(at),
 
557
  _transition(NULL),
 
558
  _pageLock(new QReadWriteLock(QReadWriteLock::Recursive)),
 
559
  _docLock(docLock)
 
560
{
 
561
  Q_ASSERT(_pageLock);
 
562
 
 
563
#ifdef DEBUG
 
564
//  qDebug() << "Page::Page(" << parent << ", " << at << ")";
 
565
#endif
 
566
 
 
567
  if (!pageDummyBrush) {
 
568
    pageDummyBrush = new QBrush();
 
569
 
 
570
    // Make a texture brush which can be used to print "rendering page" all over
 
571
    // the dummy tiles that are shown while the rendering thread is doing its
 
572
    // work
 
573
    QImage brushTex(1024, 1024, QImage::Format_ARGB32);
 
574
    QRectF textRect;
 
575
    QPainter p;
 
576
    p.begin(&brushTex);
 
577
    p.fillRect(brushTex.rect(), Qt::white);
 
578
    p.setPen(Qt::lightGray);
 
579
    p.drawText(brushTex.rect(), Qt::AlignCenter | Qt::AlignVCenter | Qt::TextSingleLine, QCoreApplication::translate("QtPDF::PDFDocumentScene", "rendering page"), &textRect);
 
580
    p.end();
 
581
    textRect.adjust(-textRect.width() * .05, -textRect.height() * .1, textRect.width() * .05, textRect.height() * .1);
 
582
    brushTex = brushTex.copy(textRect.toAlignedRect());
 
583
 
 
584
    pageDummyBrush->setTextureImage(brushTex);
 
585
    pageDummyBrush->setTransform(QTransform().rotate(-45));
 
586
  }
 
587
}
 
588
 
 
589
Page::~Page()
 
590
{
 
591
#ifdef DEBUG
 
592
//  qDebug() << "Page::~Page(" << _n << ")";
 
593
#endif
 
594
}
 
595
 
 
596
int Page::pageNum() { QReadLocker pageLocker(_pageLock); return _n; }
 
597
 
 
598
void Page::detachFromParent()
 
599
{
 
600
  QWriteLocker pageLocker(_pageLock);
 
601
  _parent = NULL;
 
602
}
 
603
 
 
604
QSharedPointer<QImage> Page::getCachedImage(double xres, double yres, QRect render_box)
 
605
{
 
606
  QReadLocker docLocker(_docLock.data());
 
607
  QReadLocker pageLocker(_pageLock);
 
608
  if (!_parent)
 
609
    return QSharedPointer<QImage>();
 
610
  return _parent->pageCache().getImage(PDFPageTile(xres, yres, render_box, _n));
 
611
}
 
612
 
 
613
void Page::asyncRenderToImage(QObject *listener, double xres, double yres, QRect render_box, bool cache)
 
614
{
 
615
  QReadLocker docLocker(_docLock.data());
 
616
  QReadLocker pageLocker(_pageLock);
 
617
  if (!_parent)
 
618
    return;
 
619
  _parent->processingThread().addPageProcessingRequest(new PageProcessingRenderPageRequest(this, listener, xres, yres, render_box, cache));
 
620
}
 
621
 
 
622
bool higherResolutionThan(const PDFPageTile & t1, const PDFPageTile & t2)
 
623
{
 
624
  // Note: We silently assume that xres and yres behave the same way
 
625
  return t1.xres > t2.xres;
 
626
}
 
627
 
 
628
QSharedPointer<QImage> Page::getTileImage(QObject * listener, const double xres, const double yres, QRect render_box /* = QRect() */)
 
629
{
 
630
  QReadLocker docLocker(_docLock.data());
 
631
  QReadLocker pageLocker(_pageLock);
 
632
 
 
633
  // If the render_box is empty, use the whole page
 
634
  if (render_box.isNull())
 
635
    render_box = QRectF(0, 0, pageSizeF().width() * xres / 72., pageSizeF().height() * yres / 72.).toAlignedRect();
 
636
 
 
637
  // If the tile is cached, return it
 
638
  QSharedPointer<QImage> retVal = getCachedImage(xres, yres, render_box);
 
639
  if (retVal)
 
640
    return retVal;
 
641
 
 
642
  if (listener) {
 
643
    // Render asyncronously, but add a dummy image to the cache first and return
 
644
    // that in the end
 
645
    // Note: Start the rendering in the background before constructing the image
 
646
    // to take advantage of multi-core CPUs. Since we hold the write lock here
 
647
    // there's nothing to worry about
 
648
    asyncRenderToImage(listener, xres, yres, render_box, true);
 
649
 
 
650
    QImage * tmpImg = new QImage(render_box.width(), render_box.height(), QImage::Format_ARGB32);
 
651
    QPainter p(tmpImg);
 
652
    p.fillRect(tmpImg->rect(), *pageDummyBrush);
 
653
 
 
654
    // Look through the cache to find tiles we can reuse (by scaling) for our
 
655
    // dummy tile
 
656
    // TODO: Benchmark this. If it is actualy too slow (i.e., just keeping the
 
657
    // rendered image from popping up due to the write lock we hold) disable it
 
658
    if (_parent) {
 
659
      QList<PDFPageTile> tiles = _parent->pageCache().tiles();
 
660
      for (QList<PDFPageTile>::iterator it = tiles.begin(); it != tiles.end(); ) {
 
661
        if (it->page_num != pageNum()) {
 
662
          it = tiles.erase(it);
 
663
          continue;
 
664
        }
 
665
        // See if it->render_box intersects with render_box (after proper scaling)
 
666
        QRect scaledRect = QTransform::fromScale(xres / it->xres, yres / it->yres).mapRect(it->render_box);
 
667
        if (!scaledRect.intersects(render_box)) {
 
668
          it = tiles.erase(it);
 
669
          continue;
 
670
        }
 
671
        ++it;
 
672
      }
 
673
      // Sort the remaining tiles by size, high-res first
 
674
      qSort(tiles.begin(), tiles.end(), higherResolutionThan);
 
675
      // Finally, crop, scale and paint each image until the whole area is
 
676
      // filled or no images are left in the list
 
677
      QPainterPath clipPath;
 
678
      clipPath.addRect(0, 0, render_box.width(), render_box.height());
 
679
      foreach (PDFPageTile tile, tiles) {
 
680
        QSharedPointer<QImage> tileImg = _parent->pageCache().getImage(tile);
 
681
        if (!tileImg)
 
682
          continue;
 
683
 
 
684
        // cropRect is the part of `tile` that overlaps the tile-to-paint (after
 
685
        // proper scaling).
 
686
        // paintRect is the part `tile` fills of the area we paint to (after
 
687
        // proper scaling).
 
688
        QRect cropRect = QTransform::fromScale(tile.xres / xres, tile.yres / yres).mapRect(render_box).intersected(tile.render_box).translated(-tile.render_box.left(), -tile.render_box.top());
 
689
        QRect paintRect = QTransform::fromScale(xres / tile.xres, yres / tile.yres).mapRect(tile.render_box).intersected(render_box).translated(-render_box.left(), -render_box.top());
 
690
 
 
691
        // Get the actual image and paint it onto the dummy tile
 
692
        QImage tmp(tileImg->copy(cropRect).scaled(paintRect.size()));
 
693
        p.setClipPath(clipPath);
 
694
        p.drawImage(paintRect.topLeft(), tmp);
 
695
 
 
696
        // Confine the clipping path to the part we have not painted to yet.
 
697
        QPainterPath pp;
 
698
        pp.addRect(paintRect);
 
699
        clipPath = clipPath.subtracted(pp);
 
700
        if (clipPath.isEmpty())
 
701
          break;
 
702
      }
 
703
    }
 
704
    // stop painting or else we couldn't (possibly) delete tmpImg below
 
705
    p.end();
 
706
 
 
707
    // Add the dummy tile to the cache
 
708
    // Note: In the meantime the asynchronous rendering could have finished and
 
709
    // insert the final image in the cache---we must handle that case and delete
 
710
    // our temporary image
 
711
    retVal = _parent->pageCache().setImage(PDFPageTile(xres, yres, render_box, _n), tmpImg, false);
 
712
    if (retVal != tmpImg)
 
713
      delete tmpImg;
 
714
    return retVal;
 
715
  }
 
716
  else {
 
717
    renderToImage(xres, yres, render_box, true);
 
718
    return getCachedImage(xres, yres, render_box);
 
719
  }
 
720
}
 
721
 
 
722
void Page::asyncLoadLinks(QObject *listener)
 
723
{
 
724
  QReadLocker docLocker(_docLock.data());
 
725
  QReadLocker pageLocker(_pageLock);
 
726
  if (!_parent)
 
727
    return;
 
728
  _parent->processingThread().addPageProcessingRequest(new PageProcessingLoadLinksRequest(this, listener));
 
729
}
 
730
 
 
731
//static
 
732
QList<SearchResult> Page::executeSearch(SearchRequest request)
 
733
{
 
734
  QSharedPointer<Document> doc(request.doc.toStrongRef());
 
735
  if (!doc)
 
736
    return QList<SearchResult>();
 
737
  QSharedPointer<Page> page = doc->page(request.pageNum).toStrongRef();
 
738
  if (!page)
 
739
    return QList<SearchResult>();
 
740
  return page->search(request.searchString);
 
741
}
 
742
 
 
743
} // namespace Backend
 
744
 
 
745
} // namespace QtPDF
 
746
 
 
747
// vim: set sw=2 ts=2 et
 
748