2 * Copyright (C) 2011 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #import "WKPrintingView.h"
33 #import "PDFKitImports.h"
35 #import "ShareableBitmap.h"
36 #import "WebPageProxy.h"
37 #import <PDFKit/PDFKit.h>
38 #import <WebCore/GraphicsContext.h>
39 #import <WebCore/LocalDefaultSystemAppearance.h>
40 #import <WebCore/WebCoreObjCExtras.h>
41 #import <wtf/RunLoop.h>
43 NSString * const WebKitOriginalTopPrintingMarginKey = @"WebKitOriginalTopMargin";
44 NSString * const WebKitOriginalBottomPrintingMarginKey = @"WebKitOriginalBottomMargin";
46 NSString * const NSPrintInfoDidChangeNotification = @"NSPrintInfoDidChange";
48 static BOOL isForcingPreviewUpdate;
50 @implementation WKPrintingView
52 - (id)initWithFrameProxy:(WebKit::WebFrameProxy&)frame view:(NSView *)wkView
54 self = [super init]; // No frame rect to pass to NSView.
66 if (WebCoreObjCScheduleDeallocateOnMainThread([WKPrintingView class], self))
77 - (void)_setAutodisplay:(BOOL)newState
79 #pragma clang diagnostic push
80 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
81 if (!newState && [[_wkView window] isAutodisplay])
82 #pragma clang diagnostic pop
83 [_wkView displayIfNeeded];
85 #pragma clang diagnostic push
86 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
87 [[_wkView window] setAutodisplay:newState];
88 #pragma clang diagnostic pop
90 // For some reason, painting doesn't happen for a long time without this call, <rdar://problem/8975229>.
92 [_wkView displayIfNeeded];
96 - (void)_suspendAutodisplay
98 // A drawRect: call on WKView causes a switch to screen mode, which is slow due to relayout, and we want to avoid that.
99 // Disabling autodisplay will prevent random updates from causing this, but resizing the window will still work.
100 if (_autodisplayResumeTimer) {
101 [_autodisplayResumeTimer invalidate];
102 _autodisplayResumeTimer = nil;
104 [self _setAutodisplay:NO];
107 - (void)_delayedResumeAutodisplayTimerFired
109 ASSERT(RunLoop::isMain());
111 _autodisplayResumeTimer = nil;
112 [self _setAutodisplay:YES];
114 // Enabling autodisplay normally implicitly calls endPrinting() via -[WKView drawRect:], but not when content is in accelerated compositing mode.
115 if (_webFrame->page())
116 _webFrame->page()->endPrinting();
119 - (void)_delayedResumeAutodisplay
121 // AppKit calls endDocument/beginDocument when print option change. We don't want to switch between print and screen mode just for that,
122 // and enabling autodisplay may result in switching into screen mode. So, autodisplay is only resumed on next run loop iteration.
123 if (!_autodisplayResumeTimer) {
124 _autodisplayResumeTimer = [NSTimer timerWithTimeInterval:0 target:self selector:@selector(_delayedResumeAutodisplayTimerFired) userInfo:nil repeats:NO];
125 // The timer must be scheduled on main thread, because printing thread may finish before it fires.
126 [[NSRunLoop mainRunLoop] addTimer:_autodisplayResumeTimer forMode:NSDefaultRunLoopMode];
130 - (void)_adjustPrintingMarginsForHeaderAndFooter
132 ASSERT(RunLoop::isMain()); // This function calls the client, which should only be done on main thread.
134 NSPrintInfo *info = [_printOperation printInfo];
135 NSMutableDictionary *infoDictionary = [info dictionary];
137 // We need to modify the top and bottom margins in the NSPrintInfo to account for the space needed by the
138 // header and footer. Because this method can be called more than once on the same NSPrintInfo (see 5038087),
139 // we stash away the unmodified top and bottom margins the first time this method is called, and we read from
140 // those stashed-away values on subsequent calls.
141 double originalTopMargin;
142 double originalBottomMargin;
143 NSNumber *originalTopMarginNumber = [infoDictionary objectForKey:WebKitOriginalTopPrintingMarginKey];
144 if (!originalTopMarginNumber) {
145 ASSERT(![infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey]);
146 originalTopMargin = [info topMargin];
147 originalBottomMargin = [info bottomMargin];
148 [infoDictionary setObject:[NSNumber numberWithDouble:originalTopMargin] forKey:WebKitOriginalTopPrintingMarginKey];
149 [infoDictionary setObject:[NSNumber numberWithDouble:originalBottomMargin] forKey:WebKitOriginalBottomPrintingMarginKey];
151 ASSERT([originalTopMarginNumber isKindOfClass:[NSNumber class]]);
152 ASSERT([[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] isKindOfClass:[NSNumber class]]);
153 originalTopMargin = [originalTopMarginNumber doubleValue];
154 originalBottomMargin = [[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] doubleValue];
157 CGFloat scale = [info scalingFactor];
158 [info setTopMargin:originalTopMargin + _webFrame->page()->headerHeight(*_webFrame) * scale];
159 [info setBottomMargin:originalBottomMargin + _webFrame->page()->footerHeight(*_webFrame) * scale];
162 - (BOOL)_isPrintingPreview
164 // <rdar://problem/8901041> Please add an API returning whether the current print operation is for preview.
165 // Assuming that if NSPrintOperation is allowed to spawn a thread for printing, it will. Print preview doesn't spawn a thread.
166 return !_isPrintingFromSecondaryThread;
169 - (void)_updatePreview
171 // <rdar://problem/8900923> Please add an API to force print preview update.
172 ASSERT(!isForcingPreviewUpdate);
173 isForcingPreviewUpdate = YES;
174 [[NSNotificationCenter defaultCenter] postNotificationName:NSPrintInfoDidChangeNotification object:nil];
175 isForcingPreviewUpdate = NO;
178 - (BOOL)_hasPageRects
180 // WebCore always prints at least one page.
181 return !_printingPageRects.isEmpty();
184 - (NSUInteger)_firstPrintedPageNumber
186 // Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion.
187 return [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue];
190 - (NSUInteger)_lastPrintedPageNumber
192 ASSERT([self _hasPageRects]);
194 // Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion.
195 NSUInteger firstPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue];
196 NSUInteger lastPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintLastPage] unsignedIntegerValue];
197 if (lastPage - firstPage >= _printingPageRects.size())
198 return _printingPageRects.size();
202 - (uint64_t)_expectedPreviewCallbackForRect:(const WebCore::IntRect&)rect
204 for (HashMap<uint64_t, WebCore::IntRect>::iterator iter = _expectedPreviewCallbacks.begin(); iter != _expectedPreviewCallbacks.end(); ++iter) {
205 if (iter->value == rect)
211 struct IPCCallbackContext {
212 RetainPtr<WKPrintingView> view;
216 static void pageDidDrawToImage(const WebKit::ShareableBitmap::Handle& imageHandle, IPCCallbackContext* context)
218 ASSERT(RunLoop::isMain());
220 WKPrintingView *view = context->view.get();
222 // If the user has already changed print setup, then this response is obsolete. And if this callback is not in response to the latest request,
223 // then the user has already moved to another page - we'll cache the response, but won't draw it.
224 HashMap<uint64_t, WebCore::IntRect>::iterator iter = view->_expectedPreviewCallbacks.find(context->callbackID);
225 if (iter != view->_expectedPreviewCallbacks.end()) {
226 ASSERT([view _isPrintingPreview]);
228 if (!imageHandle.isNull()) {
229 auto image = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::Protection::ReadOnly);
232 view->_pagePreviews.add(iter->value, image);
235 view->_expectedPreviewCallbacks.remove(context->callbackID);
236 bool receivedResponseToLatestRequest = view->_latestExpectedPreviewCallback == context->callbackID;
237 if (receivedResponseToLatestRequest) {
238 view->_latestExpectedPreviewCallback = 0;
239 [view _updatePreview];
244 - (void)_preparePDFDataForPrintingOnSecondaryThread
246 ASSERT(RunLoop::isMain());
248 if (!_webFrame->page()) {
249 _printingCallbackCondition.notifyOne();
253 std::lock_guard<Lock> lock(_printingCallbackMutex);
255 ASSERT([self _hasPageRects]);
256 ASSERT(_printedPagesData.isEmpty());
257 ASSERT(!_printedPagesPDFDocument);
258 ASSERT(!_expectedPrintCallback);
260 NSUInteger firstPage = [self _firstPrintedPageNumber];
261 NSUInteger lastPage = [self _lastPrintedPageNumber];
263 ASSERT(firstPage > 0);
264 ASSERT(firstPage <= lastPage);
265 LOG(Printing, "WKPrintingView requesting PDF data for pages %u...%u", firstPage, lastPage);
267 WebKit::PrintInfo printInfo([_printOperation printInfo]);
268 // Return to printing mode if we're already back to screen (e.g. due to window resizing).
269 _webFrame->page()->beginPrinting(_webFrame.get(), printInfo);
271 IPCCallbackContext* context = new IPCCallbackContext;
272 auto callback = WebKit::DataCallback::create([context](API::Data* data, WebKit::CallbackBase::Error) {
273 ASSERT(RunLoop::isMain());
275 std::unique_ptr<IPCCallbackContext> contextDeleter(context);
276 WKPrintingView *view = context->view.get();
278 if (context->callbackID == view->_expectedPrintCallback) {
279 ASSERT(![view _isPrintingPreview]);
280 ASSERT(view->_printedPagesData.isEmpty());
281 ASSERT(!view->_printedPagesPDFDocument);
283 view->_printedPagesData.append(data->bytes(), data->size());
284 view->_expectedPrintCallback = 0;
285 view->_printingCallbackCondition.notifyOne();
288 _expectedPrintCallback = callback->callbackID().toInteger();
290 context->view = self;
291 context->callbackID = callback->callbackID().toInteger();
293 _webFrame->page()->drawPagesToPDF(_webFrame.get(), printInfo, firstPage - 1, lastPage - firstPage + 1, WTFMove(callback));
296 static void pageDidComputePageRects(const Vector<WebCore::IntRect>& pageRects, double totalScaleFactorForPrinting, IPCCallbackContext* context)
298 ASSERT(RunLoop::isMain());
300 WKPrintingView *view = context->view.get();
302 // If the user has already changed print setup, then this response is obsolete.
303 if (context->callbackID == view->_expectedComputedPagesCallback) {
304 ASSERT(RunLoop::isMain());
305 ASSERT(view->_expectedPreviewCallbacks.isEmpty());
306 ASSERT(!view->_latestExpectedPreviewCallback);
307 ASSERT(!view->_expectedPrintCallback);
308 ASSERT(view->_pagePreviews.isEmpty());
309 view->_expectedComputedPagesCallback = 0;
311 view->_printingPageRects = pageRects;
312 view->_totalScaleFactorForPrinting = totalScaleFactorForPrinting;
314 // Sanitize a response coming from the Web process.
315 if (view->_printingPageRects.isEmpty())
316 view->_printingPageRects.append(WebCore::IntRect(0, 0, 1, 1));
317 if (view->_totalScaleFactorForPrinting <= 0)
318 view->_totalScaleFactorForPrinting = 1;
320 const WebCore::IntRect& lastPrintingPageRect = view->_printingPageRects[view->_printingPageRects.size() - 1];
321 NSRect newFrameSize = NSMakeRect(0, 0,
322 ceil(lastPrintingPageRect.maxX() * view->_totalScaleFactorForPrinting),
323 ceil(lastPrintingPageRect.maxY() * view->_totalScaleFactorForPrinting));
324 LOG(Printing, "WKPrintingView setting frame size to x:%g y:%g width:%g height:%g", newFrameSize.origin.x, newFrameSize.origin.y, newFrameSize.size.width, newFrameSize.size.height);
325 [view setFrame:newFrameSize];
327 if ([view _isPrintingPreview]) {
328 // Show page count, and ask for an actual image to replace placeholder.
329 [view _updatePreview];
331 // When printing, request everything we'll need beforehand.
332 [view _preparePDFDataForPrintingOnSecondaryThread];
337 - (BOOL)_askPageToComputePageRects
339 ASSERT(RunLoop::isMain());
341 if (!_webFrame->page())
344 ASSERT(!_expectedComputedPagesCallback);
346 IPCCallbackContext* context = new IPCCallbackContext;
347 auto callback = WebKit::ComputedPagesCallback::create([context](const Vector<WebCore::IntRect>& pageRects, double totalScaleFactorForPrinting, WebKit::CallbackBase::Error) {
348 std::unique_ptr<IPCCallbackContext> contextDeleter(context);
349 pageDidComputePageRects(pageRects, totalScaleFactorForPrinting, context);
351 _expectedComputedPagesCallback = callback->callbackID().toInteger();
352 context->view = self;
353 context->callbackID = _expectedComputedPagesCallback;
355 _webFrame->page()->computePagesForPrinting(_webFrame.get(), WebKit::PrintInfo([_printOperation printInfo]), WTFMove(callback));
359 static void prepareDataForPrintingOnSecondaryThread(WKPrintingView *view)
361 ASSERT(RunLoop::isMain());
363 std::lock_guard<Lock> lock(view->_printingCallbackMutex);
365 // We may have received page rects while a message to call this function traveled from secondary thread to main one.
366 if ([view _hasPageRects]) {
367 [view _preparePDFDataForPrintingOnSecondaryThread];
371 // A request for pages has already been made, just wait for it to finish.
372 if (view->_expectedComputedPagesCallback)
375 [view _askPageToComputePageRects];
378 - (BOOL)knowsPageRange:(NSRangePointer)range
380 LOG(Printing, "-[WKPrintingView %p knowsPageRange:], %s, %s", self, [self _hasPageRects] ? "print data is available" : "print data is not available yet", RunLoop::isMain() ? "on main thread" : "on secondary thread");
381 ASSERT(_printOperation == [NSPrintOperation currentOperation]);
383 // Assuming that once we switch to printing from a secondary thread, we don't go back.
384 ASSERT(!_isPrintingFromSecondaryThread || !RunLoop::isMain());
385 if (!RunLoop::isMain())
386 _isPrintingFromSecondaryThread = YES;
388 if (!_webFrame->page()) {
389 *range = NSMakeRange(1, NSIntegerMax);
393 [self _suspendAutodisplay];
395 [self performSelectorOnMainThread:@selector(_adjustPrintingMarginsForHeaderAndFooter) withObject:nil waitUntilDone:YES];
397 if ([self _hasPageRects])
398 *range = NSMakeRange(1, _printingPageRects.size());
399 else if (!RunLoop::isMain()) {
400 ASSERT(![self _isPrintingPreview]);
401 std::unique_lock<Lock> lock(_printingCallbackMutex);
403 RunLoop::main().dispatch([self] {
404 prepareDataForPrintingOnSecondaryThread(self);
407 _printingCallbackCondition.wait(lock);
408 *range = NSMakeRange(1, _printingPageRects.size());
410 ASSERT([self _isPrintingPreview]);
412 // If a request for pages hasn't already been made, make it now.
413 if (!_expectedComputedPagesCallback)
414 [self _askPageToComputePageRects];
416 *range = NSMakeRange(1, NSIntegerMax);
421 - (unsigned)_pageForRect:(NSRect)rect
423 // Assuming that rect exactly matches one of the pages.
424 for (size_t i = 0; i < _printingPageRects.size(); ++i) {
425 WebCore::IntRect currentRect(_printingPageRects[i]);
426 currentRect.scale(_totalScaleFactorForPrinting);
427 if (rect.origin.y == currentRect.y() && rect.origin.x == currentRect.x())
430 ASSERT_NOT_REACHED();
431 return 0; // Invalid page number.
434 static CFStringRef linkDestinationName(PDFDocument *document, PDFDestination *destination)
436 return (CFStringRef)[NSString stringWithFormat:@"%lu-%f-%f", (unsigned long)[document indexForPage:destination.page], destination.point.x, destination.point.y];
439 - (void)_drawPDFDocument:(PDFDocument *)pdfDocument page:(unsigned)page atPoint:(NSPoint)point
442 LOG_ERROR("Couldn't create a PDF document with data passed for preview");
448 pdfPage = [pdfDocument pageAtIndex:page];
449 } @catch (id exception) {
450 LOG_ERROR("Preview data doesn't have page %d: %@", page, exception);
454 NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext];
455 #pragma clang diagnostic push
456 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
457 CGContextRef context = static_cast<CGContextRef>([nsGraphicsContext graphicsPort]);
458 #pragma clang diagnostic pop
460 CGContextSaveGState(context);
461 CGContextTranslateCTM(context, point.x, point.y);
462 CGContextScaleCTM(context, _totalScaleFactorForPrinting, -_totalScaleFactorForPrinting);
463 CGContextTranslateCTM(context, 0, -[pdfPage boundsForBox:kPDFDisplayBoxMediaBox].size.height);
464 #pragma clang diagnostic push
465 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
466 [pdfPage drawWithBox:kPDFDisplayBoxMediaBox];
467 #pragma clang diagnostic pop
469 CGAffineTransform transform = CGContextGetCTM(context);
471 for (const auto& destination : _linkDestinationsPerPage[page]) {
472 CGPoint destinationPoint = CGPointApplyAffineTransform(NSPointToCGPoint([destination point]), transform);
473 CGPDFContextAddDestinationAtPoint(context, linkDestinationName(pdfDocument, destination.get()), destinationPoint);
476 for (PDFAnnotation *annotation in [pdfPage annotations]) {
477 if (![annotation isKindOfClass:WebKit::pdfAnnotationLinkClass()])
480 #pragma clang diagnostic push
481 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
482 PDFAnnotationLink *linkAnnotation = (PDFAnnotationLink *)annotation;
483 #pragma clang diagnostic pop
484 NSURL *url = [linkAnnotation URL];
485 CGRect transformedRect = CGRectApplyAffineTransform(NSRectToCGRect([linkAnnotation bounds]), transform);
488 PDFDestination *destination = [linkAnnotation destination];
491 CGPDFContextSetDestinationForRect(context, linkDestinationName(pdfDocument, destination), transformedRect);
496 CGPDFContextSetURLForRect(context, (CFURLRef)url, transformedRect);
499 CGContextRestoreGState(context);
502 - (void)_drawPreview:(NSRect)nsRect
504 ASSERT(RunLoop::isMain());
506 WebCore::IntRect scaledPrintingRect(nsRect);
507 scaledPrintingRect.scale(1 / _totalScaleFactorForPrinting);
508 WebCore::IntSize imageSize(nsRect.size);
509 imageSize.scale(_webFrame->page()->deviceScaleFactor());
510 HashMap<WebCore::IntRect, RefPtr<WebKit::ShareableBitmap>>::iterator pagePreviewIterator = _pagePreviews.find(scaledPrintingRect);
511 if (pagePreviewIterator == _pagePreviews.end()) {
512 // It's too early to ask for page preview if we don't even know page size and scale.
513 if ([self _hasPageRects]) {
514 if (uint64_t existingCallback = [self _expectedPreviewCallbackForRect:scaledPrintingRect]) {
515 // We've already asked for a preview of this page, and are waiting for response.
516 // There is no need to ask again.
517 _latestExpectedPreviewCallback = existingCallback;
519 // Preview isn't available yet, request it asynchronously.
520 if (!_webFrame->page())
523 // Return to printing mode if we're already back to screen (e.g. due to window resizing).
524 _webFrame->page()->beginPrinting(_webFrame.get(), WebKit::PrintInfo([_printOperation printInfo]));
526 IPCCallbackContext* context = new IPCCallbackContext;
527 auto callback = WebKit::ImageCallback::create([context](const WebKit::ShareableBitmap::Handle& imageHandle, WebKit::CallbackBase::Error) {
528 std::unique_ptr<IPCCallbackContext> contextDeleter(context);
529 pageDidDrawToImage(imageHandle, context);
531 _latestExpectedPreviewCallback = callback->callbackID().toInteger();
532 _expectedPreviewCallbacks.add(_latestExpectedPreviewCallback, scaledPrintingRect);
534 context->view = self;
535 context->callbackID = callback->callbackID().toInteger();
537 _webFrame->page()->drawRectToImage(_webFrame.get(), WebKit::PrintInfo([_printOperation printInfo]), scaledPrintingRect, imageSize, WTFMove(callback));
542 // FIXME: Draw a placeholder
546 RefPtr<WebKit::ShareableBitmap> bitmap = pagePreviewIterator->value;
547 #pragma clang diagnostic push
548 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
549 CGContextRef cgContext = static_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]);
550 #pragma clang diagnostic pop
552 WebCore::GraphicsContext context(cgContext);
553 WebCore::GraphicsContextStateSaver stateSaver(context);
555 bitmap->paint(context, _webFrame->page()->deviceScaleFactor(), WebCore::IntPoint(nsRect.origin), bitmap->bounds());
558 - (void)drawRect:(NSRect)nsRect
560 LOG(Printing, "WKPrintingView %p printing rect x:%g, y:%g, width:%g, height:%g%s", self, nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height, [self _isPrintingPreview] ? " for preview" : "");
562 ASSERT(_printOperation == [NSPrintOperation currentOperation]);
564 auto* page = _webFrame->page();
568 WebCore::LocalDefaultSystemAppearance localAppearance(page->useSystemAppearance(), page->useDarkAppearance());
570 if ([self _isPrintingPreview]) {
571 [self _drawPreview:nsRect];
575 ASSERT(!RunLoop::isMain());
576 ASSERT(!_printedPagesData.isEmpty()); // Prepared by knowsPageRange:
578 if (!_printedPagesPDFDocument) {
579 RetainPtr<NSData> pdfData = adoptNS([[NSData alloc] initWithBytes:_printedPagesData.data() length:_printedPagesData.size()]);
580 _printedPagesPDFDocument = adoptNS([[WebKit::pdfDocumentClass() alloc] initWithData:pdfData.get()]);
582 unsigned pageCount = [_printedPagesPDFDocument pageCount];
583 _linkDestinationsPerPage.clear();
584 _linkDestinationsPerPage.resize(pageCount);
585 for (unsigned i = 0; i < pageCount; i++) {
586 PDFPage *page = [_printedPagesPDFDocument pageAtIndex:i];
587 for (PDFAnnotation *annotation in page.annotations) {
588 if (![annotation isKindOfClass:WebKit::pdfAnnotationLinkClass()])
591 #pragma clang diagnostic push
592 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
593 PDFAnnotationLink *linkAnnotation = (PDFAnnotationLink *)annotation;
594 #pragma clang diagnostic pop
595 if (linkAnnotation.URL)
598 PDFDestination *destination = linkAnnotation.destination;
602 unsigned destinationPageIndex = [_printedPagesPDFDocument indexForPage:destination.page];
603 _linkDestinationsPerPage[destinationPageIndex].append(destination);
608 unsigned printedPageNumber = [self _pageForRect:nsRect] - [self _firstPrintedPageNumber];
609 [self _drawPDFDocument:_printedPagesPDFDocument.get() page:printedPageNumber atPoint:NSMakePoint(nsRect.origin.x, nsRect.origin.y)];
612 - (void)_drawPageBorderWithSizeOnMainThread:(NSSize)borderSize
614 ASSERT(RunLoop::isMain());
616 // When printing from a secondary thread, the main thread doesn't have graphics context and printing operation set up.
617 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
618 [NSGraphicsContext setCurrentContext:[_printOperation context]];
620 ASSERT(![NSPrintOperation currentOperation]);
621 [NSPrintOperation setCurrentOperation:_printOperation];
623 [self drawPageBorderWithSize:borderSize];
625 [NSPrintOperation setCurrentOperation:nil];
626 [NSGraphicsContext setCurrentContext:currentContext];
629 - (void)drawPageBorderWithSize:(NSSize)borderSize
631 ASSERT(NSEqualSizes(borderSize, [[_printOperation printInfo] paperSize]));
632 ASSERT(_printOperation == [NSPrintOperation currentOperation]);
634 if (!RunLoop::isMain()) {
635 // Don't call the client from a secondary thread.
636 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[WKPrintingView instanceMethodSignatureForSelector:@selector(_drawPageBorderWithSizeOnMainThread:)]];
637 [invocation setSelector:@selector(_drawPageBorderWithSizeOnMainThread:)];
638 [invocation setArgument:&borderSize atIndex:2];
639 [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:self waitUntilDone:YES];
643 if (!_webFrame->page())
646 // The header and footer rect height scales with the page, but the width is always
647 // all the way across the printed page (inset by printing margins).
648 NSPrintInfo *printInfo = [_printOperation printInfo];
649 CGFloat scale = [printInfo scalingFactor];
650 NSSize paperSize = [printInfo paperSize];
651 CGFloat headerFooterLeft = [printInfo leftMargin] / scale;
652 CGFloat headerFooterWidth = (paperSize.width - ([printInfo leftMargin] + [printInfo rightMargin])) / scale;
653 NSRect footerRect = NSMakeRect(headerFooterLeft, [printInfo bottomMargin] / scale - _webFrame->page()->footerHeight(*_webFrame), headerFooterWidth, _webFrame->page()->footerHeight(*_webFrame));
654 NSRect headerRect = NSMakeRect(headerFooterLeft, (paperSize.height - [printInfo topMargin]) / scale, headerFooterWidth, _webFrame->page()->headerHeight(*_webFrame));
656 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
657 [currentContext saveGraphicsState];
658 NSRectClip(headerRect);
659 _webFrame->page()->drawHeader(*_webFrame, headerRect);
660 [currentContext restoreGraphicsState];
662 [currentContext saveGraphicsState];
663 NSRectClip(footerRect);
664 _webFrame->page()->drawFooter(*_webFrame, footerRect);
665 [currentContext restoreGraphicsState];
668 - (NSRect)rectForPage:(NSInteger)page
670 ASSERT(_printOperation == [NSPrintOperation currentOperation]);
671 if (![self _hasPageRects]) {
672 LOG(Printing, "-[WKPrintingView %p rectForPage:%d] - data is not yet available", self, (int)page);
673 if (!_webFrame->page()) {
674 // We may have not told AppKit how many pages there are, so it will try to print until a null rect is returned.
675 return NSMakeRect(0, 0, 0, 0);
677 // We must be still calculating the page range.
678 ASSERT(_expectedComputedPagesCallback);
679 return NSMakeRect(0, 0, 1, 1);
682 // If Web process crashes while computing page rects, we never tell AppKit how many pages there are.
683 // Returning a null rect prevents selecting non-existent pages in preview dialog.
684 if (static_cast<unsigned>(page) > _printingPageRects.size()) {
685 ASSERT(!_webFrame->page());
686 return NSMakeRect(0, 0, 0, 0);
689 WebCore::IntRect rect = _printingPageRects[page - 1];
690 rect.scale(_totalScaleFactorForPrinting);
691 LOG(Printing, "-[WKPrintingView %p rectForPage:%d] -> x %d, y %d, width %d, height %d", self, (int)page, rect.x(), rect.y(), rect.width(), rect.height());
695 // Temporary workaround for <rdar://problem/8944535>. Force correct printout positioning.
696 - (NSPoint)locationOfPrintRect:(NSRect)aRect
698 ASSERT(_printOperation == [NSPrintOperation currentOperation]);
699 return NSMakePoint([[_printOperation printInfo] leftMargin], [[_printOperation printInfo] bottomMargin]);
702 - (void)beginDocument
704 ASSERT(_printOperation == [NSPrintOperation currentOperation]);
706 // Forcing preview update gets us here, but page setup hasn't actually changed.
707 if (isForcingPreviewUpdate)
710 LOG(Printing, "-[WKPrintingView %p beginDocument]", self);
712 [super beginDocument];
714 [self _suspendAutodisplay];
719 ASSERT(_printOperation == [NSPrintOperation currentOperation]);
721 // Forcing preview update gets us here, but page setup hasn't actually changed.
722 if (isForcingPreviewUpdate)
725 LOG(Printing, "-[WKPrintingView %p endDocument] - clearing cached data", self);
727 // Both existing data and pending responses are now obsolete.
728 _printingPageRects.clear();
729 _totalScaleFactorForPrinting = 1;
730 _pagePreviews.clear();
731 _printedPagesData.clear();
732 _printedPagesPDFDocument = nullptr;
733 _expectedComputedPagesCallback = 0;
734 _expectedPreviewCallbacks.clear();
735 _latestExpectedPreviewCallback = 0;
736 _expectedPrintCallback = 0;
738 [self _delayedResumeAutodisplay];
744 #endif // PLATFORM(MAC)