Exemple #1
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();
         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;
}
Exemple #4
0
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;
}