| 199 | } |
| 200 | |
| 201 | void ContinuousPageWidget::paintEvent(QPaintEvent *event) |
| 202 | { |
| 203 | if (!continuousViewModel || continuousViewModel->numPages() == 0 || !provider) { |
| 204 | return; |
| 205 | } |
| 206 | |
| 207 | QPainter painter(this); |
| 208 | const qreal dpr = devicePixelRatioF(); |
| 209 | const int effectivePixelWidth = std::max(1, qRound(width() * dpr)); |
| 210 | |
| 211 | // A width change makes every cached/in-flight scale obsolete. Bump the |
| 212 | // generation so stale results that are already running get discarded. |
| 213 | if (scaledPageCache.effectiveWidth != effectivePixelWidth) { |
| 214 | scaledPageCache.invalidateForWidth(effectivePixelWidth); |
| 215 | ++cacheGeneration; |
| 216 | pendingScaleRequests.clear(); |
| 217 | scalePool.clear(); |
| 218 | } |
| 219 | |
| 220 | const int numPages = continuousViewModel->numPages(); |
| 221 | |
| 222 | const QRect paintRect = event->rect(); |
| 223 | int firstPage = qBound(0, continuousViewModel->pageAtY(paintRect.top()), numPages - 1); |
| 224 | int lastPage = qBound(0, continuousViewModel->pageAtY(paintRect.bottom()), numPages - 1); |
| 225 | |
| 226 | // Cache management and prefetch follow the real viewport, not this paint's |
| 227 | // dirty rect (which can be a single page from an async delivery), so a |
| 228 | // partial repaint never evicts a still-visible page. |
| 229 | int prefetchMin = 0; |
| 230 | int prefetchMax = -1; |
| 231 | managedPageRange(prefetchMin, prefetchMax); |
| 232 | scaledPageCache.keepOnlyRange(prefetchMin, prefetchMax); |
| 233 | |
| 234 | // Drive the background decoder to keep the visible + prefetch window ready. |
| 235 | // This is the only place decoding is requested, and it never blocks. |
| 236 | provider->requestRange(prefetchMin, prefetchMax); |
| 237 | |
| 238 | auto targetPixelSizeFor = [dpr](const QSize &scaled) { |
| 239 | return QSize(std::max(1, qRound(scaled.width() * dpr)), |
| 240 | std::max(1, qRound(scaled.height() * dpr))); |
| 241 | }; |
| 242 | |
| 243 | const int w = width(); |
| 244 | for (int i = firstPage; i <= lastPage && i < numPages; ++i) { |
| 245 | const int y = continuousViewModel->yPositionForPage(i); |
| 246 | const QSize scaled = continuousViewModel->scaledPageSize(i); |
| 247 | // center horizontally if page is narrower than widget |
| 248 | int x = (w - scaled.width()) / 2; |
| 249 | if (x < 0) { |
| 250 | x = 0; |
| 251 | } |
| 252 | const QRect pageRect(x, y, scaled.width(), scaled.height()); |
| 253 | |
| 254 | const QImage *img = provider->image(i); |
| 255 | if (img && !img->isNull()) { |
| 256 | const QSize targetPixelSize = targetPixelSizeFor(scaled); |
| 257 | const QImage *drawable = cachedScaledImage(i, img, scaled, targetPixelSize, dpr); |
| 258 | if (drawable) { |
nothing calls this directly
no test coverage detected