1 /* 2 * Copyright (C) 2011 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 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. 12 * 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. 24 */ 25 26 #import "config.h" 27 #import "WKPrintingView.h" 28 29 #import "Logging.h" 30 #import "PrintInfo.h" 31 #import "WebData.h" 32 #import "WebPageProxy.h" 33 34 using namespace WebKit; 35 using namespace WebCore; 36 37 NSString * const WebKitOriginalTopPrintingMarginKey = @"WebKitOriginalTopMargin"; 38 NSString * const WebKitOriginalBottomPrintingMarginKey = @"WebKitOriginalBottomMargin"; 39 40 NSString * const NSPrintInfoDidChangeNotification = @"NSPrintInfoDidChange"; 41 42 static BOOL isForcingPreviewUpdate; 43 44 @implementation WKPrintingView 45 46 - (id)initWithFrameProxy:(WebKit::WebFrameProxy*)frame view:(NSView *)wkView 47 { 48 self = [super init]; // No frame rect to pass to NSView. 49 if (!self) 50 return nil; 51 52 _webFrame = frame; 53 _wkView = wkView; 54 55 return self; 56 } 57 58 - (BOOL)isFlipped 59 { 60 return YES; 61 } 62 63 - (void)_setAutodisplay:(BOOL)newState 64 { 65 if (!newState && [[_wkView.get() window] isAutodisplay]) 66 [_wkView.get() displayIfNeeded]; 67 68 [[_wkView.get() window] setAutodisplay:newState]; 69 70 // For some reason, painting doesn't happen for a long time without this call, <rdar://problem/8975229>. 71 if (newState) 72 [_wkView.get() displayIfNeeded]; 73 } 74 75 76 - (void)_suspendAutodisplay 77 { 78 // A drawRect: call on WKView causes a switch to screen mode, which is slow due to relayout, and we want to avoid that. 79 // Disabling autodisplay will prevent random updates from causing this, but resizing the window will still work. 80 if (_autodisplayResumeTimer) { 81 [_autodisplayResumeTimer invalidate]; 82 _autodisplayResumeTimer = nil; 83 } else 84 [self _setAutodisplay:NO]; 85 } 86 87 - (void)_delayedResumeAutodisplayTimerFired 88 { 89 ASSERT(isMainThread()); 90 91 _autodisplayResumeTimer = nil; 92 [self _setAutodisplay:YES]; 93 } 94 95 - (void)_delayedResumeAutodisplay 96 { 97 // AppKit calls endDocument/beginDocument when print option change. We don't want to switch between print and screen mode just for that, 98 // and enabling autodisplay may result in switching into screen mode. So, autodisplay is only resumed on next run loop iteration. 99 if (!_autodisplayResumeTimer) { 100 _autodisplayResumeTimer = [NSTimer timerWithTimeInterval:0 target:self selector:@selector(_delayedResumeAutodisplayTimerFired) userInfo:nil repeats:NO]; 101 // The timer must be scheduled on main thread, because printing thread may finish before it fires. 102 [[NSRunLoop mainRunLoop] addTimer:_autodisplayResumeTimer forMode:NSDefaultRunLoopMode]; 103 } 104 } 105 106 - (void)_adjustPrintingMarginsForHeaderAndFooter 107 { 108 NSPrintInfo *info = [_printOperation printInfo]; 109 NSMutableDictionary *infoDictionary = [info dictionary]; 110 111 // We need to modify the top and bottom margins in the NSPrintInfo to account for the space needed by the 112 // header and footer. Because this method can be called more than once on the same NSPrintInfo (see 5038087), 113 // we stash away the unmodified top and bottom margins the first time this method is called, and we read from 114 // those stashed-away values on subsequent calls. 115 double originalTopMargin; 116 double originalBottomMargin; 117 NSNumber *originalTopMarginNumber = [infoDictionary objectForKey:WebKitOriginalTopPrintingMarginKey]; 118 if (!originalTopMarginNumber) { 119 ASSERT(![infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey]); 120 originalTopMargin = [info topMargin]; 121 originalBottomMargin = [info bottomMargin]; 122 [infoDictionary setObject:[NSNumber numberWithDouble:originalTopMargin] forKey:WebKitOriginalTopPrintingMarginKey]; 123 [infoDictionary setObject:[NSNumber numberWithDouble:originalBottomMargin] forKey:WebKitOriginalBottomPrintingMarginKey]; 124 } else { 125 ASSERT([originalTopMarginNumber isKindOfClass:[NSNumber class]]); 126 ASSERT([[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] isKindOfClass:[NSNumber class]]); 127 originalTopMargin = [originalTopMarginNumber doubleValue]; 128 originalBottomMargin = [[infoDictionary objectForKey:WebKitOriginalBottomPrintingMarginKey] doubleValue]; 129 } 130 131 CGFloat scale = [info scalingFactor]; 132 [info setTopMargin:originalTopMargin + _webFrame->page()->headerHeight(_webFrame.get()) * scale]; 133 [info setBottomMargin:originalBottomMargin + _webFrame->page()->footerHeight(_webFrame.get()) * scale]; 134 } 135 136 - (BOOL)_isPrintingPreview 137 { 138 // <rdar://problem/8901041> Please add an API returning whether the current print operation is for preview. 139 // Assuming that if NSPrintOperation is allowed to spawn a thread for printing, it will. Print preview doesn't spawn a thread. 140 return !_isPrintingFromSecondaryThread; 141 } 142 143 - (void)_updatePreview 144 { 145 // <rdar://problem/8900923> Please add an API to force print preview update. 146 ASSERT(!isForcingPreviewUpdate); 147 isForcingPreviewUpdate = YES; 148 [[NSNotificationCenter defaultCenter] postNotificationName:NSPrintInfoDidChangeNotification object:nil]; 149 isForcingPreviewUpdate = NO; 150 } 151 152 - (BOOL)_hasPageRects 153 { 154 // WebCore always prints at least one page. 155 return !_printingPageRects.isEmpty(); 156 } 157 158 - (NSUInteger)_firstPrintedPageNumber 159 { 160 // Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion. 161 return [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue]; 162 } 163 164 - (NSUInteger)_lastPrintedPageNumber 165 { 166 ASSERT([self _hasPageRects]); 167 168 // Need to directly access the dictionary because -[NSPrintOperation pageRange] verifies pagination, potentially causing recursion. 169 NSUInteger firstPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintFirstPage] unsignedIntegerValue]; 170 NSUInteger lastPage = [[[[_printOperation printInfo] dictionary] objectForKey:NSPrintLastPage] unsignedIntegerValue]; 171 if (lastPage - firstPage >= _printingPageRects.size()) 172 return _printingPageRects.size(); 173 return lastPage; 174 } 175 176 - (uint64_t)_expectedPreviewCallbackForRect:(const IntRect&)rect 177 { 178 for (HashMap<uint64_t, WebCore::IntRect>::iterator iter = _expectedPreviewCallbacks.begin(); iter != _expectedPreviewCallbacks.end(); ++iter) { 179 if (iter->second == rect) 180 return iter->first; 181 } 182 return 0; 183 } 184 185 struct IPCCallbackContext { 186 RetainPtr<WKPrintingView> view; 187 uint64_t callbackID; 188 }; 189 190 static void pageDidDrawToPDF(WKDataRef dataRef, WKErrorRef, void* untypedContext) 191 { 192 ASSERT(isMainThread()); 193 194 OwnPtr<IPCCallbackContext> context = adoptPtr(static_cast<IPCCallbackContext*>(untypedContext)); 195 WKPrintingView *view = context->view.get(); 196 WebData* data = toImpl(dataRef); 197 198 if (context->callbackID == view->_expectedPrintCallback) { 199 ASSERT(![view _isPrintingPreview]); 200 ASSERT(view->_printedPagesData.isEmpty()); 201 ASSERT(!view->_printedPagesPDFDocument); 202 if (data) 203 view->_printedPagesData.append(data->bytes(), data->size()); 204 view->_expectedPrintCallback = 0; 205 view->_printingCallbackCondition.signal(); 206 } else { 207 // If the user has already changed print setup, then this response is obsolete. And this callback is not in response to the latest request, 208 // then the user has already moved to another page - we'll cache the response, but won't draw it. 209 HashMap<uint64_t, WebCore::IntRect>::iterator iter = view->_expectedPreviewCallbacks.find(context->callbackID); 210 if (iter != view->_expectedPreviewCallbacks.end()) { 211 ASSERT([view _isPrintingPreview]); 212 213 if (data) { 214 pair<HashMap<WebCore::IntRect, Vector<uint8_t> >::iterator, bool> entry = view->_pagePreviews.add(iter->second, Vector<uint8_t>()); 215 entry.first->second.append(data->bytes(), data->size()); 216 } 217 view->_expectedPreviewCallbacks.remove(context->callbackID); 218 bool receivedResponseToLatestRequest = view->_latestExpectedPreviewCallback == context->callbackID; 219 if (receivedResponseToLatestRequest) { 220 view->_latestExpectedPreviewCallback = 0; 221 [view _updatePreview]; 222 } 223 } 224 } 225 } 226 227 - (void)_preparePDFDataForPrintingOnSecondaryThread 228 { 229 ASSERT(isMainThread()); 230 231 if (!_webFrame->page()) { 232 _printingCallbackCondition.signal(); 233 return; 234 } 235 236 MutexLocker lock(_printingCallbackMutex); 237 238 ASSERT([self _hasPageRects]); 239 ASSERT(_printedPagesData.isEmpty()); 240 ASSERT(!_printedPagesPDFDocument); 241 ASSERT(!_expectedPrintCallback); 242 243 NSUInteger firstPage = [self _firstPrintedPageNumber]; 244 NSUInteger lastPage = [self _lastPrintedPageNumber]; 245 246 ASSERT(firstPage > 0); 247 ASSERT(firstPage <= lastPage); 248 LOG(View, "WKPrintingView requesting PDF data for pages %u...%u", firstPage, lastPage); 249 250 // Return to printing mode if we're already back to screen (e.g. due to window resizing). 251 _webFrame->page()->beginPrinting(_webFrame.get(), PrintInfo([_printOperation printInfo])); 252 253 IPCCallbackContext* context = new IPCCallbackContext; 254 RefPtr<DataCallback> callback = DataCallback::create(context, pageDidDrawToPDF); 255 _expectedPrintCallback = callback->callbackID(); 256 257 context->view = self; 258 context->callbackID = callback->callbackID(); 259 260 _webFrame->page()->drawPagesToPDF(_webFrame.get(), firstPage - 1, lastPage - firstPage + 1, callback.get()); 261 } 262 263 static void pageDidComputePageRects(const Vector<WebCore::IntRect>& pageRects, double totalScaleFactorForPrinting, WKErrorRef, void* untypedContext) 264 { 265 ASSERT(isMainThread()); 266 267 OwnPtr<IPCCallbackContext> context = adoptPtr(static_cast<IPCCallbackContext*>(untypedContext)); 268 WKPrintingView *view = context->view.get(); 269 270 // If the user has already changed print setup, then this response is obsolete. 271 if (context->callbackID == view->_expectedComputedPagesCallback) { 272 ASSERT(isMainThread()); 273 ASSERT(view->_expectedPreviewCallbacks.isEmpty()); 274 ASSERT(!view->_latestExpectedPreviewCallback); 275 ASSERT(!view->_expectedPrintCallback); 276 ASSERT(view->_pagePreviews.isEmpty()); 277 view->_expectedComputedPagesCallback = 0; 278 279 view->_printingPageRects = pageRects; 280 view->_totalScaleFactorForPrinting = totalScaleFactorForPrinting; 281 282 // Sanitize a response coming from the Web process. 283 if (view->_printingPageRects.isEmpty()) 284 view->_printingPageRects.append(IntRect(0, 0, 1, 1)); 285 if (view->_totalScaleFactorForPrinting <= 0) 286 view->_totalScaleFactorForPrinting = 1; 287 288 const IntRect& lastPrintingPageRect = view->_printingPageRects[view->_printingPageRects.size() - 1]; 289 NSRect newFrameSize = NSMakeRect(0, 0, 290 ceil(lastPrintingPageRect.maxX() * view->_totalScaleFactorForPrinting), 291 ceil(lastPrintingPageRect.maxY() * view->_totalScaleFactorForPrinting)); 292 LOG(View, "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); 293 [view setFrame:newFrameSize]; 294 295 if ([view _isPrintingPreview]) { 296 // Show page count, and ask for an actual image to replace placeholder. 297 [view _updatePreview]; 298 } else { 299 // When printing, request everything we'll need beforehand. 300 [view _preparePDFDataForPrintingOnSecondaryThread]; 301 } 302 } 303 } 304 305 - (BOOL)_askPageToComputePageRects 306 { 307 ASSERT(isMainThread()); 308 309 if (!_webFrame->page()) 310 return NO; 311 312 ASSERT(!_expectedComputedPagesCallback); 313 314 IPCCallbackContext* context = new IPCCallbackContext; 315 RefPtr<ComputedPagesCallback> callback = ComputedPagesCallback::create(context, pageDidComputePageRects); 316 _expectedComputedPagesCallback = callback->callbackID(); 317 context->view = self; 318 context->callbackID = _expectedComputedPagesCallback; 319 320 _webFrame->page()->computePagesForPrinting(_webFrame.get(), PrintInfo([_printOperation printInfo]), callback.release()); 321 return YES; 322 } 323 324 static void prepareDataForPrintingOnSecondaryThread(void* untypedContext) 325 { 326 ASSERT(isMainThread()); 327 328 WKPrintingView *view = static_cast<WKPrintingView *>(untypedContext); 329 MutexLocker lock(view->_printingCallbackMutex); 330 331 // We may have received page rects while a message to call this function traveled from secondary thread to main one. 332 if ([view _hasPageRects]) { 333 [view _preparePDFDataForPrintingOnSecondaryThread]; 334 return; 335 } 336 337 // A request for pages has already been made, just wait for it to finish. 338 if (view->_expectedComputedPagesCallback) 339 return; 340 341 [view _askPageToComputePageRects]; 342 } 343 344 - (BOOL)knowsPageRange:(NSRangePointer)range 345 { 346 LOG(View, "-[WKPrintingView %p knowsPageRange:], %s, %s", self, [self _hasPageRects] ? "print data is available" : "print data is not available yet", isMainThread() ? "on main thread" : "on secondary thread"); 347 ASSERT(_printOperation == [NSPrintOperation currentOperation]); 348 349 // Assuming that once we switch to printing from a secondary thread, we don't go back. 350 ASSERT(!_isPrintingFromSecondaryThread || !isMainThread()); 351 if (!isMainThread()) 352 _isPrintingFromSecondaryThread = YES; 353 354 if (!_webFrame->page()) { 355 *range = NSMakeRange(1, NSIntegerMax); 356 return YES; 357 } 358 359 [self _suspendAutodisplay]; 360 361 [self _adjustPrintingMarginsForHeaderAndFooter]; 362 363 if ([self _hasPageRects]) 364 *range = NSMakeRange(1, _printingPageRects.size()); 365 else if (!isMainThread()) { 366 ASSERT(![self _isPrintingPreview]); 367 MutexLocker lock(_printingCallbackMutex); 368 callOnMainThread(prepareDataForPrintingOnSecondaryThread, self); 369 _printingCallbackCondition.wait(_printingCallbackMutex); 370 *range = NSMakeRange(1, _printingPageRects.size()); 371 } else { 372 ASSERT([self _isPrintingPreview]); 373 374 // If a request for pages hasn't already been made, make it now. 375 if (!_expectedComputedPagesCallback) 376 [self _askPageToComputePageRects]; 377 378 *range = NSMakeRange(1, NSIntegerMax); 379 } 380 return YES; 381 } 382 383 - (unsigned)_pageForRect:(NSRect)rect 384 { 385 // Assuming that rect exactly matches one of the pages. 386 for (size_t i = 0; i < _printingPageRects.size(); ++i) { 387 IntRect currentRect(_printingPageRects[i]); 388 currentRect.scale(_totalScaleFactorForPrinting); 389 if (rect.origin.y == currentRect.y() && rect.origin.x == currentRect.x()) 390 return i + 1; 391 } 392 ASSERT_NOT_REACHED(); 393 return 0; // Invalid page number. 394 } 395 396 - (void)_drawPDFDocument:(CGPDFDocumentRef)pdfDocument page:(unsigned)page atPoint:(NSPoint)point 397 { 398 if (!pdfDocument) { 399 LOG_ERROR("Couldn't create a PDF document with data passed for preview"); 400 return; 401 } 402 403 CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdfDocument, page); 404 if (!pdfPage) { 405 LOG_ERROR("Preview data doesn't have page %d", page); 406 return; 407 } 408 409 NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext]; 410 CGContextRef context = static_cast<CGContextRef>([nsGraphicsContext graphicsPort]); 411 412 CGContextSaveGState(context); 413 CGContextTranslateCTM(context, point.x, point.y); 414 CGContextScaleCTM(context, _totalScaleFactorForPrinting, -_totalScaleFactorForPrinting); 415 CGContextTranslateCTM(context, 0, -CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox).size.height); 416 CGContextDrawPDFPage(context, pdfPage); 417 CGContextRestoreGState(context); 418 } 419 420 - (void)_drawPreview:(NSRect)nsRect 421 { 422 ASSERT(isMainThread()); 423 424 IntRect rect(nsRect); 425 rect.scale(1 / _totalScaleFactorForPrinting); 426 HashMap<WebCore::IntRect, Vector<uint8_t> >::iterator pagePreviewIterator = _pagePreviews.find(rect); 427 if (pagePreviewIterator == _pagePreviews.end()) { 428 // It's too early to ask for page preview if we don't even know page size and scale. 429 if ([self _hasPageRects]) { 430 if (uint64_t existingCallback = [self _expectedPreviewCallbackForRect:rect]) { 431 // We've already asked for a preview of this page, and are waiting for response. 432 // There is no need to ask again. 433 _latestExpectedPreviewCallback = existingCallback; 434 } else { 435 // Preview isn't available yet, request it asynchronously. 436 if (!_webFrame->page()) 437 return; 438 439 // Return to printing mode if we're already back to screen (e.g. due to window resizing). 440 _webFrame->page()->beginPrinting(_webFrame.get(), PrintInfo([_printOperation printInfo])); 441 442 IPCCallbackContext* context = new IPCCallbackContext; 443 RefPtr<DataCallback> callback = DataCallback::create(context, pageDidDrawToPDF); 444 _latestExpectedPreviewCallback = callback->callbackID(); 445 _expectedPreviewCallbacks.add(_latestExpectedPreviewCallback, rect); 446 447 context->view = self; 448 context->callbackID = callback->callbackID(); 449 450 _webFrame->page()->drawRectToPDF(_webFrame.get(), rect, callback.get()); 451 return; 452 } 453 } 454 455 // FIXME: Draw a placeholder 456 return; 457 } 458 459 const Vector<uint8_t>& pdfData = pagePreviewIterator->second; 460 RetainPtr<CGDataProviderRef> pdfDataProvider(AdoptCF, CGDataProviderCreateWithData(0, pdfData.data(), pdfData.size(), 0)); 461 RetainPtr<CGPDFDocumentRef> pdfDocument(AdoptCF, CGPDFDocumentCreateWithProvider(pdfDataProvider.get())); 462 463 [self _drawPDFDocument:pdfDocument.get() page:1 atPoint:NSMakePoint(nsRect.origin.x, nsRect.origin.y)]; 464 } 465 466 - (void)drawRect:(NSRect)nsRect 467 { 468 LOG(View, "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" : ""); 469 470 ASSERT(_printOperation == [NSPrintOperation currentOperation]); 471 472 if (!_webFrame->page()) 473 return; 474 475 if ([self _isPrintingPreview]) { 476 [self _drawPreview:nsRect]; 477 return; 478 } 479 480 ASSERT(!isMainThread()); 481 ASSERT(!_printedPagesData.isEmpty()); // Prepared by knowsPageRange: 482 483 if (!_printedPagesPDFDocument) { 484 RetainPtr<CGDataProviderRef> pdfDataProvider(AdoptCF, CGDataProviderCreateWithData(0, _printedPagesData.data(), _printedPagesData.size(), 0)); 485 _printedPagesPDFDocument.adoptCF(CGPDFDocumentCreateWithProvider(pdfDataProvider.get())); 486 } 487 488 unsigned printedPageNumber = [self _pageForRect:nsRect] - [self _firstPrintedPageNumber] + 1; 489 [self _drawPDFDocument:_printedPagesPDFDocument.get() page:printedPageNumber atPoint:NSMakePoint(nsRect.origin.x, nsRect.origin.y)]; 490 } 491 492 - (void)_drawPageBorderWithSizeOnMainThread:(NSSize)borderSize 493 { 494 ASSERT(isMainThread()); 495 496 // When printing from a secondary thread, the main thread doesn't have graphics context and printing operation set up. 497 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 498 [NSGraphicsContext setCurrentContext:[_printOperation context]]; 499 500 ASSERT(![NSPrintOperation currentOperation]); 501 [NSPrintOperation setCurrentOperation:_printOperation]; 502 503 [self drawPageBorderWithSize:borderSize]; 504 505 [NSPrintOperation setCurrentOperation:nil]; 506 [NSGraphicsContext setCurrentContext:currentContext]; 507 } 508 509 - (void)drawPageBorderWithSize:(NSSize)borderSize 510 { 511 ASSERT(NSEqualSizes(borderSize, [[_printOperation printInfo] paperSize])); 512 ASSERT(_printOperation == [NSPrintOperation currentOperation]); 513 514 if (!isMainThread()) { 515 // Don't call the client from a secondary thread. 516 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[WKPrintingView instanceMethodSignatureForSelector:@selector(_drawPageBorderWithSizeOnMainThread:)]]; 517 [invocation setSelector:@selector(_drawPageBorderWithSizeOnMainThread:)]; 518 [invocation setArgument:&borderSize atIndex:2]; 519 [invocation performSelectorOnMainThread:@selector(invokeWithTarget:) withObject:self waitUntilDone:YES]; 520 return; 521 } 522 523 if (!_webFrame->page()) 524 return; 525 526 // The header and footer rect height scales with the page, but the width is always 527 // all the way across the printed page (inset by printing margins). 528 NSPrintInfo *printInfo = [_printOperation printInfo]; 529 CGFloat scale = [printInfo scalingFactor]; 530 NSSize paperSize = [printInfo paperSize]; 531 CGFloat headerFooterLeft = [printInfo leftMargin] / scale; 532 CGFloat headerFooterWidth = (paperSize.width - ([printInfo leftMargin] + [printInfo rightMargin])) / scale; 533 NSRect footerRect = NSMakeRect(headerFooterLeft, [printInfo bottomMargin] / scale - _webFrame->page()->footerHeight(_webFrame.get()), headerFooterWidth, _webFrame->page()->footerHeight(_webFrame.get())); 534 NSRect headerRect = NSMakeRect(headerFooterLeft, (paperSize.height - [printInfo topMargin]) / scale, headerFooterWidth, _webFrame->page()->headerHeight(_webFrame.get())); 535 536 NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 537 [currentContext saveGraphicsState]; 538 NSRectClip(headerRect); 539 _webFrame->page()->drawHeader(_webFrame.get(), headerRect); 540 [currentContext restoreGraphicsState]; 541 542 [currentContext saveGraphicsState]; 543 NSRectClip(footerRect); 544 _webFrame->page()->drawFooter(_webFrame.get(), footerRect); 545 [currentContext restoreGraphicsState]; 546 } 547 548 - (NSRect)rectForPage:(NSInteger)page 549 { 550 ASSERT(_printOperation == [NSPrintOperation currentOperation]); 551 if (![self _hasPageRects]) { 552 LOG(View, "-[WKPrintingView %p rectForPage:%d] - data is not yet available", self, (int)page); 553 if (!_webFrame->page()) { 554 // We may have not told AppKit how many pages there are, so it will try to print until a null rect is returned. 555 return NSMakeRect(0, 0, 0, 0); 556 } 557 // We must be still calculating the page range. 558 ASSERT(_expectedComputedPagesCallback); 559 return NSMakeRect(0, 0, 1, 1); 560 } 561 562 // If Web process crashes while computing page rects, we never tell AppKit how many pages there are. 563 // Returning a null rect prevents selecting non-existent pages in preview dialog. 564 if (static_cast<unsigned>(page) > _printingPageRects.size()) { 565 ASSERT(!_webFrame->page()); 566 return NSMakeRect(0, 0, 0, 0); 567 } 568 569 IntRect rect = _printingPageRects[page - 1]; 570 rect.scale(_totalScaleFactorForPrinting); 571 LOG(View, "-[WKPrintingView %p rectForPage:%d] -> x %d, y %d, width %d, height %d", self, (int)page, rect.x(), rect.y(), rect.width(), rect.height()); 572 return rect; 573 } 574 575 // Temporary workaround for <rdar://problem/8944535>. Force correct printout positioning. 576 - (NSPoint)locationOfPrintRect:(NSRect)aRect 577 { 578 ASSERT(_printOperation == [NSPrintOperation currentOperation]); 579 return NSMakePoint([[_printOperation printInfo] leftMargin], [[_printOperation printInfo] bottomMargin]); 580 } 581 582 - (void)beginDocument 583 { 584 ASSERT(_printOperation == [NSPrintOperation currentOperation]); 585 586 // Forcing preview update gets us here, but page setup hasn't actually changed. 587 if (isForcingPreviewUpdate) 588 return; 589 590 LOG(View, "-[WKPrintingView %p beginDocument]", self); 591 592 [super beginDocument]; 593 594 [self _suspendAutodisplay]; 595 } 596 597 - (void)endDocument 598 { 599 ASSERT(_printOperation == [NSPrintOperation currentOperation]); 600 601 // Forcing preview update gets us here, but page setup hasn't actually changed. 602 if (isForcingPreviewUpdate) 603 return; 604 605 LOG(View, "-[WKPrintingView %p endDocument] - clearing cached data", self); 606 607 // Both existing data and pending responses are now obsolete. 608 _printingPageRects.clear(); 609 _totalScaleFactorForPrinting = 1; 610 _pagePreviews.clear(); 611 _printedPagesData.clear(); 612 _printedPagesPDFDocument = nullptr; 613 _expectedComputedPagesCallback = 0; 614 _expectedPreviewCallbacks.clear(); 615 _latestExpectedPreviewCallback = 0; 616 _expectedPrintCallback = 0; 617 618 [self _delayedResumeAutodisplay]; 619 620 [super endDocument]; 621 } 622 @end 623