1 /* Copyright Krzysztof Kowalczyk 2006-2007
3 #include "DisplayModel.h"
7 // TODO: get rid of the need for GooString and UGooString in common code
9 #include "UGooString.h"
10 // TODO: get rid of the need for GooMutex.h ?
17 #define PREDICTIVE_RENDER 1
20 #define MAX_BITMAPS_CACHED 256
21 static GooMutex cacheMutex
;
22 static BitmapCacheEntry
* gBitmapCache
[MAX_BITMAPS_CACHED
] = {0};
23 static int gBitmapCacheCount
= 0;
25 static MutexAutoInitDestroy
gAutoCacheMutex(&cacheMutex
);
27 DisplaySettings gDisplaySettings
= {
28 PADDING_PAGE_BORDER_TOP_DEF
,
29 PADDING_PAGE_BORDER_BOTTOM_DEF
,
30 PADDING_PAGE_BORDER_LEFT_DEF
,
31 PADDING_PAGE_BORDER_RIGHT_DEF
,
32 PADDING_BETWEEN_PAGES_X_DEF
,
33 PADDING_BETWEEN_PAGES_Y_DEF
36 bool validZoomReal(double zoomReal
)
38 if ((zoomReal
< ZOOM_MIN
) || (zoomReal
> ZOOM_MAX
)) {
39 DBG_OUT("validZoomReal() invalid zoom: %.4f\n", zoomReal
);
45 bool displayModeFacing(DisplayMode displayMode
)
47 if ((DM_SINGLE_PAGE
== displayMode
) || (DM_CONTINUOUS
== displayMode
))
49 else if ((DM_FACING
== displayMode
) || (DM_CONTINUOUS_FACING
== displayMode
))
55 bool displayModeContinuous(DisplayMode displayMode
)
57 if ((DM_SINGLE_PAGE
== displayMode
) || (DM_FACING
== displayMode
))
59 else if ((DM_CONTINUOUS
== displayMode
) || (DM_CONTINUOUS_FACING
== displayMode
))
65 int columnsFromDisplayMode(DisplayMode displayMode
)
67 if (DM_SINGLE_PAGE
== displayMode
) {
69 } else if (DM_FACING
== displayMode
) {
71 } else if (DM_CONTINUOUS
== displayMode
) {
73 } else if (DM_CONTINUOUS_FACING
== displayMode
) {
80 DisplaySettings
*globalDisplaySettings(void)
82 return &gDisplaySettings
;
85 bool rotationFlipped(int rotation
)
87 assert(validRotation(rotation
));
88 normalizeRotation(&rotation
);
89 if ((90 == rotation
) || (270 == rotation
))
94 bool displayStateFromDisplayModel(DisplayState
*ds
, DisplayModel
*dm
)
96 ds
->filePath
= str_escape(dm
->fileName());
99 ds
->displayMode
= dm
->displayMode();
100 ds
->fullScreen
= dm
->fullScreen();
101 ds
->pageNo
= dm
->currentPageNo();
102 ds
->rotation
= dm
->rotation();
103 ds
->zoomVirtual
= dm
->zoomVirtual();
104 ds
->scrollX
= (int)dm
->areaOffset
.x
;
105 if (displayModeContinuous(dm
->displayMode())) {
106 /* TODO: should be offset of top page */
109 ds
->scrollY
= (int)dm
->areaOffset
.y
;
111 ds
->windowDx
= dm
->drawAreaSize
.dxI();
112 ds
->windowDy
= dm
->drawAreaSize
.dyI();
118 /* Given 'pageInfo', which should contain correct information about
119 pageDx, pageDy and rotation, return a page size after applying a global
121 void pageSizeAfterRotation(PdfPageInfo
*pageInfo
, int rotation
,
122 double *pageDxOut
, double *pageDyOut
)
124 assert(pageInfo
&& pageDxOut
&& pageDyOut
);
125 if (!pageInfo
|| !pageDxOut
|| !pageDyOut
)
128 *pageDxOut
= pageInfo
->pageDx
;
129 *pageDyOut
= pageInfo
->pageDy
;
131 rotation
= rotation
+ pageInfo
->rotation
;
132 normalizeRotation(&rotation
);
133 if (rotationFlipped(rotation
))
134 swap_double(pageDxOut
, pageDyOut
);
137 DisplayModel::DisplayModel(DisplayMode displayMode
)
139 _displayMode
= displayMode
;
140 _rotation
= INVALID_ROTATION
;
141 _zoomVirtual
= INVALID_ZOOM
;
143 _startPage
= INVALID_PAGE_NO
;
151 searchHitPageNo
= INVALID_PAGE_NO
;
152 searchState
.searchState
= eSsNone
;
153 searchState
.str
= new GooString();
154 searchState
.strU
= new UGooString();
157 DisplayModel::~DisplayModel()
162 PdfPageInfo
*DisplayModel::getPageInfo(int pageNo
) const
164 assert(validPageNo(pageNo
));
166 if (!pagesInfo
) return NULL
;
167 return &(pagesInfo
[pageNo
-1]);
170 bool DisplayModel::load(const char *fileName
, int startPage
, WindowInfo
*win
)
173 if (!_pdfEngine
->load(fileName
, win
))
176 if (validPageNo(startPage
))
177 _startPage
= startPage
;
181 if (!buildPagesInfo())
186 bool DisplayModel::buildPagesInfo(void)
189 int _pageCount
= pageCount();
191 pagesInfo
= (PdfPageInfo
*)calloc(1, _pageCount
* sizeof(PdfPageInfo
));
195 for (int pageNo
= 1; pageNo
<= _pageCount
; pageNo
++) {
196 PdfPageInfo
*pageInfo
= getPageInfo(pageNo
);
197 SizeD pageSize
= pdfEngine()->pageSize(pageNo
);
198 pageInfo
->pageDx
= pageSize
.dx();
199 pageInfo
->pageDy
= pageSize
.dy();
200 pageInfo
->rotation
= pdfEngine()->pageRotation(pageNo
);
202 pageInfo
->links
= NULL
;
203 pageInfo
->textPage
= NULL
;
205 pageInfo
->visible
= false;
206 pageInfo
->shown
= false;
207 if (displayModeContinuous(_displayMode
)) {
208 pageInfo
->shown
= true;
210 if ((pageNo
>= _startPage
) && (pageNo
< _startPage
+ columnsFromDisplayMode(_displayMode
))) {
211 DBG_OUT("DisplayModelSplash::CreateFromPdfDoc() set page %d as shown\n", pageNo
);
212 pageInfo
->shown
= true;
219 bool DisplayModel::pageShown(int pageNo
)
221 PdfPageInfo
*pageInfo
= getPageInfo(pageNo
);
224 return pageInfo
->shown
;
227 bool DisplayModel::pageVisible(int pageNo
)
229 PdfPageInfo
*pageInfo
= getPageInfo(pageNo
);
232 return pageInfo
->visible
;
235 /* Return true if a page is visible or a page below or above is visible */
236 bool DisplayModel::pageVisibleNearby(int pageNo
)
238 /* TODO: should it check 2 pages above and below in facing mode? */
239 if (pageVisible(pageNo
))
241 if (validPageNo(pageNo
-1) && pageVisible(pageNo
-1))
243 if (validPageNo(pageNo
+1) && pageVisible(pageNo
+1))
248 /* Given a zoom level that can include a "virtual" zoom levels like ZOOM_FIT_WIDTH
249 and ZOOM_FIT_PAGE, calculate an absolute zoom level */
250 double DisplayModel::zoomRealFromFirtualForPage(double zoomVirtual
, int pageNo
)
252 double _zoomReal
, zoomX
, zoomY
, pageDx
, pageDy
;
253 double areaForPageDx
, areaForPageDy
;
254 int areaForPageDxInt
;
257 assert(0 != drawAreaSize
.dxI());
258 assert(0 != drawAreaSize
.dy());
260 pageSizeAfterRotation(getPageInfo(pageNo
), rotation(), &pageDx
, &pageDy
);
262 assert(0 != (int)pageDx
);
263 assert(0 != (int)pageDy
);
265 columns
= columnsFromDisplayMode(displayMode());
266 areaForPageDx
= (drawAreaSize
.dx() - PADDING_PAGE_BORDER_LEFT
- PADDING_PAGE_BORDER_RIGHT
);
267 areaForPageDx
-= (PADDING_BETWEEN_PAGES_X
* (columns
- 1));
268 areaForPageDxInt
= (int)(areaForPageDx
/ columns
);
269 areaForPageDx
= (double)areaForPageDxInt
;
270 areaForPageDy
= drawAreaSize
.dy() - PADDING_PAGE_BORDER_TOP
- PADDING_PAGE_BORDER_BOTTOM
;
271 if (ZOOM_FIT_WIDTH
== zoomVirtual
) {
272 /* TODO: should use gWinDx if we don't show scrollbarY */
273 _zoomReal
= (areaForPageDx
* 100.0) / (double)pageDx
;
274 } else if (ZOOM_FIT_PAGE
== zoomVirtual
) {
275 zoomX
= (areaForPageDx
* 100.0) / (double)pageDx
;
276 zoomY
= (areaForPageDy
* 100.0) / (double)pageDy
;
282 _zoomReal
= zoomVirtual
;
287 int DisplayModel::firstVisiblePageNo(void) const
290 if (!pagesInfo
) return INVALID_PAGE_NO
;
292 for (int pageNo
= 1; pageNo
<= pageCount(); ++pageNo
) {
293 PdfPageInfo
*pageInfo
= getPageInfo(pageNo
);
294 if (pageInfo
->visible
)
298 return INVALID_PAGE_NO
;
301 int DisplayModel::currentPageNo(void) const
303 if (displayModeContinuous(displayMode()))
304 return firstVisiblePageNo();
309 void DisplayModel::setZoomVirtual(double zoomVirtual
)
312 double minZoom
= INVALID_BIG_ZOOM
;
315 assert(ValidZoomVirtual(zoomVirtual
));
316 _zoomVirtual
= zoomVirtual
;
318 if ((ZOOM_FIT_WIDTH
== zoomVirtual
) || (ZOOM_FIT_PAGE
== zoomVirtual
)) {
319 /* we want the same zoom for all pages, so use the smallest zoom
320 across the pages so that the largest page fits. In most PDFs all
321 pages are the same size anyway */
322 for (pageNo
= 1; pageNo
<= pageCount(); pageNo
++) {
323 if (pageShown(pageNo
)) {
324 thisPageZoom
= zoomRealFromFirtualForPage(this->zoomVirtual(), pageNo
);
325 assert(0 != thisPageZoom
);
326 if (minZoom
> thisPageZoom
)
327 minZoom
= thisPageZoom
;
330 assert(minZoom
!= INVALID_BIG_ZOOM
);
331 this->_zoomReal
= minZoom
;
333 this->_zoomReal
= zoomVirtual
;
336 /* Given pdf info and zoom/rotation, calculate the position of each page on a
337 large sheet that is continous view. Needs to be recalculated when:
340 * switching between display modes
341 * navigating to another page in non-continuous mode */
342 void DisplayModel::relayout(double zoomVirtual
, int rotation
)
345 PdfPageInfo
*pageInfo
= NULL
;
347 double pageDx
=0, pageDy
=0;
348 int currDxInt
, currDyInt
;
349 double totalAreaDx
, totalAreaDy
;
350 double areaPerPageDx
;
351 int areaPerPageDxInt
;
359 double newAreaOffsetX
;
365 normalizeRotation(&rotation
);
366 assert(validRotation(rotation
));
368 _rotation
= rotation
;
370 double currPosY
= PADDING_PAGE_BORDER_TOP
;
371 double currZoomReal
= _zoomReal
;
372 setZoomVirtual(zoomVirtual
);
374 // DBG_OUT("DisplayModel::relayout(), pageCount=%d, zoomReal=%.6f, zoomVirtual=%.2f\n", pageCount, dm->zoomReal, dm->zoomVirtual);
377 if (0 == currZoomReal
)
378 newAreaOffsetX
= 0.0;
380 newAreaOffsetX
= areaOffset
.x
* _zoomReal
/ currZoomReal
;
381 areaOffset
.x
= newAreaOffsetX
;
382 /* calculate the position of each page on the canvas, given current zoom,
383 rotation, columns parameters. You can think of it as a simple
384 table layout i.e. rows with a fixed number of columns. */
385 columns
= columnsFromDisplayMode(displayMode());
386 columnsLeft
= columns
;
387 currPosX
= PADDING_PAGE_BORDER_LEFT
;
389 for (pageNo
= 1; pageNo
<= pageCount(); ++pageNo
) {
390 pageInfo
= getPageInfo(pageNo
);
391 if (!pageInfo
->shown
) {
392 assert(!pageInfo
->visible
);
395 pageSizeAfterRotation(pageInfo
, rotation
, &pageDx
, &pageDy
);
396 currDxInt
= (int)(pageDx
* _zoomReal
* 0.01 + 0.5);
397 currDyInt
= (int)(pageDy
* _zoomReal
* 0.01 + 0.5);
398 pageInfo
->currDx
= (double)currDxInt
;
399 pageInfo
->currDy
= (double)currDyInt
;
401 if (rowMaxPageDy
< pageInfo
->currDy
)
402 rowMaxPageDy
= pageInfo
->currDy
;
404 pageInfo
->currPosX
= currPosX
;
405 pageInfo
->currPosY
= currPosY
;
406 /* set position of the next page to be after this page with padding.
407 Note: for the last page we don't want padding so we'll have to
408 substract it when we create new page */
409 currPosX
+= (pageInfo
->currDx
+ PADDING_BETWEEN_PAGES_X
);
412 assert(columnsLeft
>= 0);
413 if (0 == columnsLeft
) {
414 /* starting next row */
415 currPosY
+= rowMaxPageDy
+ PADDING_BETWEEN_PAGES_Y
;
417 thisRowDx
= currPosX
- PADDING_BETWEEN_PAGES_X
+ PADDING_PAGE_BORDER_RIGHT
;
418 if (totalAreaDx
< thisRowDx
)
419 totalAreaDx
= thisRowDx
;
420 columnsLeft
= columns
;
421 currPosX
= PADDING_PAGE_BORDER_LEFT
;
423 /* DBG_OUT(" page = %3d, (x=%3d, y=%5d, dx=%4d, dy=%4d) orig=(dx=%d,dy=%d)\n",
424 pageNo, (int)pageInfo->currPosX, (int)pageInfo->currPosY,
425 (int)pageInfo->currDx, (int)pageInfo->currDy,
426 (int)pageDx, (int)pageDy); */
429 if (columnsLeft
< columns
) {
430 /* this is a partial row */
431 currPosY
+= rowMaxPageDy
+ PADDING_BETWEEN_PAGES_Y
;
432 thisRowDx
= currPosX
+ (pageInfo
->currDx
+ PADDING_BETWEEN_PAGES_X
) - PADDING_BETWEEN_PAGES_X
+ PADDING_PAGE_BORDER_RIGHT
;
433 if (totalAreaDx
< thisRowDx
)
434 totalAreaDx
= thisRowDx
;
437 /* since pages can be smaller than the drawing area, center them in x axis */
438 if (totalAreaDx
< drawAreaSize
.dx()) {
440 offX
= (drawAreaSize
.dx() - totalAreaDx
) / 2.0 + PADDING_PAGE_BORDER_LEFT
;
442 areaPerPageDx
= totalAreaDx
- PADDING_PAGE_BORDER_LEFT
- PADDING_PAGE_BORDER_RIGHT
;
443 areaPerPageDx
= areaPerPageDx
- (PADDING_BETWEEN_PAGES_X
* (columns
- 1));
444 areaPerPageDxInt
= (int)(areaPerPageDx
/ (double)columns
);
445 areaPerPageDx
= (double)areaPerPageDxInt
;
446 totalAreaDx
= drawAreaSize
.dx();
448 for (pageNo
= 1; pageNo
<= pageCount(); ++pageNo
) {
449 pageInfo
= getPageInfo(pageNo
);
450 if (!pageInfo
->shown
) {
451 assert(!pageInfo
->visible
);
454 pageOffX
= (pageInARow
* (PADDING_BETWEEN_PAGES_X
+ areaPerPageDx
));
455 pageOffX
+= (areaPerPageDx
- pageInfo
->currDx
) / 2;
456 assert(pageOffX
>= 0.0);
457 pageInfo
->currPosX
= pageOffX
+ offX
;
459 if (pageInARow
== columns
)
464 /* if after resizing we would have blank space on the right due to x offset
465 being too much, make x offset smaller so that there's no blank space */
466 if (drawAreaSize
.dx() - (totalAreaDx
- newAreaOffsetX
) > 0) {
467 newAreaOffsetX
= totalAreaDx
- drawAreaSize
.dx();
468 areaOffset
.x
= newAreaOffsetX
;
471 /* if a page is smaller than drawing area in y axis, y-center the page */
472 totalAreaDy
= currPosY
+ PADDING_PAGE_BORDER_BOTTOM
- PADDING_BETWEEN_PAGES_Y
;
473 if (totalAreaDy
< drawAreaSize
.dy()) {
474 offY
= PADDING_PAGE_BORDER_TOP
+ (drawAreaSize
.dy() - totalAreaDy
) / 2;
475 DBG_OUT(" offY = %.2f\n", offY
);
477 totalAreaDy
= drawAreaSize
.dy();
478 for (pageNo
= 1; pageNo
<= pageCount(); ++pageNo
) {
479 pageInfo
= getPageInfo(pageNo
);
480 if (!pageInfo
->shown
) {
481 assert(!pageInfo
->visible
);
484 pageInfo
->currPosY
+= offY
;
485 DBG_OUT(" page = %3d, (x=%3d, y=%5d, dx=%4d, dy=%4d) orig=(dx=%d,dy=%d)\n",
486 pageNo
, (int)pageInfo
->currPosX
, (int)pageInfo
->currPosY
,
487 (int)pageInfo
->currDx
, (int)pageInfo
->currDy
,
488 (int)pageDx
, (int)pageDy
);
492 _canvasSize
= SizeD(totalAreaDx
, totalAreaDy
);
495 void DisplayModel::changeStartPage(int startPage
)
497 assert(validPageNo(startPage
));
498 assert(!displayModeContinuous(displayMode()));
500 int columns
= columnsFromDisplayMode(displayMode());
501 _startPage
= startPage
;
502 for (int pageNo
= 1; pageNo
<= pageCount(); pageNo
++) {
503 PdfPageInfo
*pageInfo
= getPageInfo(pageNo
);
504 if (displayModeContinuous(displayMode()))
505 pageInfo
->shown
= true;
507 pageInfo
->shown
= false;
508 if ((pageNo
>= startPage
) && (pageNo
< startPage
+ columns
)) {
509 //DBG_OUT("DisplayModel::changeStartPage() set page %d as shown\n", pageNo);
510 pageInfo
->shown
= true;
512 pageInfo
->visible
= false;
514 relayout(zoomVirtual(), rotation());
517 /* Given positions of each page in a large sheet that is continous view and
518 coordinates of a current view into that large sheet, calculate which
519 parts of each page is visible on the screen.
520 Needs to be recalucated after scrolling the view. */
521 void DisplayModel::recalcVisibleParts(void)
527 PdfPageInfo
* pageInfo
;
534 drawAreaRect
.x
= (int)areaOffset
.x
;
535 drawAreaRect
.y
= (int)areaOffset
.y
;
536 drawAreaRect
.dx
= drawAreaSize
.dxI();
537 drawAreaRect
.dy
= drawAreaSize
.dyI();
539 // DBG_OUT("DisplayModel::recalcVisibleParts() draw area (x=%3d,y=%3d,dx=%4d,dy=%4d)\n",
540 // drawAreaRect.x, drawAreaRect.y, drawAreaRect.dx, drawAreaRect.dy);
542 for (pageNo
= 1; pageNo
<= pageCount(); ++pageNo
) {
543 pageInfo
= getPageInfo(pageNo
);
544 if (!pageInfo
->shown
) {
545 assert(!pageInfo
->visible
);
548 pageRect
.x
= (int)pageInfo
->currPosX
;
549 pageRect
.y
= (int)pageInfo
->currPosY
;
550 pageRect
.dx
= (int)pageInfo
->currDx
;
551 pageRect
.dy
= (int)pageInfo
->currDy
;
552 pageInfo
->visible
= false;
553 if (RectI_Intersect(&pageRect
, &drawAreaRect
, &intersect
)) {
554 pageInfo
->visible
= true;
556 pageInfo
->bitmapX
= (int) ((double)intersect
.x
- pageInfo
->currPosX
);
557 assert(pageInfo
->bitmapX
>= 0);
558 pageInfo
->bitmapY
= (int) ((double)intersect
.y
- pageInfo
->currPosY
);
559 assert(pageInfo
->bitmapY
>= 0);
560 pageInfo
->bitmapDx
= intersect
.dx
;
561 pageInfo
->bitmapDy
= intersect
.dy
;
562 pageInfo
->screenX
= (int) ((double)intersect
.x
- areaOffset
.x
);
563 assert(pageInfo
->screenX
>= 0);
564 assert(pageInfo
->screenX
<= drawAreaSize
.dx());
565 pageInfo
->screenY
= (int) ((double)intersect
.y
- areaOffset
.y
);
566 assert(pageInfo
->screenX
>= 0);
567 assert(pageInfo
->screenY
<= drawAreaSize
.dy());
568 /* DBG_OUT(" visible page = %d, (x=%3d,y=%3d,dx=%4d,dy=%4d) at (x=%d,y=%d)\n",
569 pageNo, pageInfo->bitmapX, pageInfo->bitmapY,
570 pageInfo->bitmapDx, pageInfo->bitmapDy,
571 pageInfo->screenX, pageInfo->screenY); */
575 assert(visibleCount
> 0);
578 /* Map rectangle <r> on the page <pageNo> to point on the screen. */
579 void DisplayModel::rectCvtUserToScreen(int pageNo
, RectD
*r
)
581 double sx
, sy
, ex
, ey
;
588 cvtUserToScreen(pageNo
, &sx
, &sy
);
589 cvtUserToScreen(pageNo
, &ex
, &ey
);
590 RectD_FromXY(r
, sx
, ex
, sy
, ey
);
593 /* Map rectangle <r> on the page <pageNo> to point on the screen. */
594 void DisplayModel::rectCvtScreenToUser(int *pageNo
, RectD
*r
)
596 double sx
, sy
, ex
, ey
;
603 cvtScreenToUser(pageNo
, &sx
, &sy
);
604 cvtScreenToUser(pageNo
, &ex
, &ey
);
605 RectD_FromXY(r
, sx
, ex
, sy
, ey
);
608 int DisplayModel::getPageNoByPoint (double x
, double y
)
610 for (int pageNo
= 1; pageNo
<= pageCount(); ++pageNo
) {
611 PdfPageInfo
*pageInfo
= getPageInfo(pageNo
);
612 if (!pageInfo
->visible
)
614 assert(pageInfo
->shown
);
615 if (!pageInfo
->shown
)
619 pageOnScreen
.x
= pageInfo
->screenX
;
620 pageOnScreen
.y
= pageInfo
->screenY
;
621 pageOnScreen
.dx
= pageInfo
->bitmapDx
;
622 pageOnScreen
.dy
= pageInfo
->bitmapDy
;
624 if (RectI_Inside (&pageOnScreen
, (int)x
, (int)y
)) /* @note: int casts */
627 return POINT_OUT_OF_PAGE
;
630 void DisplayModel::recalcSearchHitCanvasPos(void)
635 pageNo
= searchHitPageNo
;
636 if (INVALID_PAGE_NO
== pageNo
) return;
637 rect
= searchHitRectPage
;
638 rectCvtUserToScreen(pageNo
, &rect
);
639 searchHitRectCanvas
.x
= (int)rect
.x
;
640 searchHitRectCanvas
.y
= (int)rect
.y
;
641 searchHitRectCanvas
.dx
= (int)rect
.dx
;
642 searchHitRectCanvas
.dy
= (int)rect
.dy
;
645 /* Recalculates the position of each link on the canvas i.e. applies current
646 rotation and zoom level and offsets it by the offset of each page in
648 TODO: applying rotation and zoom level could be split into a separate
649 function for speedup, since it only has to change after rotation/zoomLevel
650 changes while this function has to be called after each scrolling.
651 But I'm not sure if this would be a significant speedup */
652 void DisplayModel::recalcLinksCanvasPos(void)
655 PdfPageInfo
* pageInfo
;
659 // TODO: calling it here is a bit of a hack
660 recalcSearchHitCanvasPos();
662 DBG_OUT("DisplayModel::recalcLinksCanvasPos() linkCount=%d\n", _linkCount
);
670 for (linkNo
= 0; linkNo
< _linkCount
; linkNo
++) {
671 pdfLink
= link(linkNo
);
672 pageInfo
= getPageInfo(pdfLink
->pageNo
);
673 if (!pageInfo
->visible
) {
674 /* hack: make the links on pages that are not shown invisible by
675 moving it off canvas. A better solution would probably be
676 not adding those links in the first place */
677 pdfLink
->rectCanvas
.x
= -100;
678 pdfLink
->rectCanvas
.y
= -100;
679 pdfLink
->rectCanvas
.dx
= 0;
680 pdfLink
->rectCanvas
.dy
= 0;
684 rect
= pdfLink
->rectPage
;
685 rectCvtUserToScreen(pdfLink
->pageNo
, &rect
);
687 #if 0 // this version is correct but needs to be made generic, not specific to poppler
688 /* hack: in PDFs that have a crop-box (like treo700psprint_UG.pdf)
689 we need to shift links by the offset of crop-box. Since we do it
690 after conversion involving ctm, we need to apply current zoom and
691 rotation. This is probably not the best place to be doing this
692 but it's the only one we managed to make work */
693 double offX
= dm
->pdfDoc
->getCatalog()->getPage(pdfLink
->pageNo
)->getCropBox()->x1
;
694 double offY
= dm
->pdfDoc
->getCatalog()->getPage(pdfLink
->pageNo
)->getCropBox()->y1
;
695 if (flippedRotation(dm
->rotation
)) {
700 offX
= offX
* dm
->zoomReal
* 0.01;
701 offY
= offY
* dm
->zoomReal
* 0.01;
703 pdfLink
->rectCanvas
.x
= (int)rect
.x
;
704 pdfLink
->rectCanvas
.y
= (int)rect
.y
;
705 pdfLink
->rectCanvas
.dx
= (int)rect
.dx
;
706 pdfLink
->rectCanvas
.dy
= (int)rect
.dy
;
709 DBG_OUT(" link on page (x=%d, y=%d, dx=%d, dy=%d),\n",
710 (int)pdfLink
->rectPage
.x
, (int)pdfLink
->rectPage
.y
,
711 (int)pdfLink
->rectPage
.dx
, (int)pdfLink
->rectPage
.dy
);
712 DBG_OUT(" screen (x=%d, y=%d, dx=%d, dy=%d)\n",
713 (int)rect
.x
, (int)rect
.y
,
714 (int)rect
.dx
, (int)rect
.dy
);
719 void DisplayModel::clearSearchHit(void)
721 DBG_OUT("DisplayModel::clearSearchHit()\n");
722 searchHitPageNo
= INVALID_PAGE_NO
;
725 void DisplayModel::setSearchHit(int pageNo
, RectD
*hitRect
)
727 //DBG_OUT("DisplayModel::setSearchHit() page=%d at pos (%.2f, %.2f)-(%.2f,%.2f)\n", pageNo, xs, ys, xe, ye);
728 searchHitPageNo
= pageNo
;
729 searchHitRectPage
= *hitRect
;
730 recalcSearchHitCanvasPos();
733 /* Given position 'x'/'y' in the draw area, returns a structure describing
734 a link or NULL if there is no link at this position.
735 Note: DisplayModelSplash owns this memory so it should not be changed by the
736 caller and caller should not reference it after it has changed (i.e. process
737 it immediately since it will become invalid after each _relayout()).
738 TODO: this function is called frequently from UI code so make sure that
739 it's fast enough for a decent number of link.
740 Possible speed improvement: remember which links are visible after
741 scrolling and skip the _Inside test for those invisible.
742 Another way: build another list with only those visible, so we don't
743 even have to travers those that are invisible.
745 PdfLink
*DisplayModel::linkAtPosition(int x
, int y
)
747 if (0 == _linkCount
) return NULL
;
749 if (!_links
) return NULL
;
751 int canvasPosX
= x
+ (int)areaOffset
.x
;
752 int canvasPosY
= y
+ (int)areaOffset
.y
;
753 for (int i
= 0; i
< _linkCount
; i
++) {
754 PdfLink
*currLink
= link(i
);
756 if (RectI_Inside(&(currLink
->rectCanvas
), canvasPosX
, canvasPosY
))
762 /* Send the request to render a given page to a rendering thread */
763 void DisplayModel::startRenderingPage(int pageNo
)
765 RenderQueue_Add(this, pageNo
);
768 void DisplayModel::renderVisibleParts(void)
771 PdfPageInfo
* pageInfo
;
774 // DBG_OUT("DisplayModel::renderVisibleParts()\n");
775 for (pageNo
= 1; pageNo
<= pageCount(); ++pageNo
) {
776 pageInfo
= getPageInfo(pageNo
);
777 if (pageInfo
->visible
) {
778 assert(pageInfo
->shown
);
779 startRenderingPage(pageNo
);
780 lastVisible
= pageNo
;
783 assert(0 != lastVisible
);
784 #ifdef PREDICTIVE_RENDER
785 if (lastVisible
!= pageCount())
786 startRenderingPage(lastVisible
+1);
790 void DisplayModel::changeTotalDrawAreaSize(SizeD totalDrawAreaSize
)
795 currPageNo
= currentPageNo();
797 setTotalDrawAreaSize(totalDrawAreaSize
);
799 relayout(zoomVirtual(), rotation());
800 recalcVisibleParts();
801 recalcLinksCanvasPos();
802 renderVisibleParts();
803 setScrollbarsState();
804 newPageNo
= currentPageNo();
805 if (newPageNo
!= currPageNo
)
807 repaintDisplay(true);
810 void DisplayModel::goToPage(int pageNo
, int scrollY
, int scrollX
)
812 assert(validPageNo(pageNo
));
813 if (!validPageNo(pageNo
))
816 /* in facing mode only start at odd pages (odd because page
817 numbering starts with 1, so odd is really an even page) */
818 if (displayModeFacing(displayMode()))
819 pageNo
= ((pageNo
-1) & ~1) + 1;
821 if (!displayModeContinuous(displayMode())) {
822 /* in single page mode going to another page involves recalculating
823 the size of canvas */
824 changeStartPage(pageNo
);
826 //DBG_OUT("DisplayModel::goToPage(pageNo=%d, scrollY=%d)\n", pageNo, scrollY);
828 areaOffset
.x
= (double)scrollX
;
829 PdfPageInfo
* pageInfo
= getPageInfo(pageNo
);
831 /* Hack: if an image is smaller in Y axis than the draw area, then we center
832 the image by setting pageInfo->currPosY in RecalcPagesInfo. So we shouldn't
833 scroll (adjust areaOffset.y) there because it defeats the purpose.
834 TODO: is there a better way of y-centering?
835 TODO: it probably doesn't work in continuous mode (but that's a corner
837 if (!displayModeContinuous(displayMode()))
838 areaOffset
.y
= (double)scrollY
;
840 areaOffset
.y
= pageInfo
->currPosY
- PADDING_PAGE_BORDER_TOP
+ (double)scrollY
;
841 /* TODO: prevent scrolling too far */
843 recalcVisibleParts();
844 recalcLinksCanvasPos();
845 renderVisibleParts();
846 setScrollbarsState();
848 repaintDisplay(true);
852 void DisplayModel::changeDisplayMode(DisplayMode displayMode
)
854 if (_displayMode
== displayMode
)
857 _displayMode
= displayMode
;
858 int currPageNo
= currentPageNo();
859 if (displayModeContinuous(displayMode
)) {
860 /* mark all pages as shown but not yet visible. The equivalent code
861 for non-continuous mode is in DisplayModel::changeStartPage() called
862 from DisplayModel::goToPage() */
863 for (int pageNo
= 1; pageNo
<= pageCount(); pageNo
++) {
864 PdfPageInfo
*pageInfo
= &(pagesInfo
[pageNo
-1]);
865 pageInfo
->shown
= true;
866 pageInfo
->visible
= false;
868 relayout(zoomVirtual(), rotation());
870 goToPage(currPageNo
, 0);
873 /* given 'columns' and an absolute 'pageNo', return the number of the first
874 page in a row to which a 'pageNo' belongs e.g. if 'columns' is 2 and we
875 have 5 pages in 3 rows:
879 then, we return 1 for pages (1,2), 3 for (3,4) and 5 for (5).
880 This is 1-based index, not 0-based. */
881 static int FirstPageInARowNo(int pageNo
, int columns
)
883 int row
= ((pageNo
- 1) / columns
); /* 0-based row number */
884 int firstPageNo
= row
* columns
+ 1; /* 1-based page in a row */
888 /* In continuous mode just scrolls to the next page. In single page mode
889 rebuilds the display model for the next page.
890 Returns true if advanced to the next page or false if couldn't advance
891 (e.g. because already was at the last page) */
892 bool DisplayModel::goToNextPage(int scrollY
)
894 int columns
= columnsFromDisplayMode(displayMode());
895 int currPageNo
= currentPageNo();
896 int firstPageInCurrRow
= FirstPageInARowNo(currPageNo
, columns
);
897 int newPageNo
= currPageNo
+ columns
;
898 int firstPageInNewRow
= FirstPageInARowNo(newPageNo
, columns
);
900 // DBG_OUT("DisplayModel::goToNextPage(scrollY=%d), currPageNo=%d, firstPageInNewRow=%d\n", scrollY, currPageNo, firstPageInNewRow);
901 if ((firstPageInNewRow
> pageCount()) || (firstPageInCurrRow
== firstPageInNewRow
)) {
902 /* we're on a last row or after it, can't go any further */
905 goToPage(firstPageInNewRow
, scrollY
);
909 bool DisplayModel::goToPrevPage(int scrollY
)
911 int columns
= columnsFromDisplayMode(displayMode());
912 int currPageNo
= currentPageNo();
913 DBG_OUT("DisplayModel::goToPrevPage(scrollY=%d), currPageNo=%d\n", scrollY
, currPageNo
);
914 if (currPageNo
<= columns
) {
915 /* we're on a first page, can't go back */
918 goToPage(currPageNo
- columns
, scrollY
);
922 bool DisplayModel::goToLastPage(void)
924 DBG_OUT("DisplayModel::goToLastPage()\n");
926 int columns
= columnsFromDisplayMode(displayMode());
927 int currPageNo
= currentPageNo();
928 int firstPageInLastRow
= FirstPageInARowNo(pageCount(), columns
);
930 if (currPageNo
!= firstPageInLastRow
) { /* are we on the last page already ? */
931 goToPage(firstPageInLastRow
, 0);
937 bool DisplayModel::goToFirstPage(void)
939 DBG_OUT("DisplayModel::goToFirstPage()\n");
941 if (displayModeContinuous(displayMode())) {
942 if (0 == areaOffset
.y
) {
946 assert(pageShown(_startPage
));
947 if (1 == _startPage
) {
948 /* we're on a first page already */
956 void DisplayModel::scrollXTo(int xOff
)
958 DBG_OUT("DisplayModel::scrollXTo(xOff=%d)\n", xOff
);
959 areaOffset
.x
= (double)xOff
;
960 recalcVisibleParts();
961 recalcLinksCanvasPos();
962 setScrollbarsState();
963 repaintDisplay(false);
966 void DisplayModel::scrollXBy(int dx
)
968 DBG_OUT("DisplayModel::scrollXBy(dx=%d)\n", dx
);
970 double maxX
= _canvasSize
.dx() - drawAreaSize
.dx();
972 double prevX
= areaOffset
.x
;
973 double newX
= prevX
+ (double)dx
;
983 scrollXTo((int)newX
);
986 void DisplayModel::scrollYTo(int yOff
)
988 DBG_OUT("DisplayModel::scrollYTo(yOff=%d)\n", yOff
);
990 int currPageNo
= currentPageNo();
991 areaOffset
.y
= (double)yOff
;
992 recalcVisibleParts();
993 recalcLinksCanvasPos();
994 renderVisibleParts();
996 int newPageNo
= currentPageNo();
997 if (newPageNo
!= currPageNo
)
999 repaintDisplay(false);
1002 /* Scroll the doc in y-axis by 'dy'. If 'changePage' is TRUE, automatically
1003 switch to prev/next page in non-continuous mode if we scroll past the edges
1005 void DisplayModel::scrollYBy(int dy
, bool changePage
)
1007 PdfPageInfo
* pageInfo
;
1008 int currYOff
= (int)areaOffset
.y
;
1012 DBG_OUT("DisplayModel::scrollYBy(dy=%d, changePage=%d)\n", dy
, (int)changePage
);
1014 if (0 == dy
) return;
1016 int newYOff
= currYOff
;
1018 if (!displayModeContinuous(displayMode()) && changePage
) {
1019 if ((dy
< 0) && (0 == currYOff
)) {
1020 if (_startPage
> 1) {
1021 newPageNo
= _startPage
-1;
1022 assert(validPageNo(newPageNo
));
1023 pageInfo
= getPageInfo(newPageNo
);
1024 newYOff
= (int)pageInfo
->currDy
- drawAreaSize
.dyI();
1026 newYOff
= 0; /* TODO: center instead? */
1027 goToPrevPage(newYOff
);
1032 /* see if we have to change page when scrolling forward */
1033 if ((dy
> 0) && (_startPage
< pageCount())) {
1034 if ((int)areaOffset
.y
+ drawAreaSize
.dyI() >= _canvasSize
.dyI()) {
1044 } else if (newYOff
+ drawAreaSize
.dyI() > _canvasSize
.dyI()) {
1045 newYOff
= _canvasSize
.dyI() - drawAreaSize
.dyI();
1048 if (newYOff
== currYOff
)
1051 currPageNo
= currentPageNo();
1052 areaOffset
.y
= (double)newYOff
;
1053 recalcVisibleParts();
1054 recalcLinksCanvasPos();
1055 renderVisibleParts();
1056 setScrollbarsState();
1057 newPageNo
= currentPageNo();
1058 if (newPageNo
!= currPageNo
)
1060 repaintDisplay(false);
1063 void DisplayModel::scrollYByAreaDy(bool forward
, bool changePage
)
1065 int toScroll
= drawAreaSize
.dyI();
1067 scrollYBy(toScroll
, changePage
);
1069 scrollYBy(-toScroll
, changePage
);
1072 void DisplayModel::zoomTo(double zoomVirtual
)
1074 //DBG_OUT("DisplayModel::zoomTo() zoomVirtual=%.6f\n", _zoomVirtual);
1075 int currPageNo
= currentPageNo();
1076 relayout(zoomVirtual
, rotation());
1077 goToPage(currPageNo
, 0);
1080 void DisplayModel::zoomBy(double zoomFactor
)
1082 double newZoom
= _zoomReal
* zoomFactor
;
1083 //DBG_OUT("DisplayModel::zoomBy() zoomReal=%.6f, zoomFactor=%.2f, newZoom=%.2f\n", dm->zoomReal, zoomFactor, newZoom);
1084 if (newZoom
> ZOOM_MAX
)
1089 void DisplayModel::rotateBy(int newRotation
)
1091 normalizeRotation(&newRotation
);
1092 assert(0 != newRotation
);
1093 if (0 == newRotation
)
1095 assert(validRotation(newRotation
));
1096 if (!validRotation(newRotation
))
1099 newRotation
+= rotation();
1100 normalizeRotation(&newRotation
);
1101 assert(validRotation(newRotation
));
1102 if (!validRotation(newRotation
))
1105 int currPageNo
= currentPageNo();
1106 relayout(zoomVirtual(), newRotation
);
1107 goToPage(currPageNo
, 0);
1110 void DisplayModel::showNormalCursor(void)
1112 SetCursor(LoadCursor(NULL
, IDC_ARROW
));
1115 void DisplayModel::showBusyCursor(void)
1117 // TODO: what is the right cursor?
1118 // can I set it per-window only?
1119 SetCursor(LoadCursor(NULL
, IDC_ARROW
));
1122 void LockCache(void) {
1123 gLockMutex(&cacheMutex
);
1126 void UnlockCache(void) {
1127 gUnlockMutex(&cacheMutex
);
1130 static void BitmapCacheEntry_Free(BitmapCacheEntry
*entry
) {
1133 delete entry
->bitmap
;
1137 void BitmapCache_FreeAll(void) {
1139 for (int i
=0; i
< gBitmapCacheCount
; i
++) {
1140 BitmapCacheEntry_Free(gBitmapCache
[i
]);
1141 gBitmapCache
[i
] = NULL
;
1143 gBitmapCacheCount
= 0;
1147 /* Free all bitmaps in the cache that are not visible. Returns true if freed
1148 at least one item. */
1149 bool BitmapCache_FreeNotVisible(void) {
1151 bool freedSomething
= false;
1152 int cacheCount
= gBitmapCacheCount
;
1154 for (int i
= 0; i
< cacheCount
; i
++) {
1155 BitmapCacheEntry
* entry
= gBitmapCache
[i
];
1156 bool shouldFree
= !entry
->dm
->pageVisibleNearby(entry
->pageNo
);
1158 if (!freedSomething
)
1159 DBG_OUT("BitmapCache_FreeNotVisible() ");
1160 DBG_OUT("freed %d ", entry
->pageNo
);
1161 freedSomething
= true;
1162 BitmapCacheEntry_Free(gBitmapCache
[i
]);
1163 gBitmapCache
[i
] = NULL
;
1164 --gBitmapCacheCount
;
1168 gBitmapCache
[curPos
] = gBitmapCache
[i
];
1176 return freedSomething
;
1179 static bool BitmapCache_FreePage(DisplayModel
*dm
, int pageNo
) {
1181 int cacheCount
= gBitmapCacheCount
;
1182 bool freedSomething
= false;
1184 for (int i
= 0; i
< cacheCount
; i
++) {
1185 bool shouldFree
= (gBitmapCache
[i
]->dm
== dm
) && (gBitmapCache
[i
]->pageNo
== pageNo
);
1187 if (!freedSomething
)
1188 DBG_OUT("BitmapCache_FreePage() ");
1189 DBG_OUT("freed %d ", gBitmapCache
[i
]->pageNo
);
1190 freedSomething
= true;
1191 BitmapCacheEntry_Free(gBitmapCache
[i
]);
1192 gBitmapCache
[i
] = NULL
;
1193 --gBitmapCacheCount
;
1197 gBitmapCache
[curPos
] = gBitmapCache
[i
];
1205 return freedSomething
;
1208 /* Free all bitmaps cached for a given <dm>. Returns TRUE if freed
1209 at least one item. */
1210 bool BitmapCache_FreeForDisplayModel(DisplayModel
*dm
) {
1212 int cacheCount
= gBitmapCacheCount
;
1213 bool freedSomething
= false;
1215 for (int i
= 0; i
< cacheCount
; i
++) {
1216 bool shouldFree
= (gBitmapCache
[i
]->dm
== dm
);
1218 if (!freedSomething
)
1219 DBG_OUT("BitmapCache_FreeForDisplayModel() ");
1220 DBG_OUT("freed %d ", gBitmapCache
[i
]->pageNo
);
1221 freedSomething
= true;
1222 BitmapCacheEntry_Free(gBitmapCache
[i
]);
1223 gBitmapCache
[i
] = NULL
;
1224 --gBitmapCacheCount
;
1228 gBitmapCache
[curPos
] = gBitmapCache
[i
];
1236 return freedSomething
;
1239 void BitmapCache_Add(DisplayModel
*dm
, int pageNo
, double zoomLevel
, int rotation
,
1240 RenderedBitmap
*bitmap
, double renderTime
) {
1241 assert(gBitmapCacheCount
<= MAX_BITMAPS_CACHED
);
1243 assert(validRotation(rotation
));
1245 normalizeRotation(&rotation
);
1246 DBG_OUT("BitmapCache_Add(pageNo=%d, zoomLevel=%.2f%%, rotation=%d)\n", pageNo
, zoomLevel
, rotation
);
1249 /* It's possible there still is a cached bitmap with different zoomLevel/rotation */
1250 BitmapCache_FreePage(dm
, pageNo
);
1252 if (gBitmapCacheCount
>= MAX_BITMAPS_CACHED
- 1) {
1253 /* TODO: find entry that is not visible and remove it from cache to
1254 make room for new entry */
1256 /* @note: crossing initialization of "BitmapCacheEntry* entry" not allowed in mingw */
1257 //goto UnlockAndExit;
1261 BitmapCacheEntry
* entry
= (BitmapCacheEntry
*)malloc(sizeof(BitmapCacheEntry
));
1267 entry
->pageNo
= pageNo
;
1268 entry
->zoomLevel
= zoomLevel
;
1269 entry
->rotation
= rotation
;
1270 entry
->bitmap
= bitmap
;
1271 entry
->renderTime
= renderTime
;
1272 gBitmapCache
[gBitmapCacheCount
++] = entry
;
1277 BitmapCacheEntry
*BitmapCache_Find(DisplayModel
*dm
, int pageNo
) {
1278 BitmapCacheEntry
* entry
;
1280 for (int i
= 0; i
< gBitmapCacheCount
; i
++) {
1281 entry
= gBitmapCache
[i
];
1282 if ( (dm
== entry
->dm
) && (pageNo
== entry
->pageNo
) ) {
1292 BitmapCacheEntry
*BitmapCache_Find(DisplayModel
*dm
, int pageNo
, double zoomLevel
, int rotation
) {
1293 BitmapCacheEntry
*entry
;
1294 normalizeRotation(&rotation
);
1296 for (int i
= 0; i
< gBitmapCacheCount
; i
++) {
1297 entry
= gBitmapCache
[i
];
1298 if ( (dm
== entry
->dm
) && (pageNo
== entry
->pageNo
) &&
1299 (zoomLevel
== entry
->zoomLevel
) && (rotation
== entry
->rotation
)) {
1309 /* Return true if a bitmap for a page defined by <dm>, <pageNo>, <zoomLevel>
1310 and <rotation> exists in the cache */
1311 bool BitmapCache_Exists(DisplayModel
*dm
, int pageNo
, double zoomLevel
, int rotation
) {
1312 if (BitmapCache_Find(dm
, pageNo
, zoomLevel
, rotation
))