int SumatraUIAutomationTextRange::GetPageGlyphCount(int pageNum) { AssertCrash(document->IsDocumentLoaded()); AssertCrash(pageNum > 0); int pageLen; document->GetDM()->textCache->GetData(pageNum, &pageLen); return pageLen; }
bool RenderCache::Render(DisplayModel* dm, int pageNo, int rotation, float zoom, TilePosition* tile, RectD* pageRect, RenderingCallback* renderCb) { AssertCrash(dm); if (!dm || dm->dontRenderFlag) return false; AssertCrash(tile || pageRect && renderCb); if (!tile && !(pageRect && renderCb)) return false; ScopedCritSec scope(&requestAccess); PageRenderRequest* newRequest; /* add request to the queue */ if (requestCount == MAX_PAGE_REQUESTS) { /* queue is full -> remove the oldest items on the queue */ if (requests[0].renderCb) requests[0].renderCb->Callback(); memmove(&(requests[0]), &(requests[1]), sizeof(PageRenderRequest) * (MAX_PAGE_REQUESTS - 1)); newRequest = &(requests[MAX_PAGE_REQUESTS - 1]); } else { newRequest = &(requests[requestCount]); requestCount++; } AssertCrash(requestCount <= MAX_PAGE_REQUESTS); newRequest->dm = dm; newRequest->pageNo = pageNo; newRequest->rotation = rotation; newRequest->zoom = zoom; if (tile) { newRequest->pageRect = GetTileRectUser(dm->GetEngine(), pageNo, rotation, zoom, *tile); newRequest->tile = *tile; } else if (pageRect) { newRequest->pageRect = *pageRect; // can't cache bitmaps that aren't for a given tile AssertCrash(renderCb); } else AssertCrash(0); newRequest->abort = false; newRequest->abortCookie = nullptr; newRequest->timestamp = GetTickCount(); newRequest->renderCb = renderCb; SetEvent(startRendering); return true; }
bool RenderCache::GetNextRequest(PageRenderRequest* req) { ScopedCritSec scope(&requestAccess); if (requestCount == 0) return false; AssertCrash(requestCount > 0); AssertCrash(requestCount <= MAX_PAGE_REQUESTS); requestCount--; *req = requests[requestCount]; curReq = req; AssertCrash(requestCount >= 0); AssertCrash(!req->abort); return true; }
int Pdfsync::DocToSource(UINT pageNo, PointI pt, AutoFreeW& filename, UINT* line, UINT* col) { if (IsIndexDiscarded()) if (RebuildIndex() != PDFSYNCERR_SUCCESS) return PDFSYNCERR_SYNCFILE_CANNOT_BE_OPENED; // find the entry in the index corresponding to this page if (pageNo <= 0 || pageNo >= sheetIndex.size() || pageNo > (UINT)engine->PageCount()) return PDFSYNCERR_INVALID_PAGE_NUMBER; // PdfSync coordinates are y-inversed RectI mbox = engine->PageMediabox(pageNo).Round(); pt.y = mbox.dy - pt.y; // distance to the closest pdf location (in the range <PDFSYNC_EPSILON_SQUARE) UINT closest_xydist = UINT_MAX; UINT selected_record = UINT_MAX; // If no record is found within a distance^2 of PDFSYNC_EPSILON_SQUARE // (selected_record == -1) then we pick up the record that is closest // vertically to the hit-point. UINT closest_ydist = UINT_MAX; // vertical distance between the hit point and the vertically-closest record UINT closest_xdist = UINT_MAX; // horizontal distance between the hit point and the vertically-closest record UINT closest_ydist_record = UINT_MAX; // vertically-closest record // read all the sections of 'p' declarations for this pdf sheet for (size_t i = sheetIndex.at(pageNo); i < points.size() && points.at(i).page == pageNo; i++) { // check whether it is closer than the closest point found so far UINT dx = abs(pt.x - (int)SYNC_TO_PDF_COORDINATE(points.at(i).x)); UINT dy = abs(pt.y - (int)SYNC_TO_PDF_COORDINATE(points.at(i).y)); UINT dist = dx * dx + dy * dy; if (dist < PDFSYNC_EPSILON_SQUARE && dist < closest_xydist) { selected_record = points.at(i).record; closest_xydist = dist; } else if ((closest_xydist == UINT_MAX) && dy < PDFSYNC_EPSILON_Y && (dy < closest_ydist || (dy == closest_ydist && dx < closest_xdist))) { closest_ydist_record = points.at(i).record; closest_ydist = dy; closest_xdist = dx; } } if (selected_record == UINT_MAX) selected_record = closest_ydist_record; if (selected_record == UINT_MAX) return PDFSYNCERR_NO_SYNC_AT_LOCATION; // no record was found close enough to the hit point // We have a record number, we need to find its declaration ('l ...') in the syncfile PdfsyncLine cmp; cmp.record = selected_record; PdfsyncLine* found = (PdfsyncLine*)bsearch(&cmp, lines.LendData(), lines.size(), sizeof(PdfsyncLine), cmpLineRecords); AssertCrash(found); if (!found) return PDFSYNCERR_NO_SYNC_AT_LOCATION; filename.SetCopy(srcfiles.at(found->file)); *line = found->line; *col = found->column; return PDFSYNCERR_SUCCESS; }
void SumatraUIAutomationProvider::OnDocumentLoad(DisplayModel *dm) { AssertCrash(!document); document = new SumatraUIAutomationDocumentProvider(canvasHwnd, this); document->LoadDocument(dm); uia::RaiseStructureChangedEvent(this, StructureChangeType_ChildrenInvalidated, nullptr, 0); }
void RenderCache::DropCacheEntry(BitmapCacheEntry* entry) { ScopedCritSec scope(&cacheAccess); AssertCrash(entry); if (!entry) return; if (0 == --entry->refs) { delete entry; } }
static void BenchFile(const WCHAR* filePath, const WCHAR* pagesSpec) { if (!file::Exists(filePath)) { return; } // ad-hoc: if enabled times layout instead of rendering and does layout // using all text rendering methods, so that we can compare and find // docs that take a long time to load if (Doc::IsSupportedFile(filePath) && !gGlobalPrefs->ebookUI.useFixedPageUI) { BenchEbookLayout(filePath); return; } if (ChmModel::IsSupportedFile(filePath) && !gGlobalPrefs->chmUI.useFixedPageUI) { BenchChmLoadOnly(filePath); return; } Timer total; logbench(L"Starting: %s", filePath); Timer t; BaseEngine* engine = EngineManager::CreateEngine(filePath); if (!engine) { logbench(L"Error: failed to load %s", filePath); return; } double timeMs = t.Stop(); logbench(L"load: %.2f ms", timeMs); int pages = engine->PageCount(); logbench(L"page count: %d", pages); if (nullptr == pagesSpec) { for (int i = 1; i <= pages; i++) { BenchLoadRender(engine, i); } } AssertCrash(!pagesSpec || IsBenchPagesInfo(pagesSpec)); Vec<PageRange> ranges; if (ParsePageRanges(pagesSpec, ranges)) { for (size_t i = 0; i < ranges.size(); i++) { for (int j = ranges.at(i).start; j <= ranges.at(i).end; j++) { if (1 <= j && j <= pages) BenchLoadRender(engine, j); } } } delete engine; total.Stop(); logbench(L"Finished (in %.2f ms): %s", total.GetTimeInMs(), filePath); }
bool Load(const WCHAR *fileName) { AssertCrash(!this->fileName && !pdfEngine); if (!fileName) return false; this->fileName.Set(str::Dup(fileName)); if (file::StartsWith(fileName, "\x1F\x8B")) pdfEngine = psgz2pdf(fileName); else pdfEngine = ps2pdf(fileName); return pdfEngine != NULL; }
bool DirFileProvider::OpenDir(const WCHAR* dirPath) { AssertCrash(filesToOpen.size() == 0); bool hasFiles = CollectStressTestSupportedFilesFromDirectory(dirPath, fileFilter, filesToOpen); filesToOpen.SortNatural(); AutoFreeW pattern(str::Format(L"%s\\*", dirPath)); bool hasSubDirs = CollectPathsFromDirectory(pattern, dirsToVisit, true); return hasFiles || hasSubDirs; }
static TxtNode *TxtNodeFromToken(Allocator *allocator, TokenVal& tok, TxtNodeType nodeType) { AssertCrash((TextNode == nodeType) || (StructNode == nodeType)); TxtNode *node = AllocTxtNode(allocator, nodeType); node->lineStart = tok.lineStart; node->valStart = tok.valStart; node->valEnd = tok.valEnd; node->keyStart = tok.keyStart; node->keyEnd = tok.keyEnd; return node; }
/* appends the next unquoted argument and returns the position after it */ static const WCHAR *ParseUnquoted(const WCHAR *arg, WStrVec *out) { AssertCrash(arg && *arg && ('"' != *arg) && !str::IsWs(*arg)); const WCHAR *next; // contrary to http://msdn.microsoft.com/en-us/library/17w5ykft.aspx // we don't treat quotation marks or backslashes in non-quoted // arguments in any special way for (next = arg; *next && !str::IsWs(*next); next++); out->Append(str::DupN(arg, next - arg)); return next; }
/* Render a bitmap for page <pageNo> in <dm>. */ void RenderCache::RequestRendering(DisplayModel* dm, int pageNo, TilePosition tile, bool clearQueueForPage) { ScopedCritSec scope(&requestAccess); AssertCrash(dm); if (!dm || dm->dontRenderFlag) return; int rotation = NormalizeRotation(dm->GetRotation()); float zoom = dm->GetZoomReal(pageNo); if (curReq && (curReq->pageNo == pageNo) && (curReq->dm == dm) && (curReq->tile == tile)) { if ((curReq->zoom == zoom) && (curReq->rotation == rotation)) { /* we're already rendering exactly the same page */ return; } /* Currently rendered page is for the same page but with different zoom or rotation, so abort it */ AbortCurrentRequest(); } // clear requests for tiles of different resolution and invisible tiles if (clearQueueForPage) ClearQueueForDisplayModel(dm, pageNo, &tile); for (int i = 0; i < requestCount; i++) { PageRenderRequest* req = &(requests[i]); if ((req->pageNo == pageNo) && (req->dm == dm) && (req->tile == tile)) { if ((req->zoom == zoom) && (req->rotation == rotation)) { /* Request with exactly the same parameters already queued for rendering. Move it to the top of the queue so that it'll be rendered faster. */ PageRenderRequest tmp; tmp = requests[requestCount - 1]; requests[requestCount - 1] = *req; *req = tmp; } else { /* There was a request queued for the same page but with different zoom or rotation, so only replace this request */ req->zoom = zoom; req->rotation = rotation; } return; } } if (Exists(dm, pageNo, rotation, zoom, &tile)) { /* This page has already been rendered in the correct dimensions and isn't about to be rerendered in different dimensions */ return; } Render(dm, pageNo, rotation, zoom, &tile); }
RenderCache::~RenderCache() { EnterCriticalSection(&requestAccess); EnterCriticalSection(&cacheAccess); CloseHandle(renderThread); CloseHandle(startRendering); AssertCrash(!curReq && 0 == requestCount && 0 == cacheCount); LeaveCriticalSection(&cacheAccess); DeleteCriticalSection(&cacheAccess); LeaveCriticalSection(&requestAccess); DeleteCriticalSection(&requestAccess); }
/* Caller needs to free() the result */ char *ToMultiByte(const WCHAR *txt, UINT codePage, int cchTxtLen) { AssertCrash(txt); if (!txt) return nullptr; int requiredBufSize = WideCharToMultiByte(codePage, 0, txt, cchTxtLen, nullptr, 0, nullptr, nullptr); if (0 == requiredBufSize) return nullptr; char *res = AllocArray<char>(requiredBufSize+1); if (!res) return nullptr; WideCharToMultiByte(codePage, 0, txt, cchTxtLen, res, requiredBufSize, nullptr, nullptr); return res; }
/* Caller needs to free() the result */ char *ToMultiByte(const char *src, UINT codePageSrc, UINT codePageDest) { AssertCrash(src); if (!src) return nullptr; if (codePageSrc == codePageDest) return str::Dup(src); ScopedMem<WCHAR> tmp(ToWideChar(src, codePageSrc)); if (!tmp) return nullptr; return ToMultiByte(tmp.Get(), codePageDest); }
/* Caller needs to free() the result */ WCHAR *ToWideChar(const char *src, UINT codePage, int cbSrcLen) { AssertCrash(src); if (!src) return nullptr; int requiredBufSize = MultiByteToWideChar(codePage, 0, src, cbSrcLen, nullptr, 0); if (0 == requiredBufSize) return nullptr; WCHAR *res = AllocArray<WCHAR>(requiredBufSize+1); if (!res) return nullptr; MultiByteToWideChar(codePage, 0, src, cbSrcLen, res, requiredBufSize); return res; }
void RenderCache::Add(PageRenderRequest& req, RenderedBitmap* bitmap) { ScopedCritSec scope(&cacheAccess); AssertCrash(req.dm); req.rotation = NormalizeRotation(req.rotation); AssertCrash(cacheCount <= MAX_BITMAPS_CACHED); /* It's possible there still is a cached bitmap with different zoom/rotation */ FreePage(req.dm, req.pageNo, &req.tile); if (cacheCount >= MAX_BITMAPS_CACHED) { // free an invisible page of the same DisplayModel ... for (int i = 0; i < cacheCount; i++) { if (cache[i]->dm == req.dm && !req.dm->PageVisibleNearby(cache[i]->pageNo)) { DropCacheEntry(cache[i]); cacheCount--; memmove(&cache[i], &cache[i + 1], (cacheCount - i) * sizeof(cache[0])); break; } } // ... or just the oldest cached page if (cacheCount >= MAX_BITMAPS_CACHED) { DropCacheEntry(cache[0]); cacheCount--; memmove(&cache[0], &cache[1], cacheCount * sizeof(cache[0])); } } // Copy the PageRenderRequest as it will be reused cache[cacheCount] = new BitmapCacheEntry(req.dm, req.pageNo, req.rotation, req.zoom, req.tile, bitmap); CrashIf(!cache[cacheCount]); if (!cache[cacheCount]) delete bitmap; else cacheCount++; }
RenderCache::RenderCache() : cacheCount(0), requestCount(0), maxTileSize(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)), isRemoteSession(GetSystemMetrics(SM_REMOTESESSION)) { textColor = WIN_COL_BLACK; backgroundColor = WIN_COL_WHITE; InitializeCriticalSection(&cacheAccess); InitializeCriticalSection(&requestAccess); startRendering = CreateEvent(nullptr, FALSE, FALSE, nullptr); renderThread = CreateThread(nullptr, 0, RenderCacheThread, this, 0, 0); AssertCrash(nullptr != renderThread); }
/* appends the next quoted argument and returns the position after it */ static const WCHAR *ParseQuoted(const WCHAR *arg, WStrVec *out) { AssertCrash(arg && '"' == *arg); arg++; str::Str<WCHAR> txt(str::Len(arg) / 2); const WCHAR *next; for (next = arg; *next && *next != '"'; next++) { // skip escaped quotation marks according to // http://msdn.microsoft.com/en-us/library/17w5ykft.aspx if ('\\' == *next && '"' == SkipBackslashs(next)) next++; txt.Append(*next); } out->Append(txt.StealData()); if ('"' == *next) next++; return next; }
DisplayModel* SumatraUIAutomationDocumentProvider::GetDM() { AssertCrash(IsDocumentLoaded()); AssertCrash(dm); return dm; }
/* returns the next character in '*txt' that isn't a backslash */ static const WCHAR SkipBackslashs(const WCHAR *txt) { AssertCrash(txt && '\\' == *txt); while ('\\' == *++txt); return *txt; }
SumatraUIAutomationPageProvider* SumatraUIAutomationDocumentProvider::GetLastPage() { AssertCrash(IsDocumentLoaded()); return child_last; }
int SumatraUIAutomationTextRange::GetPageCount() { AssertCrash(document->IsDocumentLoaded()); return document->GetDM()->PageCount(); }
EbookFormattingThread::EbookFormattingThread(Doc doc, HtmlFormatterArgs *args, EbookController *ctrl, int reparseIdx, ControllerCallback *cb) : doc(doc), formatterArgs(args), cb(cb), controller(ctrl), pageCount(0), reparseIdx(reparseIdx), pagesAfterReparseIdx(0) { CrashIf(reparseIdx < 0); AssertCrash(doc.IsDocLoaded() || (doc.IsNone() && (nullptr != args->htmlStr))); }
static bool PrintToDevice(const PrintData &pd, ProgressUpdateUI *progressUI = nullptr, AbortCookieManager *abortCookie = nullptr) { AssertCrash(pd.engine); if (!pd.engine) return false; AssertCrash(pd.printerName); if (!pd.printerName) return false; BaseEngine &engine = *pd.engine; ScopedMem<WCHAR> fileName; DOCINFO di = { 0 }; di.cbSize = sizeof(DOCINFO); if (gPluginMode) { fileName.Set(url::GetFileName(gPluginURL)); // fall back to a generic "filename" instead of the more confusing temporary filename di.lpszDocName = fileName ? fileName : L"filename"; } else di.lpszDocName = engine.FileName(); int current = 1, total = 0; if (pd.sel.Count() == 0) { for (size_t i = 0; i < pd.ranges.Count(); i++) { if (pd.ranges.At(i).nToPage < pd.ranges.At(i).nFromPage) total += pd.ranges.At(i).nFromPage - pd.ranges.At(i).nToPage + 1; else total += pd.ranges.At(i).nToPage - pd.ranges.At(i).nFromPage + 1; } } else { for (int pageNo = 1; pageNo <= engine.PageCount(); pageNo++) { if (!BoundSelectionOnPage(pd.sel, pageNo).IsEmpty()) total++; } } AssertCrash(total > 0); if (0 == total) return false; if (progressUI) progressUI->UpdateProgress(current, total); // cf. http://blogs.msdn.com/b/oldnewthing/archive/2012/11/09/10367057.aspx ScopeHDC hdc(CreateDC(nullptr, pd.printerName, nullptr, pd.devMode)); if (!hdc) return false; if (StartDoc(hdc, &di) <= 0) return false; // MM_TEXT: Each logical unit is mapped to one device pixel. // Positive x is to the right; positive y is down. SetMapMode(hdc, MM_TEXT); const SizeI paperSize(GetDeviceCaps(hdc, PHYSICALWIDTH), GetDeviceCaps(hdc, PHYSICALHEIGHT)); const RectI printable(GetDeviceCaps(hdc, PHYSICALOFFSETX), GetDeviceCaps(hdc, PHYSICALOFFSETY), GetDeviceCaps(hdc, HORZRES), GetDeviceCaps(hdc, VERTRES)); const float dpiFactor = std::min(GetDeviceCaps(hdc, LOGPIXELSX) / engine.GetFileDPI(), GetDeviceCaps(hdc, LOGPIXELSY) / engine.GetFileDPI()); bool bPrintPortrait = paperSize.dx < paperSize.dy; if (pd.devMode && (pd.devMode.Get()->dmFields & DM_ORIENTATION)) bPrintPortrait = DMORIENT_PORTRAIT == pd.devMode.Get()->dmOrientation; if (pd.sel.Count() > 0) { for (int pageNo = 1; pageNo <= engine.PageCount(); pageNo++) { RectD bounds = BoundSelectionOnPage(pd.sel, pageNo); if (bounds.IsEmpty()) continue; if (progressUI) progressUI->UpdateProgress(current, total); StartPage(hdc); geomutil::SizeT<float> bSize = bounds.Size().Convert<float>(); float zoom = std::min((float)printable.dx / bSize.dx, (float)printable.dy / bSize.dy); // use the correct zoom values, if the page fits otherwise // and the user didn't ask for anything else (default setting) if (PrintScaleShrink == pd.advData.scale) zoom = std::min(dpiFactor, zoom); else if (PrintScaleNone == pd.advData.scale) zoom = dpiFactor; for (size_t i = 0; i < pd.sel.Count(); i++) { if (pd.sel.At(i).pageNo != pageNo) continue; RectD *clipRegion = &pd.sel.At(i).rect; PointI offset((int)((clipRegion->x - bounds.x) * zoom), (int)((clipRegion->y - bounds.y) * zoom)); if (pd.advData.scale != PrintScaleNone) { // center the selection on the physical paper offset.x += (int)(printable.dx - bSize.dx * zoom) / 2; offset.y += (int)(printable.dy - bSize.dy * zoom) / 2; } bool ok = false; short shrink = 1; do { RenderedBitmap *bmp = engine.RenderBitmap( pd.sel.At(i).pageNo, zoom / shrink, pd.rotation, clipRegion, Target_Print, abortCookie ? &abortCookie->cookie : nullptr); if (abortCookie) abortCookie->Clear(); if (bmp && bmp->GetBitmap()) { RectI rc(offset.x, offset.y, bmp->Size().dx * shrink, bmp->Size().dy * shrink); ok = bmp->StretchDIBits(hdc, rc); } delete bmp; shrink *= 2; } while (!ok && shrink < 32 && !(progressUI && progressUI->WasCanceled())); } // TODO: abort if !ok? if (EndPage(hdc) <= 0 || progressUI && progressUI->WasCanceled()) { AbortDoc(hdc); return false; } current++; } EndDoc(hdc); return false; } // print all the pages the user requested for (size_t i = 0; i < pd.ranges.Count(); i++) { int dir = pd.ranges.At(i).nFromPage > pd.ranges.At(i).nToPage ? -1 : 1; for (DWORD pageNo = pd.ranges.At(i).nFromPage; pageNo != pd.ranges.At(i).nToPage + dir; pageNo += dir) { if ((PrintRangeEven == pd.advData.range && pageNo % 2 != 0) || (PrintRangeOdd == pd.advData.range && pageNo % 2 == 0)) continue; if (progressUI) progressUI->UpdateProgress(current, total); StartPage(hdc); geomutil::SizeT<float> pSize = engine.PageMediabox(pageNo).Size().Convert<float>(); int rotation = 0; // Turn the document by 90 deg if it isn't in portrait mode if (pSize.dx > pSize.dy) { rotation += 90; std::swap(pSize.dx, pSize.dy); } // make sure not to print upside-down rotation = (rotation % 180) == 0 ? 0 : 270; // finally turn the page by (another) 90 deg in landscape mode if (!bPrintPortrait) { rotation = (rotation + 90) % 360; std::swap(pSize.dx, pSize.dy); } // dpiFactor means no physical zoom float zoom = dpiFactor; // offset of the top-left corner of the page from the printable area // (negative values move the page into the left/top margins, etc.); // offset adjustments are needed because the GDI coordinate system // starts at the corner of the printable area and we rather want to // center the page on the physical paper (except for PrintScaleNone // where the page starts at the very top left of the physical paper so // that printing forms/labels of varying size remains reliably possible) PointI offset(printable.x, printable.y); if (pd.advData.scale != PrintScaleNone) { // make sure to fit all content into the printable area when scaling // and the whole document page on the physical paper RectD rect = engine.PageContentBox(pageNo, Target_Print); geomutil::RectT<float> cbox = engine.Transform(rect, pageNo, 1.0, rotation).Convert<float>(); zoom = std::min((float)printable.dx / cbox.dx, std::min((float)printable.dy / cbox.dy, std::min((float)paperSize.dx / pSize.dx, (float)paperSize.dy / pSize.dy))); // use the correct zoom values, if the page fits otherwise // and the user didn't ask for anything else (default setting) if (PrintScaleShrink == pd.advData.scale && dpiFactor < zoom) zoom = dpiFactor; // center the page on the physical paper offset.x += (int)(paperSize.dx - pSize.dx * zoom) / 2; offset.y += (int)(paperSize.dy - pSize.dy * zoom) / 2; // make sure that no content lies in the non-printable paper margins geomutil::RectT<float> onPaper(printable.x + offset.x + cbox.x * zoom, printable.y + offset.y + cbox.y * zoom, cbox.dx * zoom, cbox.dy * zoom); if (onPaper.x < printable.x) offset.x += (int)(printable.x - onPaper.x); else if (onPaper.BR().x > printable.BR().x) offset.x -= (int)(onPaper.BR().x - printable.BR().x); if (onPaper.y < printable.y) offset.y += (int)(printable.y - onPaper.y); else if (onPaper.BR().y > printable.BR().y) offset.y -= (int)(onPaper.BR().y - printable.BR().y); } bool ok = false; short shrink = 1; do { RenderedBitmap *bmp = engine.RenderBitmap(pageNo, zoom / shrink, rotation, nullptr, Target_Print, abortCookie ? &abortCookie->cookie : nullptr); if (abortCookie) abortCookie->Clear(); if (bmp && bmp->GetBitmap()) { RectI rc(offset.x, offset.y, bmp->Size().dx * shrink, bmp->Size().dy * shrink); ok = bmp->StretchDIBits(hdc, rc); } delete bmp; shrink *= 2; } while (!ok && shrink < 32 && !(progressUI && progressUI->WasCanceled())); // TODO: abort if !ok? if (EndPage(hdc) <= 0 || progressUI && progressUI->WasCanceled()) { AbortDoc(hdc); return false; } current++; } } EndDoc(hdc); return true; }
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; }
SyncTex(const WCHAR* syncfilename, BaseEngine* engine) : Synchronizer(syncfilename), engine(engine), scanner(nullptr) { AssertCrash(str::EndsWithI(syncfilename, SYNCTEX_EXTENSION)); }
static bool PrintToDevice(PrintData& pd, ProgressUpdateUI *progressUI=NULL, AbortCookieManager *abortCookie=NULL) { AssertCrash(pd.engine); if (!pd.engine) return false; AssertCrash(pd.printerName); if (!pd.printerName) return false; BaseEngine& engine = *pd.engine; ScopedMem<WCHAR> fileName; DOCINFO di = { 0 }; di.cbSize = sizeof (DOCINFO); if (gPluginMode) { fileName.Set(ExtractFilenameFromURL(gPluginURL)); // fall back to a generic "filename" instead of the more confusing temporary filename di.lpszDocName = fileName ? fileName : L"filename"; } else di.lpszDocName = engine.FileName(); int current = 1, total = 0; if (pd.sel.Count() == 0) { for (size_t i = 0; i < pd.ranges.Count(); i++) { if (pd.ranges.At(i).nToPage < pd.ranges.At(i).nFromPage) total += pd.ranges.At(i).nFromPage - pd.ranges.At(i).nToPage + 1; else total += pd.ranges.At(i).nToPage - pd.ranges.At(i).nFromPage + 1; } } else { for (int pageNo = 1; pageNo <= engine.PageCount(); pageNo++) { if (!BoundSelectionOnPage(pd.sel, pageNo).IsEmpty()) total++; } } AssertCrash(total > 0); if (0 == total) return false; if (progressUI) progressUI->UpdateProgress(current, total); // cf. http://code.google.com/p/sumatrapdf/issues/detail?id=1882 // According to our crash dumps, Cannon printer drivers (caprenn.dll etc.) // are buggy and like to crash during printing with DEP violation // We disable dep during printing to hopefully not crash // TODO: even better would be to print in a separate process so that // crashes during printing wouldn't affect the main process. It's also // much more complicated to implement ScopeDisableDEP scopeNoDEP; // cf. http://blogs.msdn.com/b/oldnewthing/archive/2012/11/09/10367057.aspx ScopeHDC hdc(CreateDC(pd.driverName, pd.printerName, pd.portName, pd.devMode)); if (!hdc) return false; if (StartDoc(hdc, &di) <= 0) return false; // MM_TEXT: Each logical unit is mapped to one device pixel. // Positive x is to the right; positive y is down. SetMapMode(hdc, MM_TEXT); const SizeI paperSize(GetDeviceCaps(hdc, PHYSICALWIDTH), GetDeviceCaps(hdc, PHYSICALHEIGHT)); const RectI printable(GetDeviceCaps(hdc, PHYSICALOFFSETX), GetDeviceCaps(hdc, PHYSICALOFFSETY), GetDeviceCaps(hdc, HORZRES), GetDeviceCaps(hdc, VERTRES)); const float dpiFactor = min(GetDeviceCaps(hdc, LOGPIXELSX) / engine.GetFileDPI(), GetDeviceCaps(hdc, LOGPIXELSY) / engine.GetFileDPI()); bool bPrintPortrait = paperSize.dx < paperSize.dy; if (pd.devMode && (pd.devMode.Get()->dmFields & DM_ORIENTATION)) bPrintPortrait = DMORIENT_PORTRAIT == pd.devMode.Get()->dmOrientation; if (pd.sel.Count() > 0) { for (int pageNo = 1; pageNo <= engine.PageCount(); pageNo++) { RectD bounds = BoundSelectionOnPage(pd.sel, pageNo); if (bounds.IsEmpty()) continue; if (progressUI) progressUI->UpdateProgress(current, total); StartPage(hdc); SizeT<float> bSize = bounds.Size().Convert<float>(); float zoom = min((float)printable.dx / bSize.dx, (float)printable.dy / bSize.dy); // use the correct zoom values, if the page fits otherwise // and the user didn't ask for anything else (default setting) if (PrintScaleShrink == pd.advData.scale) zoom = min(dpiFactor, zoom); else if (PrintScaleNone == pd.advData.scale) zoom = dpiFactor; for (size_t i = 0; i < pd.sel.Count(); i++) { if (pd.sel.At(i).pageNo != pageNo) continue; RectD *clipRegion = &pd.sel.At(i).rect; PointI offset((int)((clipRegion->x - bounds.x) * zoom), (int)((clipRegion->y - bounds.y) * zoom)); if (!pd.advData.asImage) { RectI rc((int)(printable.dx - bSize.dx * zoom) / 2 + offset.x, (int)(printable.dy - bSize.dy * zoom) / 2 + offset.y, (int)(clipRegion->dx * zoom), (int)(clipRegion->dy * zoom)); engine.RenderPage(hdc, rc, pd.sel.At(i).pageNo, zoom, pd.rotation, clipRegion, Target_Print, abortCookie ? &abortCookie->cookie : NULL); if (abortCookie) abortCookie->Clear(); } else { RenderedBitmap *bmp = NULL; short shrink = 1; do { bmp = engine.RenderBitmap(pd.sel.At(i).pageNo, zoom / shrink, pd.rotation, clipRegion, Target_Print, abortCookie ? &abortCookie->cookie : NULL); if (abortCookie) abortCookie->Clear(); if (!bmp || !bmp->GetBitmap()) { shrink *= 2; delete bmp; bmp = NULL; } } while (!bmp && shrink < 32 && !(progressUI && progressUI->WasCanceled())); if (bmp) { RectI rc((int)(paperSize.dx - bSize.dx * zoom) / 2 + offset.x, (int)(paperSize.dy - bSize.dy * zoom) / 2 + offset.y, bmp->Size().dx * shrink, bmp->Size().dy * shrink); bmp->StretchDIBits(hdc, rc); delete bmp; } } } if (EndPage(hdc) <= 0 || progressUI && progressUI->WasCanceled()) { AbortDoc(hdc); return false; } current++; } EndDoc(hdc); return false; } // print all the pages the user requested for (size_t i = 0; i < pd.ranges.Count(); i++) { int dir = pd.ranges.At(i).nFromPage > pd.ranges.At(i).nToPage ? -1 : 1; for (DWORD pageNo = pd.ranges.At(i).nFromPage; pageNo != pd.ranges.At(i).nToPage + dir; pageNo += dir) { if ((PrintRangeEven == pd.advData.range && pageNo % 2 != 0) || (PrintRangeOdd == pd.advData.range && pageNo % 2 == 0)) continue; if (progressUI) progressUI->UpdateProgress(current, total); StartPage(hdc); SizeT<float> pSize = engine.PageMediabox(pageNo).Size().Convert<float>(); int rotation = 0; // Turn the document by 90 deg if it isn't in portrait mode if (pSize.dx > pSize.dy) { rotation += 90; swap(pSize.dx, pSize.dy); } // make sure not to print upside-down rotation = (rotation % 180) == 0 ? 0 : 270; // finally turn the page by (another) 90 deg in landscape mode if (!bPrintPortrait) { rotation = (rotation + 90) % 360; swap(pSize.dx, pSize.dy); } // dpiFactor means no physical zoom float zoom = dpiFactor; // offset of the top-left corner of the page from the printable area // (negative values move the page into the left/top margins, etc.); // offset adjustments are needed because the GDI coordinate system // starts at the corner of the printable area and we rather want to // center the page on the physical paper (default behavior) PointI offset(-printable.x, -printable.y); if (pd.advData.scale != PrintScaleNone) { // make sure to fit all content into the printable area when scaling // and the whole document page on the physical paper RectD rect = engine.PageContentBox(pageNo, Target_Print); RectT<float> cbox = engine.Transform(rect, pageNo, 1.0, rotation).Convert<float>(); zoom = min((float)printable.dx / cbox.dx, min((float)printable.dy / cbox.dy, min((float)paperSize.dx / pSize.dx, (float)paperSize.dy / pSize.dy))); // use the correct zoom values, if the page fits otherwise // and the user didn't ask for anything else (default setting) if (PrintScaleShrink == pd.advData.scale && dpiFactor < zoom) zoom = dpiFactor; // make sure that no content lies in the non-printable paper margins RectT<float> onPaper((paperSize.dx - pSize.dx * zoom) / 2 + cbox.x * zoom, (paperSize.dy - pSize.dy * zoom) / 2 + cbox.y * zoom, cbox.dx * zoom, cbox.dy * zoom); if (onPaper.x < printable.x) offset.x += (int)(printable.x - onPaper.x); else if (onPaper.BR().x > printable.BR().x) offset.x -= (int)(onPaper.BR().x - printable.BR().x); if (onPaper.y < printable.y) offset.y += (int)(printable.y - onPaper.y); else if (onPaper.BR().y > printable.BR().y) offset.y -= (int)(onPaper.BR().y - printable.BR().y); } if (!pd.advData.asImage) { RectI rc = RectI::FromXY((int)(paperSize.dx - pSize.dx * zoom) / 2 + offset.x, (int)(paperSize.dy - pSize.dy * zoom) / 2 + offset.y, paperSize.dx, paperSize.dy); engine.RenderPage(hdc, rc, pageNo, zoom, rotation, NULL, Target_Print, abortCookie ? &abortCookie->cookie : NULL); if (abortCookie) abortCookie->Clear(); } else { RenderedBitmap *bmp = NULL; short shrink = 1; do { bmp = engine.RenderBitmap(pageNo, zoom / shrink, rotation, NULL, Target_Print, abortCookie ? &abortCookie->cookie : NULL); if (abortCookie) abortCookie->Clear(); if (!bmp || !bmp->GetBitmap()) { shrink *= 2; delete bmp; bmp = NULL; } } while (!bmp && shrink < 32 && !(progressUI && progressUI->WasCanceled())); if (bmp) { RectI rc((paperSize.dx - bmp->Size().dx * shrink) / 2 + offset.x, (paperSize.dy - bmp->Size().dy * shrink) / 2 + offset.y, bmp->Size().dx * shrink, bmp->Size().dy * shrink); bmp->StretchDIBits(hdc, rc); delete bmp; } } if (EndPage(hdc) <= 0 || progressUI && progressUI->WasCanceled()) { AbortDoc(hdc); return false; } current++; } } EndDoc(hdc); return true; }
EbookFormattingThread::EbookFormattingThread(Doc doc, HtmlFormatterArgs *args, EbookController *ctrl) : doc(doc), formatterArgs(args), controller(ctrl), pageCount(0) { AssertCrash(doc.IsEbook() || (doc.IsNone() && (NULL != args->htmlStr))); }
bool StressTest::OpenFile(const WCHAR* fileName) { wprintf(L"%s\n", fileName); fflush(stdout); LoadArgs args(fileName); args.forceReuse = rand() % 3 != 1; WindowInfo* w = LoadDocument(args); if (!w) return false; if (w == win) { // WindowInfo reused if (!win->IsDocLoaded()) return false; } else if (!w->IsDocLoaded()) { // new WindowInfo CloseWindow(w, false); return false; } // transfer ownership of stressTest object to a new window and close the // current one AssertCrash(this == win->stressTest); if (w != win) { if (win->IsDocLoaded()) { // try to provoke a crash in RenderCache cleanup code ClientRect rect(win->hwndFrame); rect.Inflate(rand() % 10, rand() % 10); SendMessage(win->hwndFrame, WM_SIZE, 0, MAKELONG(rect.dx, rect.dy)); if (win->AsFixed()) win->cbHandler->RequestRendering(1); win->RepaintAsync(); } WindowInfo* toClose = win; w->stressTest = win->stressTest; win->stressTest = nullptr; win = w; CloseWindow(toClose, false); } if (!win->IsDocLoaded()) return false; win->ctrl->SetDisplayMode(DM_CONTINUOUS); win->ctrl->SetZoomVirtual(ZOOM_FIT_PAGE, nullptr); win->ctrl->GoToFirstPage(); if (win->tocVisible || gGlobalPrefs->showFavorites) SetSidebarVisibility(win, win->tocVisible, gGlobalPrefs->showFavorites); currPage = pageRanges.at(0).start; win->ctrl->GoToPage(currPage, false); currPageRenderTime.Start(); ++filesCount; pageForSearchStart = (rand() % win->ctrl->PageCount()) + 1; // search immediately in single page documents if (1 == pageForSearchStart) { // use text that is unlikely to be found, so that we search all pages win::SetText(win->hwndFindBox, L"!z_yt"); FindTextOnThread(win, TextSearchDirection::Forward, true); } int secs = SecsSinceSystemTime(stressStartTime); AutoFreeW tm(FormatTime(secs)); AutoFreeW s(str::Format(L"File %d: %s, time: %s", filesCount, fileName, tm)); win->ShowNotification(s, NOS_PERSIST, NG_STRESS_TEST_SUMMARY); return true; }