📄 webpdfview.mm
字号:
/* * Copyright (C) 2005, 2006, 2007, 2008 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "WebPDFView.h"#import "WebDataSourceInternal.h"#import "WebDocumentInternal.h"#import "WebDocumentPrivate.h"#import "WebFrame.h"#import "WebFrameInternal.h"#import "WebFrameView.h"#import "WebLocalizableStrings.h"#import "WebNSArrayExtras.h"#import "WebNSAttributedStringExtras.h"#import "WebNSPasteboardExtras.h"#import "WebNSViewExtras.h"#import "WebPDFRepresentation.h"#import "WebPreferencesPrivate.h"#import "WebUIDelegate.h"#import "WebUIDelegatePrivate.h"#import "WebView.h"#import "WebViewInternal.h"#import <PDFKit/PDFKit.h>#import <WebCore/EventNames.h>#import <WebCore/Frame.h>#import <WebCore/FrameLoader.h>#import <WebCore/FrameLoadRequest.h>#import <WebCore/KURL.h>#import <WebCore/KeyboardEvent.h>#import <WebCore/MouseEvent.h>#import <WebCore/PlatformKeyboardEvent.h>#import <wtf/Assertions.h>using namespace WebCore;// Redeclarations of PDFKit notifications. We can't use the API since we use a weak link to the framework.#define _webkit_PDFViewDisplayModeChangedNotification @"PDFViewDisplayModeChanged"#define _webkit_PDFViewScaleChangedNotification @"PDFViewScaleChanged"#define _webkit_PDFViewPageChangedNotification @"PDFViewChangedPage"@interface PDFDocument (PDFKitSecretsIKnow)- (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate;@endextern "C" NSString *_NSPathForSystemFramework(NSString *framework);@interface WebPDFView (FileInternal)+ (Class)_PDFPreviewViewClass;+ (Class)_PDFViewClass;- (BOOL)_anyPDFTagsFoundInMenu:(NSMenu *)menu;- (void)_applyPDFDefaults;- (BOOL)_canLookUpInDictionary;- (NSClipView *)_clipViewForPDFDocumentView;- (NSEvent *)_fakeKeyEventWithFunctionKey:(unichar)functionKey;- (NSMutableArray *)_menuItemsFromPDFKitForEvent:(NSEvent *)theEvent;- (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection;- (void)_openWithFinder:(id)sender;- (NSString *)_path;- (void)_PDFDocumentViewMightHaveScrolled:(NSNotification *)notification;- (BOOL)_pointIsInSelection:(NSPoint)point;- (NSAttributedString *)_scaledAttributedString:(NSAttributedString *)unscaledAttributedString;- (void)_setTextMatches:(NSArray *)array;- (NSString *)_temporaryPDFDirectoryPath;- (void)_trackFirstResponder;- (void)_updatePreferencesSoon;- (NSSet *)_visiblePDFPages;@end;// PDFPrefUpdatingProxy is a class that forwards everything it gets to a target and updates the PDF viewing prefs// after each of those messages. We use it as a way to hook all the places that the PDF viewing attrs change.@interface PDFPrefUpdatingProxy : NSProxy { WebPDFView *view;}- (id)initWithView:(WebPDFView *)view;@end#pragma mark C UTILITY FUNCTIONSstatic void _applicationInfoForMIMEType(NSString *type, NSString **name, NSImage **image){ NSURL *appURL = nil; OSStatus error = LSCopyApplicationForMIMEType((CFStringRef)type, kLSRolesAll, (CFURLRef *)&appURL); if (error != noErr) return; NSString *appPath = [appURL path]; CFRelease (appURL); *image = [[NSWorkspace sharedWorkspace] iconForFile:appPath]; [*image setSize:NSMakeSize(16.f,16.f)]; NSString *appName = [[NSFileManager defaultManager] displayNameAtPath:appPath]; *name = appName;}// FIXME 4182876: We can eliminate this function in favor if -isEqual: if [PDFSelection isEqual:] is overridden// to compare contents.static BOOL _PDFSelectionsAreEqual(PDFSelection *selectionA, PDFSelection *selectionB){ NSArray *aPages = [selectionA pages]; NSArray *bPages = [selectionB pages]; if (![aPages isEqual:bPages]) return NO; int count = [aPages count]; int i; for (i = 0; i < count; ++i) { NSRect aBounds = [selectionA boundsForPage:[aPages objectAtIndex:i]]; NSRect bBounds = [selectionB boundsForPage:[bPages objectAtIndex:i]]; if (!NSEqualRects(aBounds, bBounds)) { return NO; } } return YES;}@implementation WebPDFView#pragma mark WebPDFView API+ (NSBundle *)PDFKitBundle{ static NSBundle *PDFKitBundle = nil; if (PDFKitBundle == nil) { NSString *PDFKitPath = [_NSPathForSystemFramework(@"Quartz.framework") stringByAppendingString:@"/Frameworks/PDFKit.framework"]; if (PDFKitPath == nil) { LOG_ERROR("Couldn't find PDFKit.framework"); return nil; } PDFKitBundle = [NSBundle bundleWithPath:PDFKitPath]; if (![PDFKitBundle load]) { LOG_ERROR("Couldn't load PDFKit.framework"); } } return PDFKitBundle;}+ (NSArray *)supportedMIMETypes{ return [WebPDFRepresentation supportedMIMETypes];}- (void)setPDFDocument:(PDFDocument *)doc{ // Both setDocument: and _applyPDFDefaults will trigger scale and mode-changed notifications. // Those aren't reflecting user actions, so we need to ignore them. _ignoreScaleAndDisplayModeAndPageNotifications = YES; [PDFSubview setDocument:doc]; [self _applyPDFDefaults]; _ignoreScaleAndDisplayModeAndPageNotifications = NO;}#pragma mark NSObject OVERRIDES- (void)dealloc{ ASSERT(!trackedFirstResponder); [dataSource release]; [previewView release]; [PDFSubview release]; [path release]; [PDFSubviewProxy release]; [textMatches release]; [super dealloc];}#pragma mark NSResponder OVERRIDES- (void)centerSelectionInVisibleArea:(id)sender{ [PDFSubview scrollSelectionToVisible:nil];}- (void)scrollPageDown:(id)sender{ // PDFView doesn't support this responder method directly, so we pass it a fake key event [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageDownFunctionKey]];}- (void)scrollPageUp:(id)sender{ // PDFView doesn't support this responder method directly, so we pass it a fake key event [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSPageUpFunctionKey]];}- (void)scrollLineDown:(id)sender{ // PDFView doesn't support this responder method directly, so we pass it a fake key event [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSDownArrowFunctionKey]];}- (void)scrollLineUp:(id)sender{ // PDFView doesn't support this responder method directly, so we pass it a fake key event [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSUpArrowFunctionKey]];}- (void)scrollToBeginningOfDocument:(id)sender{ // PDFView doesn't support this responder method directly, so we pass it a fake key event [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSHomeFunctionKey]];}- (void)scrollToEndOfDocument:(id)sender{ // PDFView doesn't support this responder method directly, so we pass it a fake key event [PDFSubview keyDown:[self _fakeKeyEventWithFunctionKey:NSEndFunctionKey]];}// jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari// was using the old jumpToSelection selector in its menu. Newer versions of Safari will us the// selector centerSelectionInVisibleArea. We'll leave this old selector in place for two reasons:// (1) compatibility between older Safari and newer WebKit; (2) other WebKit-based applications// might be using the jumpToSelection: selector, and we don't want to break them.- (void)jumpToSelection:(id)sender{ [self centerSelectionInVisibleArea:nil];}#pragma mark NSView OVERRIDES- (BOOL)acceptsFirstResponder { return YES;}- (BOOL)becomeFirstResponder{ // This works together with setNextKeyView to splice our PDFSubview into // the key loop similar to the way NSScrollView does this. NSWindow *window = [self window]; id newFirstResponder = nil; if ([window keyViewSelectionDirection] == NSSelectingPrevious) { NSView *previousValidKeyView = [self previousValidKeyView]; if ((previousValidKeyView != self) && (previousValidKeyView != PDFSubview)) newFirstResponder = previousValidKeyView; } else { NSView *PDFDocumentView = [PDFSubview documentView]; if ([PDFDocumentView acceptsFirstResponder]) newFirstResponder = PDFDocumentView; } if (!newFirstResponder) return NO; if (![window makeFirstResponder:newFirstResponder]) return NO; [[dataSource webFrame] _clearSelectionInOtherFrames]; return YES;}- (NSView *)hitTest:(NSPoint)point{ // Override hitTest so we can override menuForEvent. NSEvent *event = [NSApp currentEvent]; NSEventType type = [event type]; if (type == NSRightMouseDown || (type == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask))) return self; return [super hitTest:point];}- (id)initWithFrame:(NSRect)frame{ self = [super initWithFrame:frame]; if (self) { [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; Class previewViewClass = [[self class] _PDFPreviewViewClass]; // We might not have found a previewViewClass, but if we did find it // then we should be able to create an instance. if (previewViewClass) { previewView = [[previewViewClass alloc] initWithFrame:frame]; ASSERT(previewView); } NSView *topLevelPDFKitView = nil; if (previewView) { // We'll retain the PDFSubview here so that it is equally retained in all // code paths. That way we don't need to worry about conditionally releasing // it later. PDFSubview = [[previewView performSelector:@selector(pdfView)] retain]; topLevelPDFKitView = previewView; } else { PDFSubview = [[[[self class] _PDFViewClass] alloc] initWithFrame:frame]; topLevelPDFKitView = PDFSubview; } ASSERT(PDFSubview); [topLevelPDFKitView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; [self addSubview:topLevelPDFKitView]; [PDFSubview setDelegate:self]; written = NO; // Messaging this proxy is the same as messaging PDFSubview, with the side effect that the // PDF viewing defaults are updated afterwards PDFSubviewProxy = (PDFView *)[[PDFPrefUpdatingProxy alloc] initWithView:self]; } return self;}- (NSMenu *)menuForEvent:(NSEvent *)theEvent{ // Start with the menu items supplied by PDFKit, with WebKit tags applied NSMutableArray *items = [self _menuItemsFromPDFKitForEvent:theEvent]; // Add in an "Open with <default PDF viewer>" item NSString *appName = nil; NSImage *appIcon = nil; _applicationInfoForMIMEType([[dataSource response] MIMEType], &appName, &appIcon); if (!appName) appName = UI_STRING("Finder", "Default application name for Open With context menu"); // To match the PDFKit style, we'll add Open with Preview even when there's no document yet to view, and // disable it using validateUserInterfaceItem. NSString *title = [NSString stringWithFormat:UI_STRING("Open with %@", "context menu item for PDF"), appName]; NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(_openWithFinder:) keyEquivalent:@""]; [item setTag:WebMenuItemTagOpenWithDefaultApplication]; if (appIcon) [item setImage:appIcon]; [items insertObject:item atIndex:0]; [item release]; [items insertObject:[NSMenuItem separatorItem] atIndex:1]; // pass the items off to the WebKit context menu mechanism WebView *webView = [[dataSource webFrame] webView]; ASSERT(webView); NSMenu *menu = [webView _menuForElement:[self elementAtPoint:[self convertPoint:[theEvent locationInWindow] fromView:nil]] defaultItems:items]; // The delegate has now had the opportunity to add items to the standard PDF-related items, or to // remove or modify some of the PDF-related items. In 10.4, the PDF context menu did not go through // the standard WebKit delegate path, and so the standard PDF-related items always appeared. For // clients that create their own context menu by hand-picking specific items from the default list, such as // Safari, none of the PDF-related items will appear until the client is rewritten to explicitly // include these items. For backwards compatibility of tip-of-tree WebKit with the 10.4 version of Safari // (the configuration that people building open source WebKit use), we'll use the entire set of PDFKit-supplied // menu items. This backward-compatibility hack won't work with any non-Safari clients, but this seems OK since
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -