UINT RenderCache::PaintTiles(HDC hdc, RectI bounds, DisplayModel *dm, int pageNo, RectI pageOnScreen, USHORT tileRes, bool renderMissing, bool *renderOutOfDateCue, bool *renderedReplacement) { int rotation = dm->Rotation(); float zoom = dm->ZoomReal(); int tileCount = 1 << tileRes; TilePosition tile = { tileRes, 0, 0 }; UINT renderTimeMin = (UINT)-1; for (tile.row = 0; tile.row < tileCount; tile.row++) { for (tile.col = 0; tile.col < tileCount; tile.col++) { RectI tileOnScreen = GetTileOnScreen(dm->engine, pageNo, rotation, zoom, tile, pageOnScreen); tileOnScreen = pageOnScreen.Intersect(tileOnScreen); RectI isect = bounds.Intersect(tileOnScreen); if (!isect.IsEmpty()) { UINT renderTime = PaintTile(hdc, isect, dm, pageNo, tile, tileOnScreen, renderMissing, renderOutOfDateCue, renderedReplacement); renderTimeMin = min(renderTime, renderTimeMin); } } } return renderTimeMin; }
Vec<SelectionOnPage> *SelectionOnPage::FromRectangle(DisplayModel *dm, RectI rect) { Vec<SelectionOnPage> *sel = new Vec<SelectionOnPage>(); for (int pageNo = dm->PageCount(); pageNo >= 1; --pageNo) { PageInfo *pageInfo = dm->GetPageInfo(pageNo); assert(!pageInfo || 0.0 == pageInfo->visibleRatio || pageInfo->shown); if (!pageInfo || !pageInfo->shown) continue; RectI intersect = rect.Intersect(pageInfo->pageOnScreen); if (intersect.IsEmpty()) continue; /* selection intersects with a page <pageNo> on the screen */ RectD isectD = dm->CvtFromScreen(intersect, pageNo); sel->Append(SelectionOnPage(pageNo, &isectD)); } sel->Reverse(); if (sel->Count() == 0) { delete sel; return NULL; } return sel; }
UINT RenderCache::Paint(HDC hdc, RectI bounds, DisplayModel *dm, int pageNo, PageInfo *pageInfo, bool *renderOutOfDateCue) { assert(pageInfo->shown && 0.0 != pageInfo->visibleRatio); int rotation = dm->Rotation(); float zoom = dm->ZoomReal(); USHORT targetRes = GetTileRes(dm, pageNo); USHORT maxRes = GetMaxTileRes(dm, pageNo, rotation); if (maxRes < targetRes) maxRes = targetRes; Vec<TilePosition> queue; queue.Append(TilePosition(0, 0, 0)); UINT renderDelayMin = RENDER_DELAY_UNDEFINED; bool neededScaling = false; while (queue.Count() > 0) { TilePosition tile = queue.At(0); queue.RemoveAt(0); RectI tileOnScreen = GetTileOnScreen(dm->engine, pageNo, rotation, zoom, tile, pageInfo->pageOnScreen); tileOnScreen = pageInfo->pageOnScreen.Intersect(tileOnScreen); RectI isect = bounds.Intersect(tileOnScreen); if (isect.IsEmpty()) continue; bool isTargetRes = tile.res == targetRes; UINT renderDelay = PaintTile(hdc, isect, dm, pageNo, tile, tileOnScreen, isTargetRes, renderOutOfDateCue, isTargetRes ? &neededScaling : NULL); if (!(isTargetRes && 0 == renderDelay) && tile.res < maxRes) { queue.Append(TilePosition(tile.res + 1, tile.row * 2, tile.col * 2)); queue.Append(TilePosition(tile.res + 1, tile.row * 2, tile.col * 2 + 1)); queue.Append(TilePosition(tile.res + 1, tile.row * 2 + 1, tile.col * 2)); queue.Append(TilePosition(tile.res + 1, tile.row * 2 + 1, tile.col * 2 + 1)); } if (isTargetRes && renderDelay > 0) neededScaling = true; renderDelayMin = min(renderDelay, renderDelayMin); // paint tiles from left to right from top to bottom if (tile.res > 0 && queue.Count() > 0 && tile.res < queue.At(0).res) queue.Sort(cmpTilePosition); } #ifdef CONSERVE_MEMORY if (!neededScaling) { if (renderOutOfDateCue) *renderOutOfDateCue = false; // free tiles with different resolution TilePosition tile(targetRes, (USHORT)-1, 0); FreePage(dm, pageNo, &tile); } FreeNotVisible(); #endif return renderDelayMin; }
static BaseEngine *ps2pdf(const WCHAR *fileName) { // TODO: read from gswin32c's stdout instead of using a TEMP file ScopedMem<WCHAR> shortPath(path::ShortPath(fileName)); ScopedMem<WCHAR> tmpFile(path::GetTempPath(L"PsE")); ScopedFile tmpFileScope(tmpFile); ScopedMem<WCHAR> gswin32c(GetGhostscriptPath()); if (!shortPath || !tmpFile || !gswin32c) return NULL; // try to help Ghostscript determine the intended page size ScopedMem<WCHAR> psSetup; RectI page = ExtractDSCPageSize(fileName); if (!page.IsEmpty()) psSetup.Set(str::Format(L" << /PageSize [%i %i] >> setpagedevice", page.dx, page.dy)); ScopedMem<WCHAR> cmdLine(str::Format( L"\"%s\" -q -dSAFER -dNOPAUSE -dBATCH -dEPSCrop -sOutputFile=\"%s\" -sDEVICE=pdfwrite -c \".setpdfwrite%s\" -f \"%s\"", gswin32c, tmpFile, psSetup ? psSetup : L"", shortPath)); fprintf(stderr, "- %s:%d: using '%ls' for creating '%%TEMP%%\\%ls'\n", path::GetBaseName(__FILE__), __LINE__, gswin32c.Get(), path::GetBaseName(tmpFile)); // TODO: the PS-to-PDF conversion can hang the UI for several seconds HANDLE process = LaunchProcess(cmdLine, NULL, CREATE_NO_WINDOW); if (!process) return NULL; DWORD timeout = 10000; #ifdef DEBUG // allow to disable the timeout for debugging purposes if (GetEnvironmentVariable(L"SUMATRAPDF_NO_GHOSTSCRIPT_TIMEOUT", NULL, 0)) timeout = INFINITE; #endif DWORD exitCode = EXIT_FAILURE; WaitForSingleObject(process, timeout); GetExitCodeProcess(process, &exitCode); TerminateProcess(process, 1); CloseHandle(process); if (exitCode != EXIT_SUCCESS) return NULL; size_t len; ScopedMem<char> pdfData(file::ReadAll(tmpFile, &len)); if (!pdfData) return NULL; ScopedComPtr<IStream> stream(CreateStreamFromData(pdfData, len)); if (!stream) return NULL; return PdfEngine::CreateFromStream(stream); }
void ZoomToSelection(WindowInfo *win, float factor, bool scrollToFit, bool relative) { if (!win->IsDocLoaded()) return; PointI pt; bool zoomToPt = win->showSelection && win->selectionOnPage; // either scroll towards the center of the current selection if (zoomToPt) { RectI selRect; for (size_t i = 0; i < win->selectionOnPage->Count(); i++) { selRect = selRect.Union(win->selectionOnPage->At(i).GetRect(win->dm)); } ClientRect rc(win->hwndCanvas); pt.x = 2 * selRect.x + selRect.dx - rc.dx / 2; pt.y = 2 * selRect.y + selRect.dy - rc.dy / 2; pt.x = limitValue(pt.x, selRect.x, selRect.x + selRect.dx); pt.y = limitValue(pt.y, selRect.y, selRect.y + selRect.dy); int pageNo = win->dm->GetPageNoByPoint(pt); if (!win->dm->ValidPageNo(pageNo) || !win->dm->PageVisible(pageNo)) zoomToPt = false; } // or towards the top-left-most part of the first visible page else { int page = win->dm->FirstVisiblePageNo(); PageInfo *pageInfo = win->dm->GetPageInfo(page); if (pageInfo) { RectI visible = pageInfo->pageOnScreen.Intersect(win->canvasRc); pt = visible.TL(); int pageNo = win->dm->GetPageNoByPoint(pt); if (!visible.IsEmpty() && win->dm->ValidPageNo(pageNo) && win->dm->PageVisible(pageNo)) zoomToPt = true; } } if (!relative && (ZOOM_FIT_PAGE == factor || ZOOM_FIT_CONTENT == factor) && scrollToFit) zoomToPt = false; if (relative) win->dm->ZoomBy(factor, zoomToPt ? &pt : NULL); else win->dm->ZoomTo(factor, zoomToPt ? &pt : NULL); UpdateToolbarState(win); }
void ZoomToSelection(WindowInfo* win, float factor, bool scrollToFit, bool relative) { PointI pt; bool zoomToPt = false; if (win->AsFixed()) { DisplayModel* dm = win->AsFixed(); // when not zooming to fit (which contradicts zooming to a specific point), ... if (!relative && (ZOOM_FIT_PAGE == factor || ZOOM_FIT_CONTENT == factor) && scrollToFit) { zoomToPt = false; } // either scroll towards the center of the current selection (if there is any) ... else if (win->showSelection && win->currentTab->selectionOnPage) { RectI selRect; for (SelectionOnPage& sel : *win->currentTab->selectionOnPage) { selRect = selRect.Union(sel.GetRect(dm)); } ClientRect rc(win->hwndCanvas); pt.x = 2 * selRect.x + selRect.dx - rc.dx / 2; pt.y = 2 * selRect.y + selRect.dy - rc.dy / 2; pt.x = limitValue(pt.x, selRect.x, selRect.x + selRect.dx); pt.y = limitValue(pt.y, selRect.y, selRect.y + selRect.dy); int pageNo = dm->GetPageNoByPoint(pt); zoomToPt = dm->ValidPageNo(pageNo) && dm->PageVisible(pageNo); } // or towards the top-left-most part of the first visible page else { int page = dm->FirstVisiblePageNo(); PageInfo* pageInfo = dm->GetPageInfo(page); if (pageInfo) { RectI visible = pageInfo->pageOnScreen.Intersect(win->canvasRc); pt = visible.TL(); int pageNo = dm->GetPageNoByPoint(pt); zoomToPt = !visible.IsEmpty() && dm->ValidPageNo(pageNo) && dm->PageVisible(pageNo); } } } win->ctrl->SetZoomVirtual(factor * (relative ? win->ctrl->GetZoomVirtual(true) : 1), zoomToPt ? &pt : nullptr); UpdateToolbarState(win); }
void PaintTransparentRectangles(HDC hdc, RectI screenRc, Vec<RectI>& rects, COLORREF selectionColor, BYTE alpha, int margin) { using namespace Gdiplus; // create path from rectangles GraphicsPath path(FillModeWinding); screenRc.Inflate(margin, margin); for (size_t i = 0; i < rects.Count(); i++) { RectI rc = rects.At(i).Intersect(screenRc); if (!rc.IsEmpty()) path.AddRectangle(Rect(rc.x, rc.y, rc.dx, rc.dy)); } // fill path (and draw optional outline margin) Graphics gs(hdc); Color c(alpha, GetRValue(selectionColor), GetGValue(selectionColor), GetBValue(selectionColor)); gs.FillPath(&SolidBrush(c), &path); if (margin) { path.Outline(NULL, 0.2f); gs.DrawPath(&Pen(Color(alpha, 0, 0, 0), (REAL)margin), &path); } }
DoubleBuffer::DoubleBuffer(HWND hwnd, RectI rect) : hTarget(hwnd), rect(rect), hdcBuffer(NULL), doubleBuffer(NULL) { hdcCanvas = ::GetDC(hwnd); if (rect.IsEmpty()) return; doubleBuffer = CreateCompatibleBitmap(hdcCanvas, rect.dx, rect.dy); if (!doubleBuffer) return; hdcBuffer = CreateCompatibleDC(hdcCanvas); if (!hdcBuffer) return; if (rect.x != 0 || rect.y != 0) { SetGraphicsMode(hdcBuffer, GM_ADVANCED); XFORM ctm = { 1.0, 0, 0, 1.0, (float)-rect.x, (float)-rect.y }; SetWorldTransform(hdcBuffer, &ctm); } DeleteObject(SelectObject(hdcBuffer, doubleBuffer)); }
static void GeomTest() { PointD ptD(12.4, -13.6); utassert(ptD.x == 12.4 && ptD.y == -13.6); PointI ptI = ptD.ToInt(); utassert(ptI.x == 12 && ptI.y == -14); ptD = ptI.Convert<double>(); utassert(PointD(12, -14) == ptD); utassert(PointD(12.4, -13.6) != ptD); SizeD szD(7.7, -3.3); utassert(szD.dx == 7.7 && szD.dy == -3.3); SizeI szI = szD.ToInt(); utassert(szI.dx == 8 && szI.dy == -3); szD = szI.Convert<double>(); utassert(SizeD(8, -3) == szD); utassert(!szD.IsEmpty() && !szI.IsEmpty()); utassert(SizeI().IsEmpty() && SizeD().IsEmpty()); struct SRIData { int x1s, x1e, y1s, y1e; int x2s, x2e, y2s, y2e; bool intersect; int i_xs, i_xe, i_ys, i_ye; int u_xs, u_xe, u_ys, u_ye; } testData[] = { { 0,10, 0,10, 0,10, 0,10, true, 0,10, 0,10, 0,10, 0,10 }, /* complete intersect */ { 0,10, 0,10, 20,30,20,30, false, 0, 0, 0, 0, 0,30, 0,30 }, /* no intersect */ { 0,10, 0,10, 5,15, 0,10, true, 5,10, 0,10, 0,15, 0,10 }, /* { | } | */ { 0,10, 0,10, 5, 7, 0,10, true, 5, 7, 0,10, 0,10, 0,10 }, /* { | | } */ { 0,10, 0,10, 5, 7, 5, 7, true, 5, 7, 5, 7, 0,10, 0,10 }, { 0,10, 0,10, 5, 15,5,15, true, 5,10, 5,10, 0,15, 0,15 }, }; for (size_t i = 0; i < dimof(testData); i++) { struct SRIData *curr = &testData[i]; RectI rx1(curr->x1s, curr->y1s, curr->x1e - curr->x1s, curr->y1e - curr->y1s); RectI rx2 = RectI::FromXY(curr->x2s, curr->y2s, curr->x2e, curr->y2e); RectI isect = rx1.Intersect(rx2); if (curr->intersect) { utassert(!isect.IsEmpty()); utassert(isect.x == curr->i_xs && isect.y == curr->i_ys); utassert(isect.x + isect.dx == curr->i_xe && isect.y + isect.dy == curr->i_ye); } else { utassert(isect.IsEmpty()); } RectI urect = rx1.Union(rx2); utassert(urect.x == curr->u_xs && urect.y == curr->u_ys); utassert(urect.x + urect.dx == curr->u_xe && urect.y + urect.dy == curr->u_ye); /* if we swap rectangles, the results should be the same */ std::swap(rx1, rx2); isect = rx1.Intersect(rx2); if (curr->intersect) { utassert(!isect.IsEmpty()); utassert(isect.x == curr->i_xs && isect.y == curr->i_ys); utassert(isect.x + isect.dx == curr->i_xe && isect.y + isect.dy == curr->i_ye); } else { utassert(isect.IsEmpty()); } urect = rx1.Union(rx2); utassert(RectI::FromXY(curr->u_xs, curr->u_ys, curr->u_xe, curr->u_ye) == urect); utassert(!rx1.Contains(PointI(-2, -2))); utassert(rx1.Contains(rx1.TL())); utassert(!rx1.Contains(PointI(rx1.x, INT_MAX))); utassert(!rx1.Contains(PointI(INT_MIN, rx1.y))); } }
void OpenMobiInWindow(Doc doc, SumatraWindow& winToReplace) { const TCHAR *fullPath = doc.GetFilePath(); DisplayState *ds = gFileHistory.Find(fullPath); if (gGlobalPrefs.rememberOpenedFiles) { ds = gFileHistory.MarkFileLoaded(fullPath); if (gGlobalPrefs.showStartPage && ds) { // TODO: do it on a background thread? CreateThumbnailForDoc(doc, *ds); } SavePrefs(); } int startReparseIdx = -1; if (ds) startReparseIdx = ds->reparseIdx; // Add the file also to Windows' recently used documents (this doesn't // happen automatically on drag&drop, reopening from history, etc.) if (HasPermission(Perm_DiskAccess) && !gPluginMode) SHAddToRecentDocs(SHARD_PATH, fullPath); ScopedMem<TCHAR> winTitle(str::Format(_T("%s - %s"), path::GetBaseName(fullPath), SUMATRA_WINDOW_TITLE)); if (winToReplace.AsEbookWindow()) { EbookWindow *mw = winToReplace.AsEbookWindow(); CrashIf(!mw); mw->ebookController->SetDoc(doc); win::SetText(mw->hwndFrame, winTitle); // TODO: if we have window position/last position for this file, restore it return; } RectI windowPos = gGlobalPrefs.windowPos; if (!windowPos.IsEmpty()) EnsureAreaVisibility(windowPos); else windowPos = GetDefaultWindowPos(); if (ds && !ds->windowPos.IsEmpty()) { // Make sure it doesn't have a position like outside of the screen etc. windowPos = ShiftRectToWorkArea(ds->windowPos); } WindowInfo *winInfo = winToReplace.AsWindowInfo(); bool wasMaximized = false; if (winInfo && winInfo->hwndFrame) wasMaximized = IsZoomed(winInfo->hwndFrame); CloseDocumentAndDeleteWindowInfo(winInfo); HWND hwnd = CreateWindow( MOBI_FRAME_CLASS_NAME, SUMATRA_WINDOW_TITLE, WS_OVERLAPPEDWINDOW, windowPos.x, windowPos.y, windowPos.dx, windowPos.dy, NULL, NULL, ghinst, NULL); if (!hwnd) return; if (HasPermission(Perm_DiskAccess) && !gPluginMode) DragAcceptFiles(hwnd, TRUE); if (Touch::SupportsGestures()) { GESTURECONFIG gc = { 0, GC_ALLGESTURES, 0 }; Touch::SetGestureConfig(hwnd, 0, 1, &gc, sizeof(GESTURECONFIG)); } EbookWindow *win = new EbookWindow(); win->ebookControls = CreateEbookControls(hwnd); win->hwndWrapper = win->ebookControls->mainWnd; win->ebookController = new EbookController(win->ebookControls); win->hwndFrame = hwnd; gEbookWindows.Append(win); win::SetText(win->hwndFrame, winTitle); SetMenu(hwnd, BuildMenu(win)); ShowWindow(hwnd, wasMaximized ? SW_SHOWMAXIMIZED : SW_SHOW); win->ebookController->SetDoc(doc, startReparseIdx); }
void OpenMobiInWindow(Doc doc, SumatraWindow& winToReplace) { const WCHAR *fullPath = doc.GetFilePath(); DisplayState *ds = gFileHistory.Find(fullPath); if (doc.IsNone()) { // TODO: a hack. In LoadDocumentOld(), if current window IsAboutWindow(), // we set loadedFilePath to prevent a crash if multiple ebook files are // dropped on about window. We need to undo that in case of failure // or else the window will be stuck in an invalid state (not about window // but not a document window either) WindowInfo *w = winToReplace.AsWindowInfo(); if (str::Eq(w->loadedFilePath, doc.GetFilePath())) { free(w->loadedFilePath); w->loadedFilePath = NULL; // this is now about window. We don't want to show it if we're already // showing other windows (the scenario: dragging ebook file on a window // showing a document, we create invisible window for this document and // we don't want to show empty window if loading fails if (gEbookWindows.Count() > 0 || gWindows.Count() > 1) { CloseWindow(w, false); if (gFileHistory.MarkFileInexistent(fullPath)) prefs::Save(); // TODO: notify the use that loading failed (e.g. show a notification) return; } } // TODO: notify the user that loading failed (e.g. show a notification) // TODO: this is not a great solution. In case of opening // file from cmd-line, we create a window but don't show it // if loading afils, we have to show the window. If window // is already visible, this is a no-op ShowWindow(winToReplace.HwndFrame(), SW_SHOW); if (gFileHistory.MarkFileInexistent(fullPath)) prefs::Save(); return; } if (gGlobalPrefs->rememberOpenedFiles) { ds = gFileHistory.MarkFileLoaded(fullPath); if (gGlobalPrefs->showStartPage) { // TODO: do it on a background thread? CreateThumbnailForDoc(doc, *ds); } prefs::Save(); } int startReparseIdx = -1; if (ds && gGlobalPrefs->rememberStatePerDocument && !ds->useDefaultState) startReparseIdx = ds->reparseIdx; // Add the file also to Windows' recently used documents (this doesn't // happen automatically on drag&drop, reopening from history, etc.) if (HasPermission(Perm_DiskAccess) && !gPluginMode) SHAddToRecentDocs(SHARD_PATH, fullPath); ScopedMem<WCHAR> winTitle(str::Format(L"%s - %s", path::GetBaseName(fullPath), SUMATRA_WINDOW_TITLE)); if (winToReplace.AsEbookWindow()) { EbookWindow *mw = winToReplace.AsEbookWindow(); CrashIf(!mw); mw->ebookController->SetDoc(doc); win::SetText(mw->hwndFrame, winTitle); // TODO: if we have window position/last position for this file, restore it return; } RectI windowPos = gGlobalPrefs->windowPos; if (!windowPos.IsEmpty()) EnsureAreaVisibility(windowPos); else windowPos = GetDefaultWindowPos(); if (ds && !ds->windowPos.IsEmpty()) { // Make sure it doesn't have a position like outside of the screen etc. windowPos = ShiftRectToWorkArea(ds->windowPos); } WindowInfo *winInfo = winToReplace.AsWindowInfo(); bool wasMaximized = false; if (winInfo && winInfo->hwndFrame) { wasMaximized = IsZoomed(winInfo->hwndFrame); // hide the window instead of destroying it so that // windows doesn't set a window from a different // process as foreground window instead of the once // created below ShowWindow(winInfo->hwndFrame, SW_HIDE); } HWND hwnd = CreateWindow( MOBI_FRAME_CLASS_NAME, SUMATRA_WINDOW_TITLE, WS_OVERLAPPEDWINDOW, windowPos.x, windowPos.y, windowPos.dx, windowPos.dy, NULL, NULL, ghinst, NULL); if (!hwnd) { CloseDocumentAndDeleteWindowInfo(winInfo); return; } if (HasPermission(Perm_DiskAccess) && !gPluginMode) DragAcceptFiles(hwnd, TRUE); if (Touch::SupportsGestures()) { GESTURECONFIG gc = { 0, GC_ALLGESTURES, 0 }; Touch::SetGestureConfig(hwnd, 0, 1, &gc, sizeof(GESTURECONFIG)); } EbookWindow *win = new EbookWindow(); win->ebookControls = CreateEbookControls(hwnd); win->hwndWrapper = win->ebookControls->mainWnd; win->ebookController = new EbookController(win->ebookControls); win->hwndFrame = hwnd; gEbookWindows.Append(win); win::SetText(win->hwndFrame, winTitle); SetMenu(hwnd, BuildMenu(win)); ShowWindow(hwnd, wasMaximized ? SW_SHOWMAXIMIZED : SW_SHOW); win->ebookController->SetDoc(doc, startReparseIdx); CloseDocumentAndDeleteWindowInfo(winInfo); }
UINT RenderCache::Paint(HDC hdc, RectI bounds, DisplayModel* dm, int pageNo, PageInfo* pageInfo, bool* renderOutOfDateCue) { AssertCrash(pageInfo->shown && 0.0 != pageInfo->visibleRatio); if (!dm->ShouldCacheRendering(pageNo)) { int rotation = dm->GetRotation(); float zoom = dm->GetZoomReal(pageNo); bounds = pageInfo->pageOnScreen.Intersect(bounds); RectD area = bounds.Convert<double>(); area.Offset(-pageInfo->pageOnScreen.x, -pageInfo->pageOnScreen.y); area = dm->GetEngine()->Transform(area, pageNo, zoom, rotation, true); RenderedBitmap* bmp = dm->GetEngine()->RenderBitmap(pageNo, zoom, rotation, &area); bool success = bmp && bmp->GetBitmap() && bmp->StretchDIBits(hdc, bounds); delete bmp; return success ? 0 : RENDER_DELAY_FAILED; } int rotation = dm->GetRotation(); float zoom = dm->GetZoomReal(); USHORT targetRes = GetTileRes(dm, pageNo); USHORT maxRes = GetMaxTileRes(dm, pageNo, rotation); if (maxRes < targetRes) maxRes = targetRes; Vec<TilePosition> queue; queue.Append(TilePosition(0, 0, 0)); UINT renderDelayMin = RENDER_DELAY_UNDEFINED; bool neededScaling = false; while (queue.size() > 0) { TilePosition tile = queue.PopAt(0); RectI tileOnScreen = GetTileOnScreen(dm->GetEngine(), pageNo, rotation, zoom, tile, pageInfo->pageOnScreen); if (tileOnScreen.IsEmpty()) { // display an error message when only empty tiles should be drawn (i.e. on page loading errors) renderDelayMin = std::min(RENDER_DELAY_FAILED, renderDelayMin); continue; } tileOnScreen = pageInfo->pageOnScreen.Intersect(tileOnScreen); RectI isect = bounds.Intersect(tileOnScreen); if (isect.IsEmpty()) continue; bool isTargetRes = tile.res == targetRes; UINT renderDelay = PaintTile(hdc, isect, dm, pageNo, tile, tileOnScreen, isTargetRes, renderOutOfDateCue, isTargetRes ? &neededScaling : nullptr); if (!(isTargetRes && 0 == renderDelay) && tile.res < maxRes) { queue.Append(TilePosition(tile.res + 1, tile.row * 2, tile.col * 2)); queue.Append(TilePosition(tile.res + 1, tile.row * 2, tile.col * 2 + 1)); queue.Append(TilePosition(tile.res + 1, tile.row * 2 + 1, tile.col * 2)); queue.Append(TilePosition(tile.res + 1, tile.row * 2 + 1, tile.col * 2 + 1)); } if (isTargetRes && renderDelay > 0) neededScaling = true; renderDelayMin = std::min(renderDelay, renderDelayMin); // paint tiles from left to right from top to bottom if (tile.res > 0 && queue.size() > 0 && tile.res < queue.at(0).res) queue.Sort(cmpTilePosition); } #ifdef CONSERVE_MEMORY if (!neededScaling) { if (renderOutOfDateCue) *renderOutOfDateCue = false; // free tiles with different resolution TilePosition tile(targetRes, (USHORT)-1, 0); FreePage(dm, pageNo, &tile); } FreeNotVisible(); #endif return renderDelayMin; }