Change the translation of the "Help" menu item to "?", so that the menu can be displa...
[reactos.git] / rosapps / smartpdf / src / DisplayModel.cc
1 /* Copyright Krzysztof Kowalczyk 2006-2007
2 License: GPLv2 */
3 #include "DisplayModel.h"
4
5 #include "str_util.h"
6
7 // TODO: get rid of the need for GooString and UGooString in common code
8 #include "GooString.h"
9 #include "UGooString.h"
10 // TODO: get rid of the need for GooMutex.h ?
11 #include "GooMutex.h"
12
13 #include <assert.h>
14 #include <stdlib.h>
15
16 #ifdef _WIN32
17 #define PREDICTIVE_RENDER 1
18 #endif
19
20 #define MAX_BITMAPS_CACHED 256
21 static GooMutex cacheMutex;
22 static BitmapCacheEntry * gBitmapCache[MAX_BITMAPS_CACHED] = {0};
23 static int gBitmapCacheCount = 0;
24
25 static MutexAutoInitDestroy gAutoCacheMutex(&cacheMutex);
26
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
34 };
35
36 bool validZoomReal(double zoomReal)
37 {
38 if ((zoomReal < ZOOM_MIN) || (zoomReal > ZOOM_MAX)) {
39 DBG_OUT("validZoomReal() invalid zoom: %.4f\n", zoomReal);
40 return false;
41 }
42 return true;
43 }
44
45 bool displayModeFacing(DisplayMode displayMode)
46 {
47 if ((DM_SINGLE_PAGE == displayMode) || (DM_CONTINUOUS == displayMode))
48 return false;
49 else if ((DM_FACING == displayMode) || (DM_CONTINUOUS_FACING == displayMode))
50 return true;
51 assert(0);
52 return false;
53 }
54
55 bool displayModeContinuous(DisplayMode displayMode)
56 {
57 if ((DM_SINGLE_PAGE == displayMode) || (DM_FACING == displayMode))
58 return false;
59 else if ((DM_CONTINUOUS == displayMode) || (DM_CONTINUOUS_FACING == displayMode))
60 return true;
61 assert(0);
62 return false;
63 }
64
65 int columnsFromDisplayMode(DisplayMode displayMode)
66 {
67 if (DM_SINGLE_PAGE == displayMode) {
68 return 1;
69 } else if (DM_FACING == displayMode) {
70 return 2;
71 } else if (DM_CONTINUOUS == displayMode) {
72 return 1;
73 } else if (DM_CONTINUOUS_FACING == displayMode) {
74 return 2;
75 } else
76 assert(0);
77 return 1;
78 }
79
80 DisplaySettings *globalDisplaySettings(void)
81 {
82 return &gDisplaySettings;
83 }
84
85 bool rotationFlipped(int rotation)
86 {
87 assert(validRotation(rotation));
88 normalizeRotation(&rotation);
89 if ((90 == rotation) || (270 == rotation))
90 return true;
91 return false;
92 }
93
94 bool displayStateFromDisplayModel(DisplayState *ds, DisplayModel *dm)
95 {
96 ds->filePath = str_escape(dm->fileName());
97 if (!ds->filePath)
98 return FALSE;
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 */
107 ds->scrollY = 0;
108 } else {
109 ds->scrollY = (int)dm->areaOffset.y;
110 }
111 ds->windowDx = dm->drawAreaSize.dxI();
112 ds->windowDy = dm->drawAreaSize.dyI();
113 ds->windowX = 0;
114 ds->windowY = 0;
115 return TRUE;
116 }
117
118 /* Given 'pageInfo', which should contain correct information about
119 pageDx, pageDy and rotation, return a page size after applying a global
120 rotation */
121 void pageSizeAfterRotation(PdfPageInfo *pageInfo, int rotation,
122 double *pageDxOut, double *pageDyOut)
123 {
124 assert(pageInfo && pageDxOut && pageDyOut);
125 if (!pageInfo || !pageDxOut || !pageDyOut)
126 return;
127
128 *pageDxOut = pageInfo->pageDx;
129 *pageDyOut = pageInfo->pageDy;
130
131 rotation = rotation + pageInfo->rotation;
132 normalizeRotation(&rotation);
133 if (rotationFlipped(rotation))
134 swap_double(pageDxOut, pageDyOut);
135 }
136
137 DisplayModel::DisplayModel(DisplayMode displayMode)
138 {
139 _displayMode = displayMode;
140 _rotation = INVALID_ROTATION;
141 _zoomVirtual = INVALID_ZOOM;
142 _fullScreen = false;
143 _startPage = INVALID_PAGE_NO;
144 _appData = NULL;
145 _pdfEngine = NULL;
146 pagesInfo = NULL;
147
148 _linkCount = 0;
149 _links = NULL;
150
151 searchHitPageNo = INVALID_PAGE_NO;
152 searchState.searchState = eSsNone;
153 searchState.str = new GooString();
154 searchState.strU = new UGooString();
155 }
156
157 DisplayModel::~DisplayModel()
158 {
159 delete _pdfEngine;
160 }
161
162 PdfPageInfo *DisplayModel::getPageInfo(int pageNo) const
163 {
164 assert(validPageNo(pageNo));
165 assert(pagesInfo);
166 if (!pagesInfo) return NULL;
167 return &(pagesInfo[pageNo-1]);
168 }
169
170 bool DisplayModel::load(const char *fileName, int startPage, WindowInfo *win)
171 {
172 assert(fileName);
173 if (!_pdfEngine->load(fileName, win))
174 return false;
175
176 if (validPageNo(startPage))
177 _startPage = startPage;
178 else
179 _startPage = 1;
180
181 if (!buildPagesInfo())
182 return false;
183 return true;
184 }
185
186 bool DisplayModel::buildPagesInfo(void)
187 {
188 assert(!pagesInfo);
189 int _pageCount = pageCount();
190
191 pagesInfo = (PdfPageInfo*)calloc(1, _pageCount * sizeof(PdfPageInfo));
192 if (!pagesInfo)
193 return false;
194
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);
201
202 pageInfo->links = NULL;
203 pageInfo->textPage = NULL;
204
205 pageInfo->visible = false;
206 pageInfo->shown = false;
207 if (displayModeContinuous(_displayMode)) {
208 pageInfo->shown = true;
209 } else {
210 if ((pageNo >= _startPage) && (pageNo < _startPage + columnsFromDisplayMode(_displayMode))) {
211 DBG_OUT("DisplayModelSplash::CreateFromPdfDoc() set page %d as shown\n", pageNo);
212 pageInfo->shown = true;
213 }
214 }
215 }
216 return true;
217 }
218
219 bool DisplayModel::pageShown(int pageNo)
220 {
221 PdfPageInfo *pageInfo = getPageInfo(pageNo);
222 if (!pageInfo)
223 return false;
224 return pageInfo->shown;
225 }
226
227 bool DisplayModel::pageVisible(int pageNo)
228 {
229 PdfPageInfo *pageInfo = getPageInfo(pageNo);
230 if (!pageInfo)
231 return false;
232 return pageInfo->visible;
233 }
234
235 /* Return true if a page is visible or a page below or above is visible */
236 bool DisplayModel::pageVisibleNearby(int pageNo)
237 {
238 /* TODO: should it check 2 pages above and below in facing mode? */
239 if (pageVisible(pageNo))
240 return true;
241 if (validPageNo(pageNo-1) && pageVisible(pageNo-1))
242 return true;
243 if (validPageNo(pageNo+1) && pageVisible(pageNo+1))
244 return true;
245 return false;
246 }
247
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)
251 {
252 double _zoomReal, zoomX, zoomY, pageDx, pageDy;
253 double areaForPageDx, areaForPageDy;
254 int areaForPageDxInt;
255 int columns;
256
257 assert(0 != drawAreaSize.dxI());
258 assert(0 != drawAreaSize.dy());
259
260 pageSizeAfterRotation(getPageInfo(pageNo), rotation(), &pageDx, &pageDy);
261
262 assert(0 != (int)pageDx);
263 assert(0 != (int)pageDy);
264
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;
277 if (zoomX < zoomY)
278 _zoomReal = zoomX;
279 else
280 _zoomReal= zoomY;
281 } else
282 _zoomReal = zoomVirtual;
283
284 return _zoomReal;
285 }
286
287 int DisplayModel::firstVisiblePageNo(void) const
288 {
289 assert(pagesInfo);
290 if (!pagesInfo) return INVALID_PAGE_NO;
291
292 for (int pageNo = 1; pageNo <= pageCount(); ++pageNo) {
293 PdfPageInfo *pageInfo = getPageInfo(pageNo);
294 if (pageInfo->visible)
295 return pageNo;
296 }
297 assert(0);
298 return INVALID_PAGE_NO;
299 }
300
301 int DisplayModel::currentPageNo(void) const
302 {
303 if (displayModeContinuous(displayMode()))
304 return firstVisiblePageNo();
305 else
306 return _startPage;
307 }
308
309 void DisplayModel::setZoomVirtual(double zoomVirtual)
310 {
311 int pageNo;
312 double minZoom = INVALID_BIG_ZOOM;
313 double thisPageZoom;
314
315 assert(ValidZoomVirtual(zoomVirtual));
316 _zoomVirtual = zoomVirtual;
317
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;
328 }
329 }
330 assert(minZoom != INVALID_BIG_ZOOM);
331 this->_zoomReal = minZoom;
332 } else
333 this->_zoomReal = zoomVirtual;
334 }
335
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:
338 * zoom changes
339 * rotation changes
340 * switching between display modes
341 * navigating to another page in non-continuous mode */
342 void DisplayModel::relayout(double zoomVirtual, int rotation)
343 {
344 int pageNo;
345 PdfPageInfo*pageInfo = NULL;
346 double currPosX;
347 double pageDx=0, pageDy=0;
348 int currDxInt, currDyInt;
349 double totalAreaDx, totalAreaDy;
350 double areaPerPageDx;
351 int areaPerPageDxInt;
352 double thisRowDx;
353 double rowMaxPageDy;
354 double offX, offY;
355 double pageOffX;
356 int columnsLeft;
357 int pageInARow;
358 int columns;
359 double newAreaOffsetX;
360
361 assert(pagesInfo);
362 if (!pagesInfo)
363 return;
364
365 normalizeRotation(&rotation);
366 assert(validRotation(rotation));
367
368 _rotation = rotation;
369
370 double currPosY = PADDING_PAGE_BORDER_TOP;
371 double currZoomReal = _zoomReal;
372 setZoomVirtual(zoomVirtual);
373
374 // DBG_OUT("DisplayModel::relayout(), pageCount=%d, zoomReal=%.6f, zoomVirtual=%.2f\n", pageCount, dm->zoomReal, dm->zoomVirtual);
375 totalAreaDx = 0;
376
377 if (0 == currZoomReal)
378 newAreaOffsetX = 0.0;
379 else
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;
388 rowMaxPageDy = 0;
389 for (pageNo = 1; pageNo <= pageCount(); ++pageNo) {
390 pageInfo = getPageInfo(pageNo);
391 if (!pageInfo->shown) {
392 assert(!pageInfo->visible);
393 continue;
394 }
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;
400
401 if (rowMaxPageDy < pageInfo->currDy)
402 rowMaxPageDy = pageInfo->currDy;
403
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);
410
411 --columnsLeft;
412 assert(columnsLeft >= 0);
413 if (0 == columnsLeft) {
414 /* starting next row */
415 currPosY += rowMaxPageDy + PADDING_BETWEEN_PAGES_Y;
416 rowMaxPageDy = 0;
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;
422 }
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); */
427 }
428
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;
435 }
436
437 /* since pages can be smaller than the drawing area, center them in x axis */
438 if (totalAreaDx < drawAreaSize.dx()) {
439 areaOffset.x = 0.0;
440 offX = (drawAreaSize.dx() - totalAreaDx) / 2.0 + PADDING_PAGE_BORDER_LEFT;
441 assert(offX >= 0.0);
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();
447 pageInARow = 0;
448 for (pageNo = 1; pageNo <= pageCount(); ++pageNo) {
449 pageInfo = getPageInfo(pageNo);
450 if (!pageInfo->shown) {
451 assert(!pageInfo->visible);
452 continue;
453 }
454 pageOffX = (pageInARow * (PADDING_BETWEEN_PAGES_X + areaPerPageDx));
455 pageOffX += (areaPerPageDx - pageInfo->currDx) / 2;
456 assert(pageOffX >= 0.0);
457 pageInfo->currPosX = pageOffX + offX;
458 ++pageInARow;
459 if (pageInARow == columns)
460 pageInARow = 0;
461 }
462 }
463
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;
469 }
470
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);
476 assert(offY >= 0.0);
477 totalAreaDy = drawAreaSize.dy();
478 for (pageNo = 1; pageNo <= pageCount(); ++pageNo) {
479 pageInfo = getPageInfo(pageNo);
480 if (!pageInfo->shown) {
481 assert(!pageInfo->visible);
482 continue;
483 }
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);
489 }
490 }
491
492 _canvasSize = SizeD(totalAreaDx, totalAreaDy);
493 }
494
495 void DisplayModel::changeStartPage(int startPage)
496 {
497 assert(validPageNo(startPage));
498 assert(!displayModeContinuous(displayMode()));
499
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;
506 else
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;
511 }
512 pageInfo->visible = false;
513 }
514 relayout(zoomVirtual(), rotation());
515 }
516
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)
522 {
523 int pageNo;
524 RectI drawAreaRect;
525 RectI pageRect;
526 RectI intersect;
527 PdfPageInfo* pageInfo;
528 int visibleCount;
529
530 assert(pagesInfo);
531 if (!pagesInfo)
532 return;
533
534 drawAreaRect.x = (int)areaOffset.x;
535 drawAreaRect.y = (int)areaOffset.y;
536 drawAreaRect.dx = drawAreaSize.dxI();
537 drawAreaRect.dy = drawAreaSize.dyI();
538
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);
541 visibleCount = 0;
542 for (pageNo = 1; pageNo <= pageCount(); ++pageNo) {
543 pageInfo = getPageInfo(pageNo);
544 if (!pageInfo->shown) {
545 assert(!pageInfo->visible);
546 continue;
547 }
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;
555 visibleCount += 1;
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); */
572 }
573 }
574
575 assert(visibleCount > 0);
576 }
577
578 /* Map rectangle <r> on the page <pageNo> to point on the screen. */
579 void DisplayModel::rectCvtUserToScreen(int pageNo, RectD *r)
580 {
581 double sx, sy, ex, ey;
582
583 sx = r->x;
584 sy = r->y;
585 ex = r->x + r->dx;
586 ey = r->y + r->dy;
587
588 cvtUserToScreen(pageNo, &sx, &sy);
589 cvtUserToScreen(pageNo, &ex, &ey);
590 RectD_FromXY(r, sx, ex, sy, ey);
591 }
592
593 /* Map rectangle <r> on the page <pageNo> to point on the screen. */
594 void DisplayModel::rectCvtScreenToUser(int *pageNo, RectD *r)
595 {
596 double sx, sy, ex, ey;
597
598 sx = r->x;
599 sy = r->y;
600 ex = r->x + r->dx;
601 ey = r->y + r->dy;
602
603 cvtScreenToUser(pageNo, &sx, &sy);
604 cvtScreenToUser(pageNo, &ex, &ey);
605 RectD_FromXY(r, sx, ex, sy, ey);
606 }
607
608 int DisplayModel::getPageNoByPoint (double x, double y)
609 {
610 for (int pageNo = 1; pageNo <= pageCount(); ++pageNo) {
611 PdfPageInfo *pageInfo = getPageInfo(pageNo);
612 if (!pageInfo->visible)
613 continue;
614 assert(pageInfo->shown);
615 if (!pageInfo->shown)
616 continue;
617
618 RectI pageOnScreen;
619 pageOnScreen.x = pageInfo->screenX;
620 pageOnScreen.y = pageInfo->screenY;
621 pageOnScreen.dx = pageInfo->bitmapDx;
622 pageOnScreen.dy = pageInfo->bitmapDy;
623
624 if (RectI_Inside (&pageOnScreen, (int)x, (int)y)) /* @note: int casts */
625 return pageNo;
626 }
627 return POINT_OUT_OF_PAGE;
628 }
629
630 void DisplayModel::recalcSearchHitCanvasPos(void)
631 {
632 int pageNo;
633 RectD rect;
634
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;
643 }
644
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
647 the canvas.
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)
653 {
654 PdfLink * pdfLink;
655 PdfPageInfo * pageInfo;
656 int linkNo;
657 RectD rect;
658
659 // TODO: calling it here is a bit of a hack
660 recalcSearchHitCanvasPos();
661
662 DBG_OUT("DisplayModel::recalcLinksCanvasPos() linkCount=%d\n", _linkCount);
663
664 if (0 == _linkCount)
665 return;
666 assert(_links);
667 if (!_links)
668 return;
669
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;
681 continue;
682 }
683
684 rect = pdfLink->rectPage;
685 rectCvtUserToScreen(pdfLink->pageNo, &rect);
686
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)) {
696 double tmp = offX;
697 offX = offY;
698 offY = tmp;
699 }
700 offX = offX * dm->zoomReal * 0.01;
701 offY = offY * dm->zoomReal * 0.01;
702 #else
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;
707 #endif
708 #if 0
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);
715 #endif
716 }
717 }
718
719 void DisplayModel::clearSearchHit(void)
720 {
721 DBG_OUT("DisplayModel::clearSearchHit()\n");
722 searchHitPageNo = INVALID_PAGE_NO;
723 }
724
725 void DisplayModel::setSearchHit(int pageNo, RectD *hitRect)
726 {
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();
731 }
732
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.
744 */
745 PdfLink *DisplayModel::linkAtPosition(int x, int y)
746 {
747 if (0 == _linkCount) return NULL;
748 assert(_links);
749 if (!_links) return NULL;
750
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);
755
756 if (RectI_Inside(&(currLink->rectCanvas), canvasPosX, canvasPosY))
757 return currLink;
758 }
759 return NULL;
760 }
761
762 /* Send the request to render a given page to a rendering thread */
763 void DisplayModel::startRenderingPage(int pageNo)
764 {
765 RenderQueue_Add(this, pageNo);
766 }
767
768 void DisplayModel::renderVisibleParts(void)
769 {
770 int pageNo;
771 PdfPageInfo* pageInfo;
772 int lastVisible = 0;
773
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;
781 }
782 }
783 assert(0 != lastVisible);
784 #ifdef PREDICTIVE_RENDER
785 if (lastVisible != pageCount())
786 startRenderingPage(lastVisible+1);
787 #endif
788 }
789
790 void DisplayModel::changeTotalDrawAreaSize(SizeD totalDrawAreaSize)
791 {
792 int newPageNo;
793 int currPageNo;
794
795 currPageNo = currentPageNo();
796
797 setTotalDrawAreaSize(totalDrawAreaSize);
798
799 relayout(zoomVirtual(), rotation());
800 recalcVisibleParts();
801 recalcLinksCanvasPos();
802 renderVisibleParts();
803 setScrollbarsState();
804 newPageNo = currentPageNo();
805 if (newPageNo != currPageNo)
806 pageChanged();
807 repaintDisplay(true);
808 }
809
810 void DisplayModel::goToPage(int pageNo, int scrollY, int scrollX)
811 {
812 assert(validPageNo(pageNo));
813 if (!validPageNo(pageNo))
814 return;
815
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;
820
821 if (!displayModeContinuous(displayMode())) {
822 /* in single page mode going to another page involves recalculating
823 the size of canvas */
824 changeStartPage(pageNo);
825 }
826 //DBG_OUT("DisplayModel::goToPage(pageNo=%d, scrollY=%d)\n", pageNo, scrollY);
827 if (-1 != scrollX)
828 areaOffset.x = (double)scrollX;
829 PdfPageInfo * pageInfo = getPageInfo(pageNo);
830
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
836 case, I hope) */
837 if (!displayModeContinuous(displayMode()))
838 areaOffset.y = (double)scrollY;
839 else
840 areaOffset.y = pageInfo->currPosY - PADDING_PAGE_BORDER_TOP + (double)scrollY;
841 /* TODO: prevent scrolling too far */
842
843 recalcVisibleParts();
844 recalcLinksCanvasPos();
845 renderVisibleParts();
846 setScrollbarsState();
847 pageChanged();
848 repaintDisplay(true);
849 }
850
851
852 void DisplayModel::changeDisplayMode(DisplayMode displayMode)
853 {
854 if (_displayMode == displayMode)
855 return;
856
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;
867 }
868 relayout(zoomVirtual(), rotation());
869 }
870 goToPage(currPageNo, 0);
871 }
872
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:
876 (1,2)
877 (3,4)
878 (5)
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)
882 {
883 int row = ((pageNo - 1) / columns); /* 0-based row number */
884 int firstPageNo = row * columns + 1; /* 1-based page in a row */
885 return firstPageNo;
886 }
887
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)
893 {
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);
899
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 */
903 return FALSE;
904 }
905 goToPage(firstPageInNewRow, scrollY);
906 return TRUE;
907 }
908
909 bool DisplayModel::goToPrevPage(int scrollY)
910 {
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 */
916 return FALSE;
917 }
918 goToPage(currPageNo - columns, scrollY);
919 return TRUE;
920 }
921
922 bool DisplayModel::goToLastPage(void)
923 {
924 DBG_OUT("DisplayModel::goToLastPage()\n");
925
926 int columns = columnsFromDisplayMode(displayMode());
927 int currPageNo = currentPageNo();
928 int firstPageInLastRow = FirstPageInARowNo(pageCount(), columns);
929
930 if (currPageNo != firstPageInLastRow) { /* are we on the last page already ? */
931 goToPage(firstPageInLastRow, 0);
932 return TRUE;
933 }
934 return FALSE;
935 }
936
937 bool DisplayModel::goToFirstPage(void)
938 {
939 DBG_OUT("DisplayModel::goToFirstPage()\n");
940
941 if (displayModeContinuous(displayMode())) {
942 if (0 == areaOffset.y) {
943 return FALSE;
944 }
945 } else {
946 assert(pageShown(_startPage));
947 if (1 == _startPage) {
948 /* we're on a first page already */
949 return FALSE;
950 }
951 }
952 goToPage(1, 0);
953 return TRUE;
954 }
955
956 void DisplayModel::scrollXTo(int xOff)
957 {
958 DBG_OUT("DisplayModel::scrollXTo(xOff=%d)\n", xOff);
959 areaOffset.x = (double)xOff;
960 recalcVisibleParts();
961 recalcLinksCanvasPos();
962 setScrollbarsState();
963 repaintDisplay(false);
964 }
965
966 void DisplayModel::scrollXBy(int dx)
967 {
968 DBG_OUT("DisplayModel::scrollXBy(dx=%d)\n", dx);
969
970 double maxX = _canvasSize.dx() - drawAreaSize.dx();
971 assert(maxX >= 0.0);
972 double prevX = areaOffset.x;
973 double newX = prevX + (double)dx;
974 if (newX < 0.0)
975 newX = 0.0;
976 else
977 if (newX > maxX)
978 newX = maxX;
979
980 if (newX == prevX)
981 return;
982
983 scrollXTo((int)newX);
984 }
985
986 void DisplayModel::scrollYTo(int yOff)
987 {
988 DBG_OUT("DisplayModel::scrollYTo(yOff=%d)\n", yOff);
989
990 int currPageNo = currentPageNo();
991 areaOffset.y = (double)yOff;
992 recalcVisibleParts();
993 recalcLinksCanvasPos();
994 renderVisibleParts();
995
996 int newPageNo = currentPageNo();
997 if (newPageNo != currPageNo)
998 pageChanged();
999 repaintDisplay(false);
1000 }
1001
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
1004 of current page */
1005 void DisplayModel::scrollYBy(int dy, bool changePage)
1006 {
1007 PdfPageInfo * pageInfo;
1008 int currYOff = (int)areaOffset.y;
1009 int newPageNo;
1010 int currPageNo;
1011
1012 DBG_OUT("DisplayModel::scrollYBy(dy=%d, changePage=%d)\n", dy, (int)changePage);
1013 assert(0 != dy);
1014 if (0 == dy) return;
1015
1016 int newYOff = currYOff;
1017
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();
1025 if (newYOff < 0)
1026 newYOff = 0; /* TODO: center instead? */
1027 goToPrevPage(newYOff);
1028 return;
1029 }
1030 }
1031
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()) {
1035 goToNextPage(0);
1036 return;
1037 }
1038 }
1039 }
1040
1041 newYOff += dy;
1042 if (newYOff < 0) {
1043 newYOff = 0;
1044 } else if (newYOff + drawAreaSize.dyI() > _canvasSize.dyI()) {
1045 newYOff = _canvasSize.dyI() - drawAreaSize.dyI();
1046 }
1047
1048 if (newYOff == currYOff)
1049 return;
1050
1051 currPageNo = currentPageNo();
1052 areaOffset.y = (double)newYOff;
1053 recalcVisibleParts();
1054 recalcLinksCanvasPos();
1055 renderVisibleParts();
1056 setScrollbarsState();
1057 newPageNo = currentPageNo();
1058 if (newPageNo != currPageNo)
1059 pageChanged();
1060 repaintDisplay(false);
1061 }
1062
1063 void DisplayModel::scrollYByAreaDy(bool forward, bool changePage)
1064 {
1065 int toScroll = drawAreaSize.dyI();
1066 if (forward)
1067 scrollYBy(toScroll, changePage);
1068 else
1069 scrollYBy(-toScroll, changePage);
1070 }
1071
1072 void DisplayModel::zoomTo(double zoomVirtual)
1073 {
1074 //DBG_OUT("DisplayModel::zoomTo() zoomVirtual=%.6f\n", _zoomVirtual);
1075 int currPageNo = currentPageNo();
1076 relayout(zoomVirtual, rotation());
1077 goToPage(currPageNo, 0);
1078 }
1079
1080 void DisplayModel::zoomBy(double zoomFactor)
1081 {
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)
1085 return;
1086 zoomTo(newZoom);
1087 }
1088
1089 void DisplayModel::rotateBy(int newRotation)
1090 {
1091 normalizeRotation(&newRotation);
1092 assert(0 != newRotation);
1093 if (0 == newRotation)
1094 return;
1095 assert(validRotation(newRotation));
1096 if (!validRotation(newRotation))
1097 return;
1098
1099 newRotation += rotation();
1100 normalizeRotation(&newRotation);
1101 assert(validRotation(newRotation));
1102 if (!validRotation(newRotation))
1103 return;
1104
1105 int currPageNo = currentPageNo();
1106 relayout(zoomVirtual(), newRotation);
1107 goToPage(currPageNo, 0);
1108 }
1109
1110 void DisplayModel::showNormalCursor(void)
1111 {
1112 SetCursor(LoadCursor(NULL, IDC_ARROW));
1113 }
1114
1115 void DisplayModel::showBusyCursor(void)
1116 {
1117 // TODO: what is the right cursor?
1118 // can I set it per-window only?
1119 SetCursor(LoadCursor(NULL, IDC_ARROW));
1120 }
1121
1122 void LockCache(void) {
1123 gLockMutex(&cacheMutex);
1124 }
1125
1126 void UnlockCache(void) {
1127 gUnlockMutex(&cacheMutex);
1128 }
1129
1130 static void BitmapCacheEntry_Free(BitmapCacheEntry *entry) {
1131 assert(entry);
1132 if (!entry) return;
1133 delete entry->bitmap;
1134 free((void*)entry);
1135 }
1136
1137 void BitmapCache_FreeAll(void) {
1138 LockCache();
1139 for (int i=0; i < gBitmapCacheCount; i++) {
1140 BitmapCacheEntry_Free(gBitmapCache[i]);
1141 gBitmapCache[i] = NULL;
1142 }
1143 gBitmapCacheCount = 0;
1144 UnlockCache();
1145 }
1146
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) {
1150 LockCache();
1151 bool freedSomething = false;
1152 int cacheCount = gBitmapCacheCount;
1153 int curPos = 0;
1154 for (int i = 0; i < cacheCount; i++) {
1155 BitmapCacheEntry* entry = gBitmapCache[i];
1156 bool shouldFree = !entry->dm->pageVisibleNearby(entry->pageNo);
1157 if (shouldFree) {
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;
1165 }
1166
1167 if (curPos != i)
1168 gBitmapCache[curPos] = gBitmapCache[i];
1169
1170 if (!shouldFree)
1171 ++curPos;
1172 }
1173 UnlockCache();
1174 if (freedSomething)
1175 DBG_OUT("\n");
1176 return freedSomething;
1177 }
1178
1179 static bool BitmapCache_FreePage(DisplayModel *dm, int pageNo) {
1180 LockCache();
1181 int cacheCount = gBitmapCacheCount;
1182 bool freedSomething = false;
1183 int curPos = 0;
1184 for (int i = 0; i < cacheCount; i++) {
1185 bool shouldFree = (gBitmapCache[i]->dm == dm) && (gBitmapCache[i]->pageNo == pageNo);
1186 if (shouldFree) {
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;
1194 }
1195
1196 if (curPos != i)
1197 gBitmapCache[curPos] = gBitmapCache[i];
1198
1199 if (!shouldFree)
1200 ++curPos;
1201 }
1202 UnlockCache();
1203 if (freedSomething)
1204 DBG_OUT("\n");
1205 return freedSomething;
1206 }
1207
1208 /* Free all bitmaps cached for a given <dm>. Returns TRUE if freed
1209 at least one item. */
1210 bool BitmapCache_FreeForDisplayModel(DisplayModel *dm) {
1211 LockCache();
1212 int cacheCount = gBitmapCacheCount;
1213 bool freedSomething = false;
1214 int curPos = 0;
1215 for (int i = 0; i < cacheCount; i++) {
1216 bool shouldFree = (gBitmapCache[i]->dm == dm);
1217 if (shouldFree) {
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;
1225 }
1226
1227 if (curPos != i)
1228 gBitmapCache[curPos] = gBitmapCache[i];
1229
1230 if (!shouldFree)
1231 ++curPos;
1232 }
1233 UnlockCache();
1234 if (freedSomething)
1235 DBG_OUT("\n");
1236 return freedSomething;
1237 }
1238
1239 void BitmapCache_Add(DisplayModel *dm, int pageNo, double zoomLevel, int rotation,
1240 RenderedBitmap *bitmap, double renderTime) {
1241 assert(gBitmapCacheCount <= MAX_BITMAPS_CACHED);
1242 assert(dm);
1243 assert(validRotation(rotation));
1244
1245 normalizeRotation(&rotation);
1246 DBG_OUT("BitmapCache_Add(pageNo=%d, zoomLevel=%.2f%%, rotation=%d)\n", pageNo, zoomLevel, rotation);
1247 LockCache();
1248
1249 /* It's possible there still is a cached bitmap with different zoomLevel/rotation */
1250 BitmapCache_FreePage(dm, pageNo);
1251
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 */
1255 delete bitmap;
1256 /* @note: crossing initialization of "BitmapCacheEntry* entry" not allowed in mingw */
1257 //goto UnlockAndExit;
1258 UnlockCache();
1259 return;
1260 }
1261 BitmapCacheEntry* entry = (BitmapCacheEntry*)malloc(sizeof(BitmapCacheEntry));
1262 if (!entry) {
1263 delete bitmap;
1264 goto UnlockAndExit;
1265 }
1266 entry->dm = dm;
1267 entry->pageNo = pageNo;
1268 entry->zoomLevel = zoomLevel;
1269 entry->rotation = rotation;
1270 entry->bitmap = bitmap;
1271 entry->renderTime = renderTime;
1272 gBitmapCache[gBitmapCacheCount++] = entry;
1273 UnlockAndExit:
1274 UnlockCache();
1275 }
1276
1277 BitmapCacheEntry *BitmapCache_Find(DisplayModel *dm, int pageNo) {
1278 BitmapCacheEntry* entry;
1279 LockCache();
1280 for (int i = 0; i < gBitmapCacheCount; i++) {
1281 entry = gBitmapCache[i];
1282 if ( (dm == entry->dm) && (pageNo == entry->pageNo) ) {
1283 goto Exit;
1284 }
1285 }
1286 entry = NULL;
1287 Exit:
1288 UnlockCache();
1289 return entry;
1290 }
1291
1292 BitmapCacheEntry *BitmapCache_Find(DisplayModel *dm, int pageNo, double zoomLevel, int rotation) {
1293 BitmapCacheEntry *entry;
1294 normalizeRotation(&rotation);
1295 LockCache();
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)) {
1300 goto Exit;
1301 }
1302 }
1303 entry = NULL;
1304 Exit:
1305 UnlockCache();
1306 return entry;
1307 }
1308
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))
1313 return true;
1314 return false;
1315 }
1316