WKPrintingView.mm   [plain text]

/*
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/

#import "config.h"
#import "WKPrintingView.h"

#if PLATFORM(MAC)

#import "APIData.h"
#import "Logging.h"
#import "PDFKitImports.h"
#import "PrintInfo.h"
#import "ShareableBitmap.h"
#import "WebPageProxy.h"
#import <Quartz/Quartz.h>
#import <WebCore/GraphicsContext.h>
#import <WebCore/LocalDefaultSystemAppearance.h>
#import <WebCore/WebCoreObjCExtras.h>
#import <wtf/RunLoop.h>

NSString * const WebKitOriginalTopPrintingMarginKey = @"WebKitOriginalTopMargin";
NSString * const WebKitOriginalBottomPrintingMarginKey = @"WebKitOriginalBottomMargin";

NSString * const NSPrintInfoDidChangeNotification = @"NSPrintInfoDidChange";

static BOOL isForcingPreviewUpdate;

@implementation WKPrintingView

- (id)initWithFrameProxy:(WebKit::WebFrameProxy&)frame view:(NSView *)wkView
{
self = [super init]; // No frame rect to pass to NSView.
if (!self)
return nil;

_webFrame = &frame;
_wkView = wkView;

return self;
}

- (void)dealloc
{
callOnMainThread([frame = WTFMove(_webFrame), previews = WTFMove(_pagePreviews)] {
// Deallocate these on the main thread, not the current thread, since the
// reference counting and the destructors aren't threadsafe.
});

[super dealloc];
}

- (BOOL)isFlipped
{
return YES;
}

- (void)_setAutodisplay:(BOOL)newState
{
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
if (!newState && [[_wkView window] isAutodisplay])
[_wkView displayIfNeeded];
ALLOW_DEPRECATED_DECLARATIONS_END

ALLOW_DEPRECATED_DECLARATIONS_BEGIN
[[_wkView window] setAutodisplay:newState];
ALLOW_DEPRECATED_DECLARATIONS_END

// For some reason, painting doesn't happen for a long time without this call, <rdar://problem/8975229>.
if (newState)
[_wkView displayIfNeeded];
}

- (void)_suspendAutodisplay
{
// A drawRect: call on WKView causes a switch to screen mode, which is slow due to relayout, and we want to avoid that.
// Disabling autodisplay will prevent random updates from causing this, but resizing the window will still work.
if (_autodisplayResumeTimer) {
[_autodisplayResumeTimer invalidate];
_autodisplayResumeTimer = nil;
} else
[self _setAutodisplay:NO];
}

- (void)_delayedResumeAutodisplayTimerFired
{
ASSERT(RunLoop::isMain());

_autodisplayResumeTimer = nil;
[self _setAutodisplay:YES];

// Enabling autodisplay normally implicitly calls endPrinting() via -[WKView drawRect:], but not when content is in accelerated compositing mode.
if (_webFrame->page())
_webFrame->page()->endPrinting();
}

- (void)_delayedResumeAutodisplay
{
// AppKit calls endDocument/beginDocument when print option change. We don't want to switch between print and screen mode just for that,
// and enabling autodisplay may result in switching into screen mode. So, autodisplay is only resumed on next run loop iteration.
if (!_autodisplayResumeTimer) {
_autodisplayResumeTimer = [NSTimer timerWithTimeInterval:0 target:self selector:@selector(_delayedResumeAutodisplayTimerFired) userInfo:nil repeats:NO];
// The timer must be scheduled on main thread, because printing thread may finish before it fires.
[[NSRunLoop mainRunLoop] addTimer:_autodisplayResumeTimer forMode:NSDefaultRunLoopMode];
}
}

- (void)_adjustPrintingMarginsForHeaderAndFooter
{
ASSERT(RunLoop::isMain()); // This function calls the client, which should only be done on main thread.

NSPrintInfo *info = [_printOperation printInfo];
NSMutableDictionary *infoDictionary = [info dictionary];

// We need to modify the top and bottom margins in the NSPrintInfo to account for the space needed by the
// header and footer. Because this method can be called more than once on the same NSPrintInfo (see 5038087),
// we stash away the unmodified top and bottom margins the first time this method is called, and we read from
// those stashed-away values on subsequent calls.
double originalTopMargin;
double originalBottomMargin;
NSNumber *originalTopMarginNumber = [infoDictionary objectForKey:WebKitOriginalTopPrintingMarginKey];
if (!originalTopMarginNumber) {
ASSERT(![infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey]);
originalTopMargin = [info topMargin];
originalBottomMargin = [info bottomMargin];
[infoDictionary setObject:[NSNumber numberWithDouble:originalTopMargin] forKey:WebKitOriginalTopPrintingMarginKey];
[infoDictionary setObject:[NSNumber numberWithDouble:originalBottomMargin] forKey:WebKitOriginalBottomPrintingMarginKey];
} else {
ASSERT([originalTopMarginNumber isKindOfClass:[NSNumber class]]);
ASSERT([[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] isKindOfClass:[NSNumber class]]);
originalTopMargin = [originalTopMarginNumber doubleValue];
originalBottomMargin = [[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] doubleValue];
}

CGFloat scale = [info scalingFactor];
[info setTopMargin:originalTopMargin + _webFrame->page()->headerHeight(*_webFrame) * scale];
[info setBottomMargin:originalBottomMargin + _webFrame->page()->footerHeight(*_webFrame) * scale];
}

- (BOOL)_isPrintingPreview
{
// <rdar://problem/8901041> Please add an API returning whether the current print operation is for preview.
// Assuming that if NSPrintOperation is allowed to spawn a thread for printing, it will. Print preview doesn't spawn a thread.
return !_isPrintingFromSecondaryThread;
}

- (void)_updatePreview
{
// <rdar://problem/8900923> Please add an API to force print preview update.
ASSERT(!isForcingPreviewUpdate);
isForcingPreviewUpdate = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:NSPrintInfoDidChangeNotification object:nil];
isForcingPreviewUpdate = NO;
}

- (BOOL)_hasPageRects
{
// WebCore always prints at least one page.
return !_printingPageRects.isEmpty();
}

- (NSUInteger)_firstPrintedPageNumber
{
// Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion.
return [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue];
}

- (NSUInteger)_lastPrintedPageNumber
{
ASSERT([self _hasPageRects]);

// Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion.
NSUInteger firstPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue];
NSUInteger lastPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintLastPage] unsignedIntegerValue];
if (lastPage - firstPage >= _printingPageRects.size())
return _printingPageRects.size();
return lastPage;
}

- (uint64_t)_expectedPreviewCallbackForRect:(const WebCore::IntRect&)rect
{
for (HashMap<uint64_t, WebCore::IntRect>::iterator iter = _expectedPreviewCallbacks.begin(); iter != _expectedPreviewCallbacks.end(); ++iter) {
if (iter->value == rect)
return iter->key;
}
return 0;
}

struct IPCCallbackContext {
RetainPtr<WKPrintingView> view;
uint64_t callbackID;
};

static void pageDidDrawToImage(const WebKit::ShareableBitmap::Handle& imageHandle, IPCCallbackContext* context)
{
ASSERT(RunLoop::isMain());

WKPrintingView *view = context->view.get();

// 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,
// then the user has already moved to another page - we'll cache the response, but won't draw it.
HashMap<uint64_t, WebCore::IntRect>::iterator iter = view->_expectedPreviewCallbacks.find(context->callbackID);
if (iter != view->_expectedPreviewCallbacks.end()) {
ASSERT([view _isPrintingPreview]);

if (!imageHandle.isNull()) {
auto image = WebKit::ShareableBitmap::create(imageHandle, WebKit::SharedMemory::Protection::ReadOnly);

if (image)
view->_pagePreviews.add(iter->value, image);
}

view->_expectedPreviewCallbacks.remove(context->callbackID);
bool receivedResponseToLatestRequest = view->_latestExpectedPreviewCallback == context->callbackID;
if (receivedResponseToLatestRequest) {
view->_latestExpectedPreviewCallback = 0;
[view _updatePreview];
}
}
}

- (void)_preparePDFDataForPrintingOnSecondaryThread
{
ASSERT(RunLoop::isMain());

if (!_webFrame->page()) {
_printingCallbackCondition.notifyOne();
return;
}

std::lock_guard<Lock> lock(_printingCallbackMutex);

ASSERT([self _hasPageRects]);
ASSERT(_printedPagesData.isEmpty());
ASSERT(!_printedPagesPDFDocument);
ASSERT(!_expectedPrintCallback);

NSUInteger firstPage = [self _firstPrintedPageNumber];
NSUInteger lastPage = [self _lastPrintedPageNumber];

ASSERT(firstPage > 0);
ASSERT(firstPage <= lastPage);
LOG(Printing, "WKPrintingView requesting PDF data for pages %u...%u", firstPage, lastPage);

WebKit::PrintInfo printInfo([_printOperation printInfo]);
// Return to printing mode if we're already back to screen (e.g. due to window resizing).
_webFrame->page()->beginPrinting(_webFrame.get(), printInfo);

IPCCallbackContext* context = new IPCCallbackContext;
auto callback = WebKit::DataCallback::create([context](API::Data* data, WebKit::CallbackBase::Error) {
ASSERT(RunLoop::isMain());

std::unique_ptr<IPCCallbackContext> contextDeleter(context);
WKPrintingView *view = context->view.get();

if (context->callbackID == view->_expectedPrintCallback) {
ASSERT(![view _isPrintingPreview]);
ASSERT(view->_printedPagesData.isEmpty());
ASSERT(!view->_printedPagesPDFDocument);
if (data)
view->_printedPagesData.append(data->bytes(), data->size());
view->_expectedPrintCallback = 0;
view->_printingCallbackCondition.notifyOne();
}
});
_expectedPrintCallback = callback->callbackID().toInteger();

context->view = self;
context->callbackID = callback->callbackID().toInteger();

_webFrame->page()->drawPagesToPDF(_webFrame.get(), printInfo, firstPage - 1, lastPage - firstPage + 1, WTFMove(callback));
}

static void pageDidComputePageRects(const Vector<WebCore::IntRect>& pageRects, double totalScaleFactorForPrinting, const WebCore::FloatBoxExtent& computedPageMargin, IPCCallbackContext* context)
{
ASSERT(RunLoop::isMain());

WKPrintingView *view = context->view.get();

// If the user has already changed print setup, then this response is obsolete.
if (context->callbackID == view->_expectedComputedPagesCallback) {
ASSERT(RunLoop::isMain());
ASSERT(view->_expectedPreviewCallbacks.isEmpty());
ASSERT(!view->_latestExpectedPreviewCallback);
ASSERT(!view->_expectedPrintCallback);
ASSERT(view->_pagePreviews.isEmpty());
view->_expectedComputedPagesCallback = 0;

view->_printingPageRects = pageRects;
view->_totalScaleFactorForPrinting = totalScaleFactorForPrinting;

// Sanitize a response coming from the Web process.
if (view->_printingPageRects.isEmpty())
view->_printingPageRects.append(WebCore::IntRect(0, 0, 1, 1));
if (view->_totalScaleFactorForPrinting <= 0)
view->_totalScaleFactorForPrinting = 1;

const WebCore::IntRect& lastPrintingPageRect = view->_printingPageRects[view->_printingPageRects.size() - 1];
NSRect newFrameSize = NSMakeRect(0, 0,
ceil(lastPrintingPageRect.maxX() * view->_totalScaleFactorForPrinting),
ceil(lastPrintingPageRect.maxY() * view->_totalScaleFactorForPrinting));
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);
[view setFrame:newFrameSize];
// Set @page margin.
auto *printInfo = [view->_printOperation printInfo];
[printInfo setTopMargin:computedPageMargin.top()];
[printInfo setBottomMargin:computedPageMargin.bottom()];
[printInfo setLeftMargin:computedPageMargin.left()];
[printInfo setRightMargin:computedPageMargin.right()];

if ([view _isPrintingPreview]) {
// Show page count, and ask for an actual image to replace placeholder.
[view _updatePreview];
} else {
// When printing, request everything we'll need beforehand.
[view _preparePDFDataForPrintingOnSecondaryThread];
}
}
}

- (BOOL)_askPageToComputePageRects
{
ASSERT(RunLoop::isMain());

if (!_webFrame->page())
return NO;

ASSERT(!_expectedComputedPagesCallback);

IPCCallbackContext* context = new IPCCallbackContext;
auto callback = WebKit::ComputedPagesCallback::create([context](const Vector<WebCore::IntRect>& pageRects, double totalScaleFactorForPrinting, const WebCore::FloatBoxExtent& computedPageMargin, WebKit::CallbackBase::Error) {
std::unique_ptr<IPCCallbackContext> contextDeleter(context);
pageDidComputePageRects(pageRects, totalScaleFactorForPrinting, computedPageMargin, context);
});
_expectedComputedPagesCallback = callback->callbackID().toInteger();
context->view = self;
context->callbackID = _expectedComputedPagesCallback;

_webFrame->page()->computePagesForPrinting(_webFrame.get(), WebKit::PrintInfo([_printOperation printInfo]), WTFMove(callback));
return YES;
}

static void prepareDataForPrintingOnSecondaryThread(WKPrintingView *view)
{
ASSERT(RunLoop::isMain());

std::lock_guard<Lock> lock(view->_printingCallbackMutex);

// We may have received page rects while a message to call this function traveled from secondary thread to main one.
if ([view _hasPageRects]) {
[view _preparePDFDataForPrintingOnSecondaryThread];
return;
}

// A request for pages has already been made, just wait for it to finish.
if (view->_expectedComputedPagesCallback)
return;

[view _askPageToComputePageRects];
}

- (BOOL)knowsPageRange:(NSRangePointer)range
{
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");
ASSERT(_printOperation == [NSPrintOperation currentOperation]);

// Assuming that once we switch to printing from a secondary thread, we don't go back.
ASSERT(!_isPrintingFromSecondaryThread || !RunLoop::isMain());
if (!RunLoop::isMain())
_isPrintingFromSecondaryThread = YES;

if (!_webFrame->page()) {
*range = NSMakeRange(1, NSIntegerMax);
return YES;
}

[self _suspendAutodisplay];

[self performSelectorOnMainThread:@selector(_adjustPrintingMarginsForHeaderAndFooter) withObject:nil waitUntilDone:YES];

if ([self _hasPageRects])
*range = NSMakeRange(1, _printingPageRects.size());
else if (!RunLoop::isMain()) {
ASSERT(![self _isPrintingPreview]);
std::unique_lock<Lock> lock(_printingCallbackMutex);

RunLoop::main().dispatch([self] {
prepareDataForPrintingOnSecondaryThread(self);
});

_printingCallbackCondition.wait(lock);
*range = NSMakeRange(1, _printingPageRects.size());
} else {
ASSERT([self _isPrintingPreview]);

// If a request for pages hasn't already been made, make it now.
if (!_expectedComputedPagesCallback)
[self _askPageToComputePageRects];

*range = NSMakeRange(1, NSIntegerMax);
}
return YES;
}

- (unsigned)_pageForRect:(NSRect)rect
{
// Assuming that rect exactly matches one of the pages.
for (size_t i = 0; i < _printingPageRects.size(); ++i) {
WebCore::IntRect currentRect(_printingPageRects[i]);
currentRect.scale(_totalScaleFactorForPrinting);
if (rect.origin.y == currentRect.y() && rect.origin.x == currentRect.x())
return i + 1;
}
ASSERT_NOT_REACHED();
return 0; // Invalid page number.
}

static NSString *linkDestinationName(PDFDocument *document, PDFDestination *destination)
{
return [NSString stringWithFormat:@"%lu-%f-%f", (unsigned long)[document indexForPage:destination.page], destination.point.x, destination.point.y];
}

- (void)_drawPDFDocument:(PDFDocument *)pdfDocument page:(unsigned)page atPoint:(NSPoint)point
{
if (!pdfDocument) {
LOG_ERROR("Couldn't create a PDF document with data passed for preview");
return;
}

PDFPage *pdfPage;
@try {
pdfPage = [pdfDocument pageAtIndex:page];
} @catch (id exception) {
LOG_ERROR("Preview data doesn't have page %d: %@", page, exception);
return;
}

NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext];
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
CGContextRef context = static_cast<CGContextRef>([nsGraphicsContext graphicsPort]);
ALLOW_DEPRECATED_DECLARATIONS_END

CGContextSaveGState(context);
CGContextTranslateCTM(context, point.x, point.y);
CGContextScaleCTM(context, _totalScaleFactorForPrinting, -_totalScaleFactorForPrinting);
CGContextTranslateCTM(context, 0, -[pdfPage boundsForBox:kPDFDisplayBoxMediaBox].size.height);
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
[pdfPage drawWithBox:kPDFDisplayBoxMediaBox];
ALLOW_DEPRECATED_DECLARATIONS_END

CGAffineTransform transform = CGContextGetCTM(context);

for (const auto& destination : _linkDestinationsPerPage[page]) {
CGPoint destinationPoint = CGPointApplyAffineTransform(NSPointToCGPoint([destination point]), transform);
CGPDFContextAddDestinationAtPoint(context, (__bridge CFStringRef)linkDestinationName(pdfDocument, destination.get()), destinationPoint);
}

for (PDFAnnotation *annotation in [pdfPage annotations]) {
if (![annotation isKindOfClass:WebKit::pdfAnnotationLinkClass()])
continue;

ALLOW_DEPRECATED_DECLARATIONS_BEGIN
PDFAnnotationLink *linkAnnotation = (PDFAnnotationLink *)annotation;
ALLOW_DEPRECATED_DECLARATIONS_END
NSURL *url = [linkAnnotation URL];
CGRect transformedRect = CGRectApplyAffineTransform(NSRectToCGRect([linkAnnotation bounds]), transform);

if (!url) {
PDFDestination *destination = [linkAnnotation destination];
if (!destination)
continue;
CGPDFContextSetDestinationForRect(context, (__bridge CFStringRef)linkDestinationName(pdfDocument, destination), transformedRect);
continue;
}

CGPDFContextSetURLForRect(context, (CFURLRef)url, transformedRect);
}

CGContextRestoreGState(context);
}

- (void)_drawPreview:(NSRect)nsRect
{
ASSERT(RunLoop::isMain());

WebCore::IntRect scaledPrintingRect(nsRect);
scaledPrintingRect.scale(1 / _totalScaleFactorForPrinting);
WebCore::IntSize imageSize(nsRect.size);
imageSize.scale(_webFrame->page()->deviceScaleFactor());
HashMap<WebCore::IntRect, RefPtr<WebKit::ShareableBitmap>>::iterator pagePreviewIterator = _pagePreviews.find(scaledPrintingRect);
if (pagePreviewIterator == _pagePreviews.end()) {
// It's too early to ask for page preview if we don't even know page size and scale.
if ([self _hasPageRects]) {
if (uint64_t existingCallback = [self _expectedPreviewCallbackForRect:scaledPrintingRect]) {
// We've already asked for a preview of this page, and are waiting for response.
// There is no need to ask again.
_latestExpectedPreviewCallback = existingCallback;
} else {
// Preview isn't available yet, request it asynchronously.
if (!_webFrame->page())
return;

// Return to printing mode if we're already back to screen (e.g. due to window resizing).
_webFrame->page()->beginPrinting(_webFrame.get(), WebKit::PrintInfo([_printOperation printInfo]));

IPCCallbackContext* context = new IPCCallbackContext;
auto callback = WebKit::ImageCallback::create([context](const WebKit::ShareableBitmap::Handle& imageHandle, WebKit::CallbackBase::Error) {
std::unique_ptr<IPCCallbackContext> contextDeleter(context);
pageDidDrawToImage(imageHandle, context);
});
_latestExpectedPreviewCallback = callback->callbackID().toInteger();
_expectedPreviewCallbacks.add(_latestExpectedPreviewCallback, scaledPrintingRect);

context->view = self;
context->callbackID = callback->callbackID().toInteger();

_webFrame->page()->drawRectToImage(_webFrame.get(), WebKit::PrintInfo([_printOperation printInfo]), scaledPrintingRect, imageSize, WTFMove(callback));
return;
}
}

// FIXME: Draw a placeholder
return;
}

RefPtr<WebKit::ShareableBitmap> bitmap = pagePreviewIterator->value;
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
CGContextRef cgContext = static_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]);
ALLOW_DEPRECATED_DECLARATIONS_END

WebCore::GraphicsContext context(cgContext);
WebCore::GraphicsContextStateSaver stateSaver(context);

bitmap->paint(context, _webFrame->page()->deviceScaleFactor(), WebCore::IntPoint(nsRect.origin), bitmap->bounds());
}

- (void)drawRect:(NSRect)nsRect
{
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" : "");

ASSERT(_printOperation == [NSPrintOperation currentOperation]);

// Always use the light appearance when printing.
WebCore::LocalDefaultSystemAppearance localAppearance(false);

if ([self _isPrintingPreview]) {
[self _drawPreview:nsRect];
return;
}

ASSERT(!RunLoop::isMain());
ASSERT(!_printedPagesData.isEmpty()); // Prepared by knowsPageRange:

if (!_printedPagesPDFDocument) {
RetainPtr<NSData> pdfData = adoptNS([[NSData alloc] initWithBytes:_printedPagesData.data() length:_printedPagesData.size()]);
_printedPagesPDFDocument = adoptNS([[WebKit::pdfDocumentClass() alloc] initWithData:pdfData.get()]);

unsigned pageCount = [_printedPagesPDFDocument pageCount];
_linkDestinationsPerPage.clear();
_linkDestinationsPerPage.resize(pageCount);
for (unsigned i = 0; i < pageCount; i++) {
PDFPage *page = [_printedPagesPDFDocument pageAtIndex:i];
for (PDFAnnotation *annotation in page.annotations) {
if (![annotation isKindOfClass:WebKit::pdfAnnotationLinkClass()])
continue;

ALLOW_DEPRECATED_DECLARATIONS_BEGIN
PDFAnnotationLink *linkAnnotation = (PDFAnnotationLink *)annotation;
ALLOW_DEPRECATED_DECLARATIONS_END
if (linkAnnotation.URL)
continue;

PDFDestination *destination = linkAnnotation.destination;
if (!destination)
continue;

unsigned destinationPageIndex = [_printedPagesPDFDocument indexForPage:destination.page];
_linkDestinationsPerPage[destinationPageIndex].append(destination);
}
}
}

unsigned printedPageNumber = [self _pageForRect:nsRect] - [self _firstPrintedPageNumber];
[self _drawPDFDocument:_printedPagesPDFDocument.get() page:printedPageNumber atPoint:NSMakePoint(nsRect.origin.x, nsRect.origin.y)];
}

- (void)_drawPageBorderWithSizeOnMainThread:(NSSize)borderSize
{
ASSERT(RunLoop::isMain());

// When printing from a secondary thread, the main thread doesn't have graphics context and printing operation set up.
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
[NSGraphicsContext setCurrentContext:[_printOperation context]];

ASSERT(![NSPrintOperation currentOperation]);
[NSPrintOperation setCurrentOperation:_printOperation];

[self drawPageBorderWithSize:borderSize];

[NSPrintOperation setCurrentOperation:nil];
[NSGraphicsContext setCurrentContext:currentContext];
}

- (void)drawPageBorderWithSize:(NSSize)borderSize
{
ASSERT(NSEqualSizes(borderSize, [[_printOperation printInfo] paperSize]));
ASSERT(_printOperation == [NSPrintOperation currentOperation]);

if (!RunLoop::isMain()) {
// Don't call the client from a secondary thread.
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[WKPrintingView instanceMethodSignatureForSelector:@selector(_drawPageBorderWithSizeOnMainThread:)]];
[invocation setSelector:@selector(_drawPageBorderWithSizeOnMainThread:)];
[invocation setArgument:&borderSize atIndex:2];
[invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:self waitUntilDone:YES];
return;
}

if (!_webFrame->page())
return;

// The header and footer rect height scales with the page, but the width is always
// all the way across the printed page (inset by printing margins).
NSPrintInfo *printInfo = [_printOperation printInfo];
CGFloat scale = [printInfo scalingFactor];
NSSize paperSize = [printInfo paperSize];
CGFloat headerFooterLeft = [printInfo leftMargin] / scale;
CGFloat headerFooterWidth = (paperSize.width - ([printInfo leftMargin] + [printInfo rightMargin])) / scale;
NSRect footerRect = NSMakeRect(headerFooterLeft, [printInfo bottomMargin] / scale - _webFrame->page()->footerHeight(*_webFrame), headerFooterWidth, _webFrame->page()->footerHeight(*_webFrame));
NSRect headerRect = NSMakeRect(headerFooterLeft, (paperSize.height - [printInfo topMargin]) / scale, headerFooterWidth, _webFrame->page()->headerHeight(*_webFrame));

NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
[currentContext saveGraphicsState];
NSRectClip(headerRect);
_webFrame->page()->drawHeader(*_webFrame, headerRect);
[currentContext restoreGraphicsState];

[currentContext saveGraphicsState];
NSRectClip(footerRect);
_webFrame->page()->drawFooter(*_webFrame, footerRect);
[currentContext restoreGraphicsState];
}

- (NSRect)rectForPage:(NSInteger)page
{
ASSERT(_printOperation == [NSPrintOperation currentOperation]);
if (![self _hasPageRects]) {
LOG(Printing, "-[WKPrintingView %p rectForPage:%d] - data is not yet available", self, (int)page);
if (!_webFrame->page()) {
// We may have not told AppKit how many pages there are, so it will try to print until a null rect is returned.
return NSMakeRect(0, 0, 0, 0);
}
// We must be still calculating the page range.
ASSERT(_expectedComputedPagesCallback);
return NSMakeRect(0, 0, 1, 1);
}

// If Web process crashes while computing page rects, we never tell AppKit how many pages there are.
// Returning a null rect prevents selecting non-existent pages in preview dialog.
if (static_cast<unsigned>(page) > _printingPageRects.size()) {
ASSERT(!_webFrame->page());
return NSMakeRect(0, 0, 0, 0);
}

WebCore::IntRect rect = _printingPageRects[page - 1];
rect.scale(_totalScaleFactorForPrinting);
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());
return rect;
}

// Temporary workaround for <rdar://problem/8944535>. Force correct printout positioning.
- (NSPoint)locationOfPrintRect:(NSRect)aRect
{
ASSERT(_printOperation == [NSPrintOperation currentOperation]);
return NSMakePoint([[_printOperation printInfo] leftMargin], [[_printOperation printInfo] bottomMargin]);
}

- (void)beginDocument
{
ASSERT(_printOperation == [NSPrintOperation currentOperation]);

// Forcing preview update gets us here, but page setup hasn't actually changed.
if (isForcingPreviewUpdate)
return;

LOG(Printing, "-[WKPrintingView %p beginDocument]", self);

[super beginDocument];

[self _suspendAutodisplay];
}

- (void)endDocument
{
ASSERT(_printOperation == [NSPrintOperation currentOperation]);

// Forcing preview update gets us here, but page setup hasn't actually changed.
if (isForcingPreviewUpdate)
return;

LOG(Printing, "-[WKPrintingView %p endDocument] - clearing cached data", self);

// Both existing data and pending responses are now obsolete.
_printingPageRects.clear();
_totalScaleFactorForPrinting = 1;
_pagePreviews.clear();
_printedPagesData.clear();
_printedPagesPDFDocument = nullptr;
_expectedComputedPagesCallback = 0;
_expectedPreviewCallbacks.clear();
_latestExpectedPreviewCallback = 0;
_expectedPrintCallback = 0;

[self _delayedResumeAutodisplay];

[super endDocument];
}
@end

#endif // PLATFORM(MAC)