// // CDSDrawView.m // PDFViewer-Mac // // Copyright © 2014-2023 PDF Technologies, Inc. All Rights Reserved. // // THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW // AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE ComPDFKit LICENSE AGREEMENT. // UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. // This notice may not be removed from this file. // #import "CDSDrawView.h" static NSInteger _index; static CGPoint _points[5]; @interface CDSDrawView () @property (nonatomic,assign) BOOL cursorIsHidden; @property (nonatomic,assign) BOOL mouseIsInView; @property (nonatomic,retain) NSTouch *activeTouch; @property (nonatomic,retain) NSBezierPath *bezierPath; @property (nonatomic,assign) BOOL isInView; @end @implementation CDSDrawView - (void)dealloc { } - (id)initWithFrame:(NSRect)frameRect { if (self = [super initWithFrame:frameRect]) { self.drawImage = [[NSImage alloc] initWithSize:self.frame.size]; self.drawColor = [NSColor colorWithDeviceRed:0 green:0 blue:0 alpha:1]; self.strokeRadius = 0.3; _bezierPath = [[NSBezierPath alloc] init]; self.wantsLayer = YES; self.layer.borderWidth = 1.0; self.layer.borderColor =[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.05].CGColor; } return self; } - (id)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { self.drawImage = [[NSImage alloc] initWithSize:self.frame.size]; self.drawColor = [NSColor colorWithDeviceRed:0 green:0 blue:0 alpha:1]; self.strokeRadius = 0.3; _bezierPath = [[NSBezierPath alloc] init]; self.wantsLayer = YES; self.layer.borderWidth = 1.0; self.layer.borderColor =[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.05].CGColor; } return self; } - (NSBezierPath *)drawBezierPath { _drawBezierPath = [_bezierPath copy]; CGRect rect = self.bezierPath.bounds; NSAffineTransform *transform = [NSAffineTransform transform]; [transform translateXBy:- rect.origin.x + +self.bezierPath.lineWidth/2.0 yBy:-rect.origin.y + self.bezierPath.lineWidth/2.0]; [_drawBezierPath transformUsingAffineTransform:transform]; return _drawBezierPath; } /* ** - (BOOL) acceptsFirstResponder ** ** Make sure the view will receive ** events. ** ** Input: none ** ** Output: YES to accept, NO to reject */ - (BOOL)acceptsFirstResponder { return YES; } - (void)setIsAcceptsTouch:(BOOL)isAcceptsTouch { _isAcceptsTouch = isAcceptsTouch; // Accept trackpad events [self setAcceptsTouchEvents:isAcceptsTouch]; size_t screenHeight = CGDisplayPixelsHigh(CGMainDisplayID()); NSRect frameToWindow = [self convertRect:self.bounds toView:nil]; NSRect frameToScreen = [self.window convertRectToScreen:frameToWindow]; if (isAcceptsTouch) { // If the mouse cursor is not already hidden, if (!self.cursorIsHidden) { frameToScreen.origin.x = frameToScreen.origin.x+5; frameToScreen.origin.y = screenHeight-frameToScreen.origin.y-5; CGWarpMouseCursorPosition(frameToScreen.origin); // Detach the mouse cursor from the mouse // hardware so that moving the mouse (or a // single finger) will not move the cursor CGAssociateMouseAndMouseCursorPosition(false); // Hide the mouse cursor [NSCursor hide]; // Remember that we detached and hid the // mouse cursor self.cursorIsHidden = YES; } } else { frameToScreen.origin.y = screenHeight-frameToScreen.origin.y; CGWarpMouseCursorPosition(frameToScreen.origin); // Attach the mouse cursor to the mouse // hardware so that moving the mouse (or a // single finger) will move the cursor CGAssociateMouseAndMouseCursorPosition(true); // Show the mouse cursor [NSCursor unhide]; // Remember that we attached and unhid the // mouse cursor so that the next touch that // begins will detach and hide it self.cursorIsHidden = NO; } } - (void)clearImage { self.drawImage = nil; [self.bezierPath removeAllPoints]; [self setNeedsDisplay:YES]; } - (void)setDrawColor:(NSColor *)drawColor { if (_drawColor != drawColor) { _drawColor = drawColor; } [self setNeedsDisplay:YES]; } - (void)setStrokeRadius:(float)strokeRadius { _strokeRadius = strokeRadius; [self setNeedsDisplay:YES]; } - (NSImage *)signatureImage { CGRect rect = CGRectZero; if (self.bezierPath.empty) { return nil; } else { rect = self.bezierPath.bounds; } CGSize size = CGSizeMake(rect.size.width+self.bezierPath.lineWidth, rect.size.height+self.bezierPath.lineWidth); NSImage *image = [[NSImage alloc] initWithSize:size]; [image lockFocus]; [self.drawColor set]; [self.drawBezierPath setLineWidth:self.strokeRadius * 2]; [self.drawBezierPath setLineCapStyle:kCGLineCapRound]; [self.drawBezierPath setLineJoinStyle:kCGLineJoinRound]; [self.drawBezierPath stroke]; [image unlockFocus]; return image; } #pragma mark Draw - (void)drawRect:(NSRect)dirtyRect { [super drawRect:dirtyRect]; [NSGraphicsContext saveGraphicsState]; [[NSColor clearColor] set]; if (([[self window] firstResponder] == self) && self.mouseIsInView) { NSSetFocusRingStyle(NSFocusRingAbove); } [[NSBezierPath bezierPathWithRect:[self bounds]] fill]; [NSGraphicsContext restoreGraphicsState]; if (self.drawImage) { CGRect imageFrame = [self imageFrameInRect:self.bounds]; [self.drawImage drawInRect:imageFrame]; } [self.drawColor set]; [self.bezierPath setLineWidth:self.strokeRadius * 2]; [self.bezierPath setLineCapStyle:kCGLineCapRound]; [self.bezierPath setLineJoinStyle:kCGLineJoinRound]; [self.bezierPath stroke]; } - (CGRect)imageFrameInRect:(CGRect)rect { CGRect imageRect; if (self.drawImage.size.width < rect.size.width && self.drawImage.size.height < rect.size.height) { imageRect.origin.x = (rect.size.width-self.drawImage.size.width)/2.0; imageRect.origin.y = (rect.size.height-self.drawImage.size.height)/2.0; imageRect.size = self.drawImage.size; } else { if (self.drawImage.size.width/self.drawImage.size.height > rect.size.width/rect.size.height) { imageRect.size.width = rect.size.width; imageRect.size.height = rect.size.width*self.drawImage.size.height/self.drawImage.size.width; } else { imageRect.size.height = rect.size.height; imageRect.size.width = rect.size.height*self.drawImage.size.width/self.drawImage.size.height; } imageRect.origin.x = (rect.size.width-imageRect.size.width)/2.0; imageRect.origin.y = (rect.size.height-imageRect.size.height)/2.0; } return imageRect; } #pragma mark Touch - (void)touchesBeganWithEvent:(NSEvent *)event { [super touchesBeganWithEvent:event]; NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseBegan inView:self]; self.activeTouch = [touches anyObject]; CGPoint point = [self.activeTouch normalizedPosition]; point.x = point.x * self.bounds.size.width; point.y = point.y * self.bounds.size.height; _index = 0; _points[0] = point; [self setNeedsDisplay:YES]; if (!self.cursorIsHidden) { CGAssociateMouseAndMouseCursorPosition(false); [NSCursor hide]; self.cursorIsHidden = YES; } } - (void)touchesMovedWithEvent:(NSEvent *)event { [super touchesMovedWithEvent:event]; NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseMoved inView:self]; BOOL isTouch = NO; for (NSTouch *touch in touches) { if (touch.identity == self.activeTouch.identity) { isTouch = YES; self.activeTouch = touch; } } if (!isTouch) { return; } NSPoint point = [self.activeTouch normalizedPosition]; point.x = point.x * self.bounds.size.width; point.y = point.y * self.bounds.size.height; _index++; _points[_index] = point; if (_index == 4) { _points[3] = CGPointMake((_points[2].x + _points[4].x)/2.0, (_points[2].y + _points[4].y)/2.0); [self.bezierPath moveToPoint:_points[0]]; [self.bezierPath curveToPoint:_points[3] controlPoint1:_points[1] controlPoint2:_points[2] ]; _points[0] = _points[3]; _points[1] = _points[4]; _index = 1; [self setNeedsDisplay:YES]; } } - (void)touchesEndedWithEvent:(NSEvent *)event { [super touchesEndedWithEvent:event]; NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseMoved inView:self]; for (NSTouch *touch in touches) { if (touch.identity == self.activeTouch.identity) { self.activeTouch = nil; } } if (_index < 4) { for (int i=0; i<_index; i++) { [self.bezierPath moveToPoint:_points[i]]; } [self setNeedsDisplay:YES]; } } - (void)touchesCancelledWithEvent:(NSEvent *)event { [super touchesCancelledWithEvent:event]; for (int i=0; i<_index; i++) { [self.bezierPath moveToPoint:_points[i]]; } self.activeTouch = nil; [self setNeedsDisplay:YES]; } #pragma mark - Mouse - (void)viewDidMoveToWindow { if ([self window] != nil) { [self addTrackingRect:[self bounds] owner:self userData:NULL assumeInside:NO]; } } - (void)mouseEntered:(NSEvent *)theEvent { [[self window] makeFirstResponder:self]; self.mouseIsInView = YES; [self setNeedsDisplay:YES]; } - (void)mouseExited:(NSEvent *)theEvent { self.mouseIsInView = NO; [self setNeedsDisplay:YES]; } - (void)mouseDown:(NSEvent *)theEvent { if ([self acceptsTouchEvents]) { return; } CGPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; if (CGRectContainsPoint(self.bounds, point)) { self.isInView = YES; _index = 0; _points[0] = point; } } - (void)mouseDragged:(NSEvent *)theEvent { if ([self acceptsTouchEvents]) { return; } NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; if (CGRectContainsPoint(self.bounds, point)) { _index++; _points[_index] = point; if (_index == 4) { if (!self.isInView) { _points[3] = CGPointMake((_points[2].x + _points[4].x)/2.0, (_points[2].y + _points[4].y)/2.0); [self.bezierPath moveToPoint:_points[0]]; [self.bezierPath curveToPoint:_points[3] controlPoint1:_points[1] controlPoint2:_points[2] ]; } self.isInView = NO; _points[0] = _points[3]; _points[1] = _points[4]; _index = 1; [self setNeedsDisplay:YES]; } } else { self.isInView = YES; _points[0] = _points[3]; _points[1] = _points[4]; _index = 1; [self setNeedsDisplay:YES]; } } - (void)mouseUp:(NSEvent *)theEvent { if ([self acceptsTouchEvents]) { return; } if (_index < 4) { for (int i=0; i<_index; i++) { [self.bezierPath moveToPoint:_points[i]]; } [self setNeedsDisplay:YES]; } } - (void)keyDown:(NSEvent *)theEvent { NSString *chars = [theEvent characters]; unichar character = [chars characterAtIndex: 0]; if (character == 27) { if (self.isAcceptsTouch) { self.isAcceptsTouch = NO; if ([self.delegate respondsToSelector:@selector(drawViewDidFinishTouchMode:)]) { [self.delegate drawViewDidFinishTouchMode:self]; } return; } } [super keyDown:theEvent]; } @end