// // KMRedactPDFView.swift // PDF Master // // Created by tangchao on 2023/12/18. // import Cocoa class KMRedactPDFView: CPDFView { /* typedef enum { KMPDFRedactViewOperationTypeNone,//没有进入任何模式 KMPDFRedactViewOperationTypeRedact,//标记密文模式 KMPDFRedactViewOperationTypeEidtText,//文本编辑 }KMPDFRedactViewOperationType; @interface KMRedactPDFView : CPDFView @property (nonatomic,retain) CPDFAnnotation *mouseMoveAnnotation; @property (nonatomic,retain) CPDFRedactAnnotation *currentAnnotation; @property (nonatomic,retain) NSMutableArray *newAddAnnotation; @property (nonatomic,retain) NSMutableArray *activeAnnotations; @property (nonatomic,assign) KMPDFRedactViewOperationType operationType; @property (nonatomic,assign) BOOL isEidtImageModel; @property (nonatomic,assign) BOOL isEidtTextModel; @property (nonatomic, copy) void(^eventColorChanged)(NSColor *color); @property (nonatomic, copy) void(^eventFontChanged)(void); @property (nonatomic, copy) void(^exportBtnTaped)(NSInteger type); - (void)resignMonitor; */ /* @property (nonatomic, retain) id localMonitor; */ override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) // Drawing code here. } /* - (void)dealloc { [_currentAnnotation release]; [_mouseMoveAnnotation release]; [_newAddAnnotation release]; [_activeAnnotations release]; [_localMonitor release]; Block_release(_eventColorChanged); Block_release(_eventFontChanged); [super dealloc]; } - (id)initWithFrame:(NSRect)frameRect { self = [super initWithFrame:frameRect]; if (self) { self.operationType = KMPDFRedactViewOperationTypeNone; [self addTrackingArea]; [self initMonitor]; } return self; } - (id)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (self) { self.operationType = KMPDFRedactViewOperationTypeNone; [self addTrackingArea]; [self initMonitor]; } return self; } - (void)addTrackingArea { self.newAddAnnotation = [NSMutableArray array]; self.activeAnnotations = [NSMutableArray array]; NSTrackingArea *trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingActiveInKeyWindow | NSTrackingMouseMoved owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; [trackingArea release]; } - (void)initMonitor { NSEventMask mask = NSEventMaskKeyDown; if (!_localMonitor) { _localMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:mask handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) { unichar eventChar = [event firstCharacter]; NSUInteger modifiers = [event standardModifierFlags]; if ((eventChar == NSDeleteCharacter || eventChar == NSDeleteFunctionKey) && (modifiers == 0)) { [self delete]; } if ([event keyCode] == 36 && modifiers == 0) { [self corpImageDoneWithEnter]; } return event; }]; } } //注销检测器 - (void)resignMonitor { [NSEvent removeMonitor:_localMonitor]; _localMonitor = nil; } - (CGSize)getWidthFromText:(NSString *)text WithSize:(NSFont *)font AboutWidth:(CGFloat)width AndHeight:(CGFloat)height { if (!text) { return CGSizeMake(0, 0); } CGRect rect = [text boundingRectWithSize:CGSizeMake(width, height) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil]; return rect.size; } - (NSMenu *)menuForEvent:(NSEvent *)event { NSMenu *menu = [super menuForEvent:event]; if (!menu) { menu = [[[NSMenu alloc] init] autorelease]; } NSPoint pagePoint = NSZeroPoint; CPDFPage *page = [self pageAndPoint:&pagePoint forEvent:event nearest:YES]; CPDFAnnotation *annotation = [page annotationAtPoint:pagePoint]; if(annotation && [annotation isKindOfClass:[CPDFRedactAnnotation class]] && self.operationType == KMPDFRedactViewOperationTypeRedact) { NSMenuItem *item = [menu insertItemWithTitle:NSLocalizedString(@"Delete", nil) action:@selector(deleteAnnotation:) target:self atIndex:0]; [item setRepresentedObject:annotation]; [menu insertItem:[NSMenuItem separatorItem] atIndex:1]; NSMenuItem *itemDefault = [menu insertItemWithTitle:NSLocalizedString(@"Make Current Properties Default", nil) action:@selector(setPropertiesDefault:) target:self atIndex:2]; [itemDefault setRepresentedObject:annotation]; [menu insertItemWithTitle:NSLocalizedString(@"Properties...", nil) action:@selector(properties:) target:self atIndex:3]; [menu insertItem:[NSMenuItem separatorItem] atIndex:4]; [menu insertItemWithTitle:NSLocalizedString(@"Repeat Mark Across Pages", nil) action:@selector(repeatMark:) target:self atIndex:5]; [menu insertItemWithTitle:NSLocalizedString(@"Apply Redactions", nil) action:@selector(applyRedact:) target:self atIndex:6]; self.currentAnnotation = (CPDFRedactAnnotation *)annotation; } return menu; } #pragma mark Menu validation - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { if (!self.document || [self.document isLocked]) { return NO; } SEL action = [menuItem action]; if (action == @selector(deleteAnnotation:)) { return YES; } else if (action == @selector(setPropertiesDefault:)) { return YES; } else if (action == @selector(properties:)) { return YES; } else if (action == @selector(repeatMark:)) { if(self.document.pageCount == 1) return NO; return YES; } else if (action == @selector(applyRedact:)) { return YES; } else { return [super validateMenuItem:menuItem]; } } - (NSArray *)menuItemsEditingAtPoint:(CGPoint)point forPage:(CPDFPage *)page { NSMutableArray *menuItems = [super menuItemsEditingAtPoint:point forPage:page].mutableCopy; if (!menuItems) { menuItems = [NSMutableArray array]; } if ([self isSelectEditCharRange] || [self isSelecteditAreaWithPoint:point]) { [menuItems insertObject:[NSMenuItem separatorItem] atIndex:0]; [menuItems insertObject:[self fontColorMenuItem] atIndex:0]; [menuItems insertObject:[self fontSizeMenuItem] atIndex:0]; } if (self.editingArea) { if (self.editingArea.IsImageArea) { [menuItems insertObject:[NSMenuItem separatorItem] atIndex:0]; // [menuItems insertObject:[self imageCutMenuItem] atIndex:0]; // [menuItems insertObject:[self imagePasteMenuItem] atIndex:0]; [menuItems insertObject:[self imageExportMenuItem] atIndex:0]; [menuItems insertObject:[self imageRotateMenuItem] atIndex:0]; } } return menuItems; } - (void)mouseMoved:(NSEvent *)theEvent { [self.window mouseMoved:theEvent]; [super mouseMoved:theEvent]; if(self.operationType != KMPDFRedactViewOperationTypeRedact) return; NSPoint pagePoint = NSZeroPoint; CPDFPage *page = [self pageAndPoint:&pagePoint forEvent:theEvent nearest:YES]; NSPoint newpoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; CPDFAreaOfInterest area = [self areaOfInterestForPoint:newpoint]; if (area & CPDFTextArea) { [[NSCursor IBeamCursor] set]; }else{ [[NSCursor arrowCursor] set]; } CPDFAnnotation *newActiveAnnotation = [page annotationAtPoint:pagePoint]; if (newActiveAnnotation && [newActiveAnnotation isKindOfClass:[CPDFRedactAnnotation class]] && (self.mouseMoveAnnotation == newActiveAnnotation)) { [(CPDFRedactAnnotation *)newActiveAnnotation setDrawRedactionsAsRedacted:YES]; [self setNeedsDisplayAnnotationViewForPage:page]; } else if(self.mouseMoveAnnotation && [self.mouseMoveAnnotation isKindOfClass:[CPDFRedactAnnotation class]]){ [(CPDFRedactAnnotation *)self.mouseMoveAnnotation setDrawRedactionsAsRedacted:NO]; [self setNeedsDisplayAnnotationViewForPage:page]; } self.mouseMoveAnnotation = newActiveAnnotation; } - (void)mouseDown:(NSEvent *)theEvent { NSPoint pagePoint = NSZeroPoint; if(self.operationType != KMPDFRedactViewOperationTypeRedact) return; CPDFPage *page = [self pageAndPoint:&pagePoint forEvent:theEvent nearest:YES]; CPDFAnnotation *newActiveAnnotation = [page annotationAtPoint:pagePoint]; NSPoint newpoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; CPDFAreaOfInterest area = [self areaOfInterestForPoint:newpoint]; [self.activeAnnotations removeAllObjects]; //预留 if(newActiveAnnotation) { if(![self.activeAnnotations containsObject:newActiveAnnotation]) { [self.activeAnnotations addObject:newActiveAnnotation]; } [self setNeedsDisplayAnnotationViewForPage:page]; [self doDragMouseWithEvent:theEvent]; } else if(area & CPDFTextArea) { [super mouseDown:theEvent]; [self doMarkUpWithEvent:theEvent]; self.currentSelection = nil; } else { [self doRedactWithEvent:theEvent]; } } #pragma mark - keyDown - (void)delete { [self.activeAnnotations enumerateObjectsUsingBlock:^(CPDFAnnotation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if([obj isKindOfClass:[CPDFRedactAnnotation class]]) { [self removeAnnotation:obj]; } }]; } - (void)corpImageDoneWithEnter { if([self.editingArea isKindOfClass:[CPDFEditImageArea class]]) { CPDFEditImageArea *editImageArea = (CPDFEditImageArea *)self.editingArea; if(editImageArea.isCropMode) { [self cropEditImageArea:editImageArea withBounds:editImageArea.cropRect]; [self exitCropWithEditImageArea:editImageArea]; } } } #pragma mark - Rendering - (void)drawPage:(CPDFPage *)page toContext:(CGContextRef)context { [self.activeAnnotations enumerateObjectsUsingBlock:^(CPDFAnnotation *annotation, NSUInteger idx, BOOL * _Nonnull stop) { if (annotation.page && [annotation.page isEqual:page]) { [annotation drawSelectionHighlightForView:self inContext:context]; } }]; } - (BOOL)doDragMouseWithEvent:(NSEvent *)theEvent { BOOL didDrag = NO;; while (YES) { if ([[[self window] nextEventMatchingMask: NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged] type] == NSEventTypeLeftMouseUp) break; didDrag = YES; } return didDrag; } - (void)doMarkUpWithEvent:(NSEvent *)theEvent { NSUInteger eventMask = NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged; while (YES) { theEvent = [[self window] nextEventMatchingMask:eventMask]; if ([theEvent type] == NSEventTypeLeftMouseUp) { if (self.currentSelection) { CPDFPage *page = self.currentSelection.page; CPDFAnnotation *annotation = [self addRedactPDFSelection:self.currentSelection]; [annotation setModificationDate:[NSDate date]]; NSString *userName = [[NSUserDefaults standardUserDefaults] stringForKey:@"SKUserName"]; [annotation setUserName:userName ? : NSFullUserName()]; if ([annotation isKindOfClass:[CPDFRedactAnnotation class]]) { [annotation setBorderWidth:10]; [(CPDFRedactAnnotation *)annotation setBorderColor:[KMPDFAnnotationRedactConfig sharedInstance].redactOutlineColor]; [(CPDFRedactAnnotation *)annotation setInteriorColor:[KMPDFAnnotationRedactConfig sharedInstance].redactFillColor]; [(CPDFRedactAnnotation *)annotation setFontColor:[KMPDFAnnotationRedactConfig sharedInstance].redactFontColor]; if([KMPDFAnnotationRedactConfig sharedInstance].overlayText) { [(CPDFRedactAnnotation *)annotation setAlignment:[KMPDFAnnotationRedactConfig sharedInstance].textAlignment]; NSFont* font = [NSFont fontWithName:@"Helvetica" size:[KMPDFAnnotationRedactConfig sharedInstance].fontSize]; [(CPDFRedactAnnotation *)annotation setFont:font]; [(CPDFRedactAnnotation *)annotation setOverlayText:[KMPDFAnnotationRedactConfig sharedInstance].overlayTextString]; } } [self addAnnotation:annotation toPage:page]; [self.newAddAnnotation addObject:annotation]; [self setNeedsDisplayForPage:page]; } break; } else if([theEvent type] == NSEventTypeLeftMouseDragged) { [super mouseDragged:theEvent]; } } } - (void)doRedactWithEvent:(NSEvent *)theEvent { NSPoint point = NSZeroPoint; CPDFPage *page = [self pageAndPoint:&point forEvent:theEvent nearest:YES]; BOOL wasMouseCoalescingEnabled = [NSEvent isMouseCoalescingEnabled]; NSWindow *window = [self window]; NSBezierPath *bezierPath = nil; CAShapeLayer *layer = nil; NSRect boxBounds = page.bounds; CGAffineTransform t = CGAffineTransformRotate(CGAffineTransformMakeScale([self scaleFactor], [self scaleFactor]), -M_PI_2 * [page rotation] / 90.0); layer = [CAShapeLayer layer]; [layer setBounds:NSRectToCGRect(boxBounds)]; [layer setAnchorPoint:CGPointZero]; [layer setPosition:NSPointToCGPoint([self convertPoint:boxBounds.origin fromPage:page])]; [layer setAffineTransform:t]; [layer setZPosition:1.0]; [layer setMasksToBounds:YES]; [layer setFillColor:[KMPDFAnnotationRedactConfig sharedInstance].redactFillColor.CGColor]; [layer setStrokeColor:CGColorGetConstantColor(kCGColorBlack)]; [layer setLineJoin:kCALineJoinRound]; [layer setLineCap:kCALineCapRound]; NSEvent *lastMouseEvent = theEvent; SKRectEdges resizeHandle = SKMinYEdgeMask | SKMaxXEdgeMask; CGRect originalBounds = CGRectMake(point.x, point.y, 0, 0); [[self layer] addSublayer:layer]; NSUInteger eventMask = NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged; CGRect rect = CGRectZero; while (YES) { theEvent = [window nextEventMatchingMask:eventMask]; if ([theEvent type] == NSEventTypeLeftMouseUp) { if (rect.size.width < MIN_NOTE_SIZE || rect.size.height < MIN_NOTE_SIZE) { break; } NSMutableArray *quadrilateralPoints = [NSMutableArray array]; CPDFRedactAnnotation *annotation = [[[CPDFRedactAnnotation alloc] initWithDocument:self.document] autorelease]; CGRect bounds = rect; [quadrilateralPoints addObject:[NSValue valueWithPoint:CGPointMake(CGRectGetMinX(bounds), CGRectGetMaxY(bounds))]]; [quadrilateralPoints addObject:[NSValue valueWithPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))]]; [quadrilateralPoints addObject:[NSValue valueWithPoint:CGPointMake(CGRectGetMinX(bounds), CGRectGetMinY(bounds))]]; [quadrilateralPoints addObject:[NSValue valueWithPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMinY(bounds))]]; [annotation setQuadrilateralPoints:quadrilateralPoints]; [annotation setModificationDate:[NSDate date]]; NSString *userName = [[NSUserDefaults standardUserDefaults] stringForKey:@"SKUserName"]; [annotation setUserName:userName ? : NSFullUserName()]; if ([annotation isKindOfClass:[CPDFRedactAnnotation class]]) { [annotation setBorderWidth:10]; [(CPDFRedactAnnotation *)annotation setBorderColor:[KMPDFAnnotationRedactConfig sharedInstance].redactOutlineColor]; [(CPDFRedactAnnotation *)annotation setInteriorColor:[KMPDFAnnotationRedactConfig sharedInstance].redactFillColor]; [(CPDFRedactAnnotation *)annotation setFontColor:[KMPDFAnnotationRedactConfig sharedInstance].redactFontColor]; if([KMPDFAnnotationRedactConfig sharedInstance].overlayText) { [(CPDFRedactAnnotation *)annotation setAlignment:[KMPDFAnnotationRedactConfig sharedInstance].textAlignment]; NSFont* font = [NSFont fontWithName:@"Helvetica" size:[KMPDFAnnotationRedactConfig sharedInstance].fontSize]; [(CPDFRedactAnnotation *)annotation setFont:font]; [(CPDFRedactAnnotation *)annotation setOverlayText:[KMPDFAnnotationRedactConfig sharedInstance].overlayTextString]; } } [self addAnnotation:annotation toPage:page]; [self.newAddAnnotation addObject:annotation]; break; } else if ([theEvent type] == NSEventTypeLeftMouseDragged) { rect = [self doResizeLinkWithEvent:lastMouseEvent fromPoint:point originalBounds:originalBounds page:page resizeHandle:&resizeHandle]; bezierPath = [NSBezierPath bezierPathWithRect:rect]; [layer setPath:[bezierPath CGPath]]; lastMouseEvent = theEvent; } } [layer removeFromSuperlayer]; [NSEvent setMouseCoalescingEnabled:wasMouseCoalescingEnabled]; } - (CPDFPage *)pageAndPoint:(NSPoint *)point forEvent:(NSEvent *)event nearest:(BOOL)nearest { NSPoint p = [event locationInView:self]; CPDFPage *page = [self pageForPoint:p nearest:nearest]; if (page && point) *point = [self convertPoint:p toPage:page]; return page; } - (CGRect)doResizeLinkWithEvent:(NSEvent *)theEvent fromPoint:(NSPoint)originalPagePoint originalBounds:(NSRect)originalBounds page:(CPDFPage*)page resizeHandle:(SKRectEdges *)resizeHandlePtr { NSPoint currentPagePoint = [self convertPoint:[theEvent locationInView:self] toPage:page]; NSRect newBounds = originalBounds; NSRect pageBounds = page.bounds; NSPoint relPoint = SKSubstractPoints(currentPagePoint, originalPagePoint); SKRectEdges resizeHandle = *resizeHandlePtr; if (NSEqualSizes(originalBounds.size, NSZeroSize)) { SKRectEdges currentResizeHandle = (relPoint.x < 0.0 ? SKMinXEdgeMask : SKMaxXEdgeMask) | (relPoint.y <= 0.0 ? SKMinYEdgeMask : SKMaxYEdgeMask); if (currentResizeHandle != resizeHandle) { *resizeHandlePtr = resizeHandle = currentResizeHandle; } } CGFloat minWidth = MIN_NOTE_SIZE; CGFloat minHeight = MIN_NOTE_SIZE; if ((resizeHandle & SKMaxXEdgeMask)) { newBounds.size.width += relPoint.x; if (NSMaxX(newBounds) > NSMaxX(pageBounds)) newBounds.size.width = NSMaxX(pageBounds) - NSMinX(newBounds); if (NSWidth(newBounds) < minWidth) { newBounds.size.width = minWidth; } } else if ((resizeHandle & SKMinXEdgeMask)) { newBounds.origin.x += relPoint.x; newBounds.size.width -= relPoint.x; if (NSMinX(newBounds) < NSMinX(pageBounds)) { newBounds.size.width = NSMaxX(newBounds) - NSMinX(pageBounds); newBounds.origin.x = NSMinX(pageBounds); } if (NSWidth(newBounds) < minWidth) { newBounds.origin.x = NSMaxX(newBounds) - minWidth; newBounds.size.width = minWidth; } } if ((resizeHandle & SKMaxYEdgeMask)) { newBounds.size.height += relPoint.y; if (NSMaxY(newBounds) > NSMaxY(pageBounds)) { newBounds.size.height = NSMaxY(pageBounds) - NSMinY(newBounds); } if (NSHeight(newBounds) < minHeight) { newBounds.size.height = minHeight; } } else if ((resizeHandle & SKMinYEdgeMask)) { newBounds.origin.y += relPoint.y; newBounds.size.height -= relPoint.y; if (NSMinY(newBounds) < NSMinY(pageBounds)) { newBounds.size.height = NSMaxY(newBounds) - NSMinY(pageBounds); newBounds.origin.y = NSMinY(pageBounds); } if (NSHeight(newBounds) < minHeight) { newBounds.origin.y = NSMaxY(newBounds) - minHeight; newBounds.size.height = minHeight; } } return newBounds; } */ }