void Render(HDC hdc, RectI target, int pageNo, float zoom) { ScopedCritSec scope(&currAccess); if (currBmp && currPage == pageNo && currSize == target.Size()) currBmp->StretchDIBits(hdc, target); else if (!thread) { reqPage = pageNo; reqZoom = zoom; reqSize = target.Size(); thread = CreateThread(NULL, 0, RenderThread, this, 0, 0); } }
void Render(HDC hdc, RectI target, int pageNo, float zoom) { ScopedCritSec scope(&currAccess); if (currBmp && currPage == pageNo && currSize == target.Size()) currBmp->StretchDIBits(hdc, target); else if (!thread) { reqPage = pageNo; reqZoom = zoom; reqSize = target.Size(); reqAbort = false; thread = CreateThread(NULL, 0, RenderThread, this, 0, 0); } else if (reqPage != pageNo || reqSize != target.Size()) { if (abortCookie) abortCookie->Abort(); reqAbort = true; } }
bool DjVuEngineImpl::RenderPage(HDC hDC, RectI screenRect, int pageNo, float zoom, int rotation, RectD *pageRect, RenderTarget target, AbortCookie **cookie_out) { bool success = true; RectD mediabox = PageMediabox(pageNo); HRGN clip = CreateRectRgn(screenRect.x, screenRect.y, screenRect.x + screenRect.dx, screenRect.y + screenRect.dy); SelectClipRgn(hDC, clip); DjVuAbortCookie *cookie = NULL; if (cookie_out) *cookie_out = cookie = new DjVuAbortCookie(); // render in 1 MB bands, as otherwise GDI can run out of memory RectD rect = pageRect ? *pageRect : mediabox; int bandDy = (int)((1 << 20) / (rect.dy * zoom)); PointI pt = Transform(rect, pageNo, zoom, rotation).TL().Convert<int>(); for (int y = 0; y * bandDy < rect.dy; y++) { RectD pageBand(rect.x, y * bandDy, rect.dx, bandDy); pageBand = pageBand.Intersect(mediabox); RectI screenBand = Transform(pageBand, pageNo, zoom, rotation).Round(); screenBand.Offset(screenRect.x - pt.x, screenRect.y - pt.y); RenderedBitmap *bmp = RenderBitmap(pageNo, zoom, rotation, &pageBand, target, cookie_out); if (bmp && bmp->GetBitmap()) success = bmp->StretchDIBits(hDC, screenBand); else success = false; delete bmp; if (cookie && cookie->abort) { success = false; break; } } SelectClipRgn(hDC, NULL); return success; }
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; }
void DrawStartPage(WindowInfo& win, HDC hdc, FileHistory& fileHistory, COLORREF textColor, COLORREF backgroundColor) { HPEN penBorder = CreatePen(PS_SOLID, DOCLIST_SEPARATOR_DY, WIN_COL_BLACK); HPEN penThumbBorder = CreatePen(PS_SOLID, DOCLIST_THUMBNAIL_BORDER_W, WIN_COL_BLACK); HPEN penLinkLine = CreatePen(PS_SOLID, 1, COL_BLUE_LINK); ScopedFont fontSumatraTxt(GetSimpleFont(hdc, L"MS Shell Dlg", 24)); ScopedFont fontLeftTxt(GetSimpleFont(hdc, L"MS Shell Dlg", 14)); HGDIOBJ origFont = SelectObject(hdc, fontSumatraTxt); /* Just to remember the orig font */ ClientRect rc(win.hwndCanvas); RECT rTmp = rc.ToRECT(); ScopedGdiObj<HBRUSH> brushLogoBg(CreateSolidBrush(GetLogoBgColor())); FillRect(hdc, &rTmp, brushLogoBg); SelectObject(hdc, brushLogoBg); SelectObject(hdc, penBorder); bool isRtl = IsUIRightToLeft(); /* render title */ RectI titleBox = RectI(PointI(0, 0), CalcSumatraVersionSize(hdc)); titleBox.x = rc.dx - titleBox.dx - 3; DrawSumatraVersion(hdc, titleBox); PaintLine(hdc, RectI(0, titleBox.dy, rc.dx, 0)); /* render recent files list */ SelectObject(hdc, penThumbBorder); SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, WIN_COL_BLACK); rc.y += titleBox.dy; rc.dy -= titleBox.dy; rTmp = rc.ToRECT(); ScopedGdiObj<HBRUSH> brushAboutBg(CreateSolidBrush(GetAboutBgColor())); FillRect(hdc, &rTmp, brushAboutBg); rc.dy -= DOCLIST_BOTTOM_BOX_DY; Vec<DisplayState *> list; fileHistory.GetFrequencyOrder(list); int width = limitValue((rc.dx - DOCLIST_MARGIN_LEFT - DOCLIST_MARGIN_RIGHT + DOCLIST_MARGIN_BETWEEN_X) / (THUMBNAIL_DX + DOCLIST_MARGIN_BETWEEN_X), 1, DOCLIST_MAX_THUMBNAILS_X); int height = min((rc.dy - DOCLIST_MARGIN_TOP - DOCLIST_MARGIN_BOTTOM + DOCLIST_MARGIN_BETWEEN_Y) / (THUMBNAIL_DY + DOCLIST_MARGIN_BETWEEN_Y), FILE_HISTORY_MAX_FREQUENT / width); PointI offset(rc.x + DOCLIST_MARGIN_LEFT + (rc.dx - width * THUMBNAIL_DX - (width - 1) * DOCLIST_MARGIN_BETWEEN_X - DOCLIST_MARGIN_LEFT - DOCLIST_MARGIN_RIGHT) / 2, rc.y + DOCLIST_MARGIN_TOP); if (offset.x < ABOUT_INNER_PADDING) offset.x = ABOUT_INNER_PADDING; else if (list.Count() == 0) offset.x = DOCLIST_MARGIN_LEFT; SelectObject(hdc, fontSumatraTxt); SIZE txtSize; const WCHAR *txt = _TR("Frequently Read"); GetTextExtentPoint32(hdc, txt, (int)str::Len(txt), &txtSize); RectI headerRect(offset.x, rc.y + (DOCLIST_MARGIN_TOP - txtSize.cy) / 2, txtSize.cx, txtSize.cy); if (isRtl) headerRect.x = rc.dx - offset.x - headerRect.dx; rTmp = headerRect.ToRECT(); DrawText(hdc, txt, -1, &rTmp, (isRtl ? DT_RTLREADING : DT_LEFT) | DT_NOPREFIX); SelectObject(hdc, fontLeftTxt); SelectObject(hdc, GetStockBrush(NULL_BRUSH)); win.staticLinks.Reset(); for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { if (h * width + w >= (int)list.Count()) { // display the "Open a document" link right below the last row height = w > 0 ? h + 1 : h; break; } DisplayState *state = list.At(h * width + w); RectI page(offset.x + w * (int)(THUMBNAIL_DX + DOCLIST_MARGIN_BETWEEN_X * win.uiDPIFactor), offset.y + h * (int)(THUMBNAIL_DY + DOCLIST_MARGIN_BETWEEN_Y * win.uiDPIFactor), THUMBNAIL_DX, THUMBNAIL_DY); if (isRtl) page.x = rc.dx - page.x - page.dx; bool loadOk = true; if (!state->thumbnail) loadOk = LoadThumbnail(*state); if (loadOk && state->thumbnail) { SizeI thumbSize = state->thumbnail->Size(); if (thumbSize.dx != THUMBNAIL_DX || thumbSize.dy != THUMBNAIL_DY) { page.dy = thumbSize.dy * THUMBNAIL_DX / thumbSize.dx; page.y += THUMBNAIL_DY - page.dy; } HRGN clip = CreateRoundRectRgn(page.x, page.y, page.x + page.dx, page.y + page.dy, 10, 10); SelectClipRgn(hdc, clip); RenderedBitmap *clone = state->thumbnail->Clone(); UpdateBitmapColors(clone->GetBitmap(), textColor, backgroundColor); clone->StretchDIBits(hdc, page); SelectClipRgn(hdc, NULL); DeleteObject(clip); delete clone; } RoundRect(hdc, page.x, page.y, page.x + page.dx, page.y + page.dy, 10, 10); int iconSpace = (int)(20 * win.uiDPIFactor); RectI rect(page.x + iconSpace, page.y + page.dy + 3, page.dx - iconSpace, iconSpace); if (isRtl) rect.x -= iconSpace; rTmp = rect.ToRECT(); DrawText(hdc, path::GetBaseName(state->filePath), -1, &rTmp, DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX | (isRtl ? DT_RIGHT : DT_LEFT)); SHFILEINFO sfi; HIMAGELIST himl = (HIMAGELIST)SHGetFileInfo(state->filePath, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_USEFILEATTRIBUTES); ImageList_Draw(himl, sfi.iIcon, hdc, isRtl ? page.x + page.dx - (int)(16 * win.uiDPIFactor) : page.x, rect.y, ILD_TRANSPARENT); win.staticLinks.Append(StaticLinkInfo(rect.Union(page), state->filePath, state->filePath)); } } /* render bottom links */ rc.y += DOCLIST_MARGIN_TOP + height * THUMBNAIL_DY + (height - 1) * DOCLIST_MARGIN_BETWEEN_Y + DOCLIST_MARGIN_BOTTOM; rc.dy = DOCLIST_BOTTOM_BOX_DY; SetTextColor(hdc, COL_BLUE_LINK); SelectObject(hdc, penLinkLine); HIMAGELIST himl = (HIMAGELIST)SendMessage(win.hwndToolbar, TB_GETIMAGELIST, 0, 0); RectI rectIcon(offset.x, rc.y, 0, 0); ImageList_GetIconSize(himl, &rectIcon.dx, &rectIcon.dy); rectIcon.y += (rc.dy - rectIcon.dy) / 2; if (isRtl) rectIcon.x = rc.dx - offset.x - rectIcon.dx; ImageList_Draw(himl, 0 /* index of Open icon */, hdc, rectIcon.x, rectIcon.y, ILD_NORMAL); txt = _TR("Open a document..."); GetTextExtentPoint32(hdc, txt, (int)str::Len(txt), &txtSize); RectI rect(offset.x + rectIcon.dx + 3, rc.y + (rc.dy - txtSize.cy) / 2, txtSize.cx, txtSize.cy); if (isRtl) rect.x = rectIcon.x - rect.dx - 3; rTmp = rect.ToRECT(); DrawText(hdc, txt, -1, &rTmp, isRtl ? DT_RTLREADING : DT_LEFT); PaintLine(hdc, RectI(rect.x, rect.y + rect.dy, rect.dx, 0)); // make the click target larger rect = rect.Union(rectIcon); rect.Inflate(10, 10); win.staticLinks.Append(StaticLinkInfo(rect, SLINK_OPEN_FILE)); rect = DrawBottomRightLink(win.hwndCanvas, hdc, _TR("Hide frequently read")); win.staticLinks.Append(StaticLinkInfo(rect, SLINK_LIST_HIDE)); SelectObject(hdc, origFont); DeleteObject(penBorder); DeleteObject(penThumbBorder); DeleteObject(penLinkLine); }
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; }
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; }