unit CocoaGDIObjects; //todo: Remove MacOSAll unit to prevent Carbon framework linking. //todo: Remove HIShape usage used in TCocoaRegion. interface {$mode objfpc}{$H+} {$modeswitch objectivec1} uses MacOSAll, // for CGContextRef LCLtype, CocoaAll, CocoaUtils, Classes, Types; type { TCocoaGDIObject } TCocoaGDIObject = class(TObject) public RefCount: Integer; procedure AddRef; procedure Release; end; TCocoaRegionType = (crt_Empty, crt_Rectangle, crt_Complex); TCocoaCombine = (cc_And, cc_Xor, cc_Or, cc_Diff, cc_Copy); { TCocoaRegion } //todo: Remove HIShape usage. HIShape is legacy TCocoaRegion = class(TCocoaGDIObject) private FShape: HIShapeRef; public constructor Create; constructor Create(const X1, Y1, X2, Y2: Integer); constructor Create(Points: PPoint; NumPts: Integer; isAlter: Boolean); destructor Destroy; override; procedure Apply(cg: CGContextRef); function GetBounds: TRect; function GetType: TCocoaRegionType; function ContainsPoint(const P: TPoint): Boolean; procedure SetShape(AShape: HIShapeRef); function CombineWith(ARegion: TCocoaRegion; CombineMode: TCocoaCombine): Boolean; public property Shape: HIShapeRef read FShape write SetShape; end; { TCocoaBrush } TCocoaBrush = class(TCocoaGDIObject) R,G,B : Single; procedure Apply(cg: CGContextRef); end; { TCocoaPen } TCocoaPen = class(TCocoaGDIObject) public Style : Integer; Width : Integer; R,G,B : Single; procedure Apply(cg: CGContextRef); constructor Create; end; { TCocoaFont } TCocoaFontStyle = set of (cfs_Bold, cfs_Italic, cfs_Underline, cfs_Strikeout); TCocoaFont = class(TCocoaGDIObject) Name : AnsiString; Size : Integer; Style : TCocoaFontStyle; Antialiased: Boolean; end; { TCocoaBitmap } TCocoaBitmap = class(TCocoaGDIObject) public image: NSImage; imagerep: NSBitmapImageRep; constructor Create(AWidth, AHeight, ADepth, ABitsPerPixel: Integer; AData: Pointer; ACopyData: Boolean = True); end; { TCocoaTextLayout } TCocoaTextLayout = class(TObject) public constructor Create; virtual; procedure SetFont(AFont: TCocoaFont); virtual; abstract; procedure SetText(UTF8Text: PChar; ByteSize: Integer); virtual; abstract; function GetSize: TSize; virtual; abstract; procedure Draw(cg: CGContextRef; X, Y: Integer; DX: PInteger); virtual; abstract; end; TCocoaTextLayoutClass = class of TCocoaTextLayout; { TCocoaContext } TCocoaContext = class(TObject) private fText : TCocoaTextLayout; fBrush : TCocoaBrush; fPen : TCocoaPen; fFont : TCocoaFont; fRegion : TCocoaRegion; fBitmap : TCocoaBitmap; procedure SetBitmap(const AValue: TCocoaBitmap); procedure SetBrush(const AValue: TCocoaBrush); procedure SetFont(const AValue: TCocoaFont); procedure SetPen(const AValue: TCocoaPen); procedure SetRegion(const AValue: TCocoaRegion); protected ContextSize : TSize; public ctx : NSGraphicsContext; PenPos : TPoint; Stack : Integer; TR,TG,TB : Single; constructor Create; destructor Destroy; override; function InitDraw(width, height: Integer): Boolean; procedure MoveTo(x,y: Integer); procedure LineTo(x,y: Integer); procedure Polygon(const Points: array of TPoint; NumPts: Integer; Winding: boolean); procedure Polyline(const Points: array of TPoint; NumPts: Integer); procedure Rectangle(X1, Y1, X2, Y2: Integer; FillRect: Boolean; UseBrush: TCocoaBrush); procedure Ellipse(X1, Y1, X2, Y2: Integer); procedure TextOut(X,Y: Integer; UTF8Chars: PChar; Count: Integer; CharsDelta: PInteger); function GetTextExtentPoint(AStr: PChar; ACount: Integer; var Size: TSize): Boolean; function GetTextMetrics(var TM: TTextMetric): Boolean; procedure SetOrigin(X,Y: Integer); procedure GetOrigin(var X,Y: Integer); function CGContext: CGContextRef; virtual; property Brush: TCocoaBrush read fBrush write SetBrush; property Pen: TCocoaPen read fPen write SetPen; property Font: TCocoaFont read fFont write SetFont; property Region: TCocoaRegion read fRegion write SetRegion; property Bitmap: TCocoaBitmap read fBitmap write SetBitmap; end; var TextLayoutClass : TCocoaTextLayoutClass = nil; implementation { TCocoaBitmap } type // The following dummy categories fix bugs in the Cocoa bindings available in FPC // Remove them when the FPC binding parser is fixed. // More details: // http://wiki.freepascal.org/FPC_PasCocoa/Differences#Sending_messages_to_id // http://wiki.lazarus.freepascal.org/FPC_PasCocoa#Category_declaration NSBitmapImageRepFix = objccategory external(NSBitmapImageRep) function initWithBitmapDataPlanes_pixelsWide_pixelsHigh__colorSpaceName_bytesPerRow_bitsPerPixel(planes: PChar; width: NSInteger; height: NSInteger; bps: NSInteger; spp: NSInteger; alpha: Boolean; isPlanar_: Boolean; colorSpaceName_: NSString; rBytes: NSInteger; pBits: NSInteger): id; message 'initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bytesPerRow:bitsPerPixel:'; function initWithBitmapDataPlanes_pixelsWide_pixelsHigh__colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel(planes: PChar; width: NSInteger; height: NSInteger; bps: NSInteger; spp: NSInteger; alpha: Boolean; isPlanar_: Boolean; colorSpaceName_: NSString; bitmapFormat_: NSBitmapFormat; rBytes: NSInteger; pBits: NSInteger): id; message 'initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel:'; end; {------------------------------------------------------------------------------ Method: TCocoaBitmap.Create Params: AWidth - Bitmap width AHeight - Bitmap height ADepth - Significant bits per pixel ABitsPerPixel - The number of allocated bits per pixel (can be larger than depth) // AAlignment - Alignment of the data for each row // ABytesPerRow - The number of bytes between rows ACopyData - Copy supplied bitmap data (OPTIONAL) Creates Carbon bitmap with the specified characteristics ------------------------------------------------------------------------------} constructor TCocoaBitmap.Create(AWidth, AHeight, ADepth, ABitsPerPixel: Integer; AData: Pointer; ACopyData: Boolean); var bitsPerSample: NSInteger; // How many bits in each color component samplesPerPixel: NSInteger;// How many color components begin case ABitsPerPixel of // Mono 1: begin bitsPerSample := 1; samplesPerPixel := 1; end; // Gray scale 8: begin bitsPerSample := 8; samplesPerPixel := 1; end; // ARGB 32: begin bitsPerSample := 8; samplesPerPixel := 4; end; else // Other RGB bitsPerSample := bitsPerSample div 3; samplesPerPixel := 3; end; // Create the associated NSImageRep imagerep := NSBitmapImageRep(NSBitmapImageRep.alloc.initWithBitmapDataPlanes_pixelsWide_pixelsHigh__colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel( @AData, // planes, BitmapDataPlanes AWidth, // width, pixelsWide AHeight,// height, PixelsHigh bitsPerSample,// bitsPerSample, bps samplesPerPixel, // samplesPerPixel, sps False, // hasAlpha False, // isPlanar NSCalibratedRGBColorSpace, // colorSpaceName 0, // bitmapFormat 0, // bytesPerRow ABitsPerPixel //bitsPerPixel )); // Create the associated NSImage image := NSImage.alloc.initWithSize(NSMakeSize(AWidth, AHeight)); image.addRepresentation(imagerep); end; { TCocoaContext } function TCocoaContext.CGContext:CGContextRef; begin Result:=CGContextRef(ctx.graphicsPort); end; procedure TCocoaContext.SetBitmap(const AValue: TCocoaBitmap); begin fBitmap:=AValue; end; procedure TCocoaContext.SetBrush(const AValue: TCocoaBrush); begin fBrush:=AValue; if Assigned(fBrush) then fBrush.Apply(CGContext); end; procedure TCocoaContext.SetFont(const AValue: TCocoaFont); begin fFont:=AValue; end; procedure TCocoaContext.SetPen(const AValue: TCocoaPen); begin fPen:=AValue; if Assigned(fPen) then fPen.Apply(CGContext); end; procedure TCocoaContext.SetRegion(const AValue: TCocoaRegion); begin fRegion:=AValue; end; constructor TCocoaContext.Create; begin fText:=TextLayoutClass.Create; end; destructor TCocoaContext.Destroy; begin fText.Free; inherited Destroy; end; function TCocoaContext.InitDraw(width,height:Integer): Boolean; var cg : CGContextRef; begin cg:=CGContext; Result:=Assigned(cg); if not Result then Exit; ContextSize.cx:=width; ContextSize.cy:=height; CGContextTranslateCTM(cg, 0, height); CGContextScaleCTM(cg, 1, -1); PenPos.x:=0; PenPos.y:=0; end; procedure TCocoaContext.MoveTo(x,y:Integer); begin PenPos.x:=x; PenPos.y:=y; end; procedure TCocoaContext.LineTo(x,y:Integer); var cg : CGContextRef; p : array [0..1] of CGPoint; deltaX, deltaY, absDeltaX, absDeltaY: Integer; clipDeltaX, clipDeltaY: Float32; tx,ty:Float32; begin cg:=CGContext; if not Assigned(cg) then Exit; deltaX := X - PenPos.x; deltaY := Y - PenPos.y; if (deltaX=0) and (deltaY=0) then Exit; absDeltaX := Abs(deltaX); absDeltaY := Abs(deltaY); if (absDeltaX<=1) and (absDeltaY<=1) then begin // special case for 1-pixel lines tx := PenPos.x + 0.55; ty := PenPos.y + 0.55; end else begin // exclude the last pixel from the line if absDeltaX > absDeltaY then begin if deltaX > 0 then clipDeltaX := -1.0 else clipDeltaX := 1.0; clipDeltaY := clipDeltaX * deltaY / deltaX; end else begin if deltaY > 0 then clipDeltaY := -1.0 else clipDeltaY := 1.0; clipDeltaX := clipDeltaY * deltaX / deltaY; end; tx := X + clipDeltaX + 0.5; ty := Y + clipDeltaY + 0.5; end; p[0].x:=PenPos.X+0.5; p[0].y:=PenPos.Y+0.5; p[1].x:=tx; p[1].y:=ty; CGContextBeginPath(cg); CGContextAddLines(cg, @p, 2); CGContextStrokePath(cg); PenPos.x := X; PenPos.y := Y; end; procedure CGContextAddLCLPoints(cg: CGContextRef; const Points: array of TPoint;NumPts:Integer); var cp : array of CGPoint; i : Integer; begin SetLength(cp, NumPts); for i:=0 to NumPts-1 do begin cp[i].x:=Points[i].X+0.5; cp[i].y:=Points[i].Y+0.5; end; CGContextAddLines(cg, @cp[0], NumPts); end; procedure CGContextAddLCLRect(cg: CGContextRef; x1, y1, x2, y2: Integer); overload; var r : CGRect; begin r.origin.x:=x1+0.5; r.origin.y:=y1+0.5; r.size.width:=x2-x1-1; r.size.height:=y2-y1-1; CGContextAddRect(cg, r); end; procedure CGContextAddLCLRect(cg: CGContextRef; const R: TRect); overload; begin CGContextAddLCLRect(cg, r.Left, r.Top, r.Right, r.Bottom); end; procedure TCocoaContext.Polygon(const Points:array of TPoint;NumPts:Integer; Winding:boolean); var cg : CGContextRef; begin cg:=CGContext; if not Assigned(cg) or (NumPts<=0) then Exit; CGContextBeginPath(cg); CGContextAddLCLPoints(cg, Points, NumPts); CGContextClosePath(cg); if Winding then CGContextDrawPath(cg, kCGPathFillStroke) else CGContextDrawPath(cg, kCGPathEOFillStroke); end; procedure TCocoaContext.Polyline(const Points: array of TPoint; NumPts: Integer); var cg : CGContextRef; begin cg:=CGContext; if not Assigned(cg) or (NumPts<=0) then Exit; CGContextBeginPath(cg); CGContextAddLCLPoints(cg, Points, NumPts); CGContextStrokePath(cg); end; procedure TCocoaContext.Rectangle(X1,Y1,X2,Y2:Integer;FillRect:Boolean; UseBrush: TCocoaBrush); var cg : CGContextRef; begin cg:=CGContext; if not Assigned(cg) then Exit; CGContextBeginPath(cg); CGContextAddLCLRect(cg, X1,Y1,X2,Y2); if FillRect then begin //using the brush if Assigned(UseBrush) then UseBrush.Apply(cg); CGContextFillPath(cg); //restore the brush if Assigned(UseBrush) and Assigned(fBrush) then fBrush.Apply(cg); end else CGContextStrokePath(cg); end; procedure TCocoaContext.Ellipse(X1,Y1,X2,Y2:Integer); var cg : CGContextRef; r : CGRect; begin cg:=CGContext; if not Assigned(cg) then Exit; r.origin.x:=x1+0.5; r.origin.y:=y1+0.5; r.size.width:=x2-x1-1; r.size.height:=y2-y1-1; CGContextBeginPath(CGContext); CGContextAddEllipseInRect(CGContext, R); CGContextDrawPath(CGContext, kCGPathFillStroke); end; procedure TCocoaContext.TextOut(X,Y:Integer;UTF8Chars:PChar;Count:Integer; CharsDelta:PInteger); var cg : CGContextRef; begin cg:=CGContext; if not Assigned(cg) then Exit; CGContextScaleCTM(cg, 1, -1); CGContextTranslateCTM(cg, 0, -ContextSize.cy); CGContextSetRGBFillColor(cg, TR, TG, TB, 1); fText.SetText(UTF8Chars, Count); fText.Draw(cg, X, ContextSize.cy-Y, CharsDelta); if Assigned(fBrush) then fBrush.Apply(cg); CGContextTranslateCTM(cg, 0, ContextSize.cy); CGContextScaleCTM(cg, 1, -1); end; {------------------------------------------------------------------------------ Method: GetTextExtentPoint Params: Str - Text string Count - Number of characters in string Size - The record for the dimensions of the string Returns: If the function succeeds Computes the width and height of the specified string of text ------------------------------------------------------------------------------} function TCocoaContext.GetTextExtentPoint(AStr: PChar; ACount: Integer; var Size: TSize): Boolean; var LStr: String; begin Result := False; Size.cx := 0; Size.cy := 0; if ACount = 0 then Exit(True); if ACount < 0 then LStr := AStr else LStr := Copy(AStr, 1, ACount); fText.SetText(PChar(LStr), Length(LStr)); Size := fText.getSize(); Result := True; end; {------------------------------------------------------------------------------ Method: TCocoaContext.GetTextMetrics Params: TM - The Record for the text metrics Returns: If the function succeeds Fills the specified buffer with the metrics for the currently selected font ------------------------------------------------------------------------------} function TCocoaContext.GetTextMetrics(var TM: TTextMetric): Boolean; {var TextStyle: ATSUStyle; M: ATSUTextMeasurement; B: Boolean; TextLayout: TCarbonTextLayout; const SName = 'GetTextMetrics'; SGetAttrName = 'ATSUGetAttribute';} begin Result := False; // TextStyle := CurrentFont.Style; FillChar(TM, SizeOf(TM), 0); { // According to the MSDN library, TEXTMETRIC: // the average char width is generally defined as the width of the letter x if not BeginTextRender('x', 1, TextLayout) then Exit; try} TM.tmAscent := 5;//RoundFixed(TextLayout.Ascent); TM.tmDescent := 5;//RoundFixed(TextLayout.Descent); TM.tmHeight := 15;//RoundFixed(TextLayout.Ascent + TextLayout.Descent); // if OSError(ATSUGetAttribute(TextStyle, kATSULeadingTag, SizeOf(M), @M, nil), // Self, SName, SGetAttrName, 'kATSULeadingTag', kATSUNotSetErr) then Exit; // TM.tmInternalLeading := RoundFixed(M); TM.tmExternalLeading := 0; TM.tmAveCharWidth := 15;//RoundFixed(TextLayout.TextAfter - TextLayout.TextBefore); // finally // EndTextRender(TextLayout); // end; TM.tmMaxCharWidth := 15;//TM.tmAscent; // TODO: don't know how to determine this right TM.tmOverhang := 0; TM.tmDigitizedAspectX := 0; TM.tmDigitizedAspectY := 0; TM.tmFirstChar := 'a'; TM.tmLastChar := 'z'; TM.tmDefaultChar := 'x'; TM.tmBreakChar := '?'; // if OSError(ATSUGetAttribute(TextStyle, kATSUQDBoldfaceTag, SizeOf(B), @B, nil), // Self, SName, SGetAttrName, 'kATSUQDBoldfaceTag', kATSUNotSetErr) then Exit; { if B then} TM.tmWeight := FW_NORMAL; // else TM.tmWeight := FW_BOLD; { if OSError(ATSUGetAttribute(TextStyle, kATSUQDItalicTag, SizeOf(B), @B, nil), Self, SName, SGetAttrName, 'kATSUQDItalicTag', kATSUNotSetErr) then Exit; TM.tmItalic := Byte(B);} { if OSError(ATSUGetAttribute(TextStyle, kATSUQDUnderlineTag, SizeOf(B), @B, nil), Self, SName, SGetAttrName, 'kATSUQDUnderlineTag', kATSUNotSetErr) then Exit; TM.tmUnderlined := Byte(B); if OSError(ATSUGetAttribute(TextStyle, kATSUStyleStrikeThroughTag, SizeOf(B), @B, nil), Self, SName, SGetAttrName, 'kATSUStyleStrikeThroughTag', kATSUNotSetErr) then Exit; TM.tmStruckOut := Byte(B);} // TODO: get these from font TM.tmPitchAndFamily := FIXED_PITCH or TRUETYPE_FONTTYPE; TM.tmCharSet := DEFAULT_CHARSET; Result := True; end; procedure TCocoaContext.SetOrigin(X,Y:Integer); var cg : CGContextRef; begin cg:=CGContext; if not Assigned(cg) then Exit; if Assigned(cg) then CGContextTranslateCTM(cg, X, Y); end; procedure TCocoaContext.GetOrigin(var X,Y: Integer); var cg : CGContextRef; t : CGAffineTransform; begin cg:=CGContext; if not Assigned(cg) then Exit; t:=CGContextGetCTM(cg); X := Round(t.tx); Y := ContextSize.cy - Round(t.ty); end; { TCocoaRegion } {------------------------------------------------------------------------------ Method: TCocoaRegion.Create Creates a new empty Cocoa region ------------------------------------------------------------------------------} constructor TCocoaRegion.Create; begin inherited Create; FShape := HIShapeCreateEmpty; end; {------------------------------------------------------------------------------ Method: TCocoaRegion.Create Params: X1, Y1, X2, Y2 - Region bounding rectangle Creates a new rectangular Cocoa region ------------------------------------------------------------------------------} constructor TCocoaRegion.Create(const X1, Y1, X2, Y2: Integer); begin inherited Create; FShape := HIShapeCreateWithRect(GetCGRect(X1, Y1, X2, Y2)); end; {------------------------------------------------------------------------------ Method: TCocoaRegion.Create Params: Points - Pointer to array of polygon points NumPts - Number of points passed FillMode - Filling mode Creates a new polygonal Cocoa region from the specified points ------------------------------------------------------------------------------} constructor TCocoaRegion.Create(Points: PPoint; NumPts: Integer; isAlter: Boolean); var Bounds: TRect; Context: CGContextRef; W, H: Integer; Data: Pointer; PData: PByte; P: PPoint; I: Integer; X, Y, SX: Integer; LC, C: Byte; //Line: String; function GetPolygonBounds: TRect; var I: Integer; begin P := Points; Result := Classes.Rect(P^.X, P^.Y, P^.X, P^.Y); for I := 1 to NumPts - 1 do begin Inc(P); if P^.X < Result.Left then Result.Left := P^.X; if P^.X > Result.Right then Result.Right := P^.X; if P^.Y < Result.Top then Result.Top := P^.Y; if P^.Y > Result.Bottom then Result.Bottom := P^.Y; end; end; procedure AddPart(X1, X2, Y: Integer); var R: HIShapeRef; begin //DebugLn('AddPart:' + DbgS(X1) + ' - ' + DbgS(X2) + ', ' + DbgS(Y)); R := HIShapeCreateWithRect(GetCGRect(X1, Y, X2, Y + 1)); HIShapeUnion(FShape, R, FShape); CFRelease(R); end; begin inherited Create; (* The passed polygon is drawed into grayscale context, the region is constructed per rows from rectangles of drawed polygon parts. *) FShape := HIShapeCreateMutable; if (NumPts <= 2) or (Points = nil) then Exit; Bounds := GetPolygonBounds; W := Bounds.Right - Bounds.Left + 2; H := Bounds.Bottom - Bounds.Top + 2; if (W <= 0) or (H <= 0) then Exit; System.GetMem(Data, W * H); System.FillChar(Data^, W * H, 0); // clear bitmap context data to black try Context := CGBitmapContextCreate(Data, W, H, 8, W, CGColorSpaceCreateDeviceGray, kCGImageAlphaNone); try CGContextSetShouldAntialias(Context, 0); // disable anti-aliasing CGContextSetGrayFillColor(Context, 1.0, 1.0); // draw white polygon P := Points; CGContextBeginPath(Context); CGContextMoveToPoint(Context, P^.X, P^.Y); for I := 1 to NumPts - 1 do begin Inc(P); CGContextAddLineToPoint(Context, P^.X, P^.Y); end; CGContextClosePath(Context); if isAlter then CGContextEOFillPath(Context) else CGContextFillPath(Context); //SetLength(Line, W); PData := Data; for Y := 0 to Pred(H) do begin LC := 0; // edge is black for X := 0 to Pred(W) do begin C := PData^; //Line[X + 1] := Chr(Ord('0') + C div 255); if (C = $FF) and (LC = 0) then SX := X; // start of painted row part if (C = 0) and (LC = $FF) then // end of painted row part (SX, X) AddPart(SX, X, Pred(H) - Y); LC := C; Inc(PData); end; //DebugLn(DbgS(Pred(H) - Y) + ':' + Line); end; finally CGContextRelease(Context); end; finally System.FreeMem(Data); end; end; {------------------------------------------------------------------------------ Method: TCocoaRegion.Destroy Destroys Cocoa region ------------------------------------------------------------------------------} destructor TCocoaRegion.Destroy; begin CFRelease(FShape); inherited Destroy; end; {------------------------------------------------------------------------------ Method: TCocoaRegion.Apply Params: ADC - Context to apply to Applies region to the specified context Note: Clipping region is only reducing ------------------------------------------------------------------------------} procedure TCocoaRegion.Apply(cg: CGContextRef); begin if not Assigned(cg) then Exit; if HIShapeIsEmpty(FShape) or (HIShapeReplacePathInCGContext(FShape, cg)<>noErr) then Exit; CGContextClip(cg); end; {------------------------------------------------------------------------------ Method: TCocoaRegion.GetBounds Returns: The bounding box of Cocoa region ------------------------------------------------------------------------------} function TCocoaRegion.GetBounds: TRect; var R: HIRect; begin if HIShapeGetBounds(FShape, R) = nil then begin System.FillChar(Result, sizeof(Result), 0); Exit; end; Result := CGRectToRect(R); end; {------------------------------------------------------------------------------ Method: TCocoaRegion.GetType Returns: The type of Cocoa region ------------------------------------------------------------------------------} function TCocoaRegion.GetType: TCocoaRegionType; begin if not Assigned(FShape) or HIShapeIsEmpty(FShape) then Result := crt_Empty else if HIShapeIsRectangular(FShape) then Result := crt_Rectangle else Result := crt_Complex; end; {------------------------------------------------------------------------------ Method: TCocoaRegion.ContainsPoint Params: P - Point Returns: If the specified point lies in Cocoa region ------------------------------------------------------------------------------} function TCocoaRegion.ContainsPoint(const P: TPoint): Boolean; var cp : CGPoint; begin cp.x:=P.x+0.5; cp.y:=P.y+0.5; Result := HIShapeContainsPoint(FShape, cp); end; procedure TCocoaRegion.SetShape(AShape: HIShapeRef); begin if Assigned(FShape) then CFRelease(FShape); FShape := AShape; end; function TCocoaRegion.CombineWith(ARegion: TCocoaRegion; CombineMode: TCocoaCombine): Boolean; var sh1, sh2: HIShapeRef; const MinCoord=-35000; MaxSize=65000; begin Result:=Assigned(ARegion); if not Assigned(ARegion) then Exit; if (CombineMode in [cc_AND, cc_OR, cc_XOR]) and HIShapeIsEmpty(FShape) then CombineMode := cc_COPY; case CombineMode of cc_AND: Shape:=HIShapeCreateIntersection(FShape, ARegion.Shape); cc_XOR: begin sh1 := HIShapeCreateUnion(FShape, ARegion.Shape); sh2 := HIShapeCreateIntersection(FShape, ARegion.Shape); Shape := HIShapeCreateDifference(sh1, sh2); CFRelease(sh1); CFRelease(sh2); end; cc_OR: Shape:=HIShapeCreateUnion(FShape, ARegion.Shape); cc_DIFF: begin if HIShapeIsEmpty(FShape) then {HIShapeCreateDifference doesn't work properly if original shape is empty} {to simulate "emptieness" very big shape is created } Shape:=HIShapeCreateWithRect(GetCGRect(MinCoord,MinCoord,MaxSize,MaxSize)); // create clip nothing. Shape:=HIShapeCreateDifference(FShape, ARegion.Shape); end; cc_COPY: Shape:=HIShapeCreateCopy(ARegion.Shape); else Result := false; end; end; { TCocoaPen } procedure TCocoaPen.Apply(cg:CGContextRef); begin if not Assigned(cg) then Exit; CGContextSetRGBStrokeColor(cg, r, g, b, 1); CGContextSetLineWidth(cg, Width); //todo: style end; constructor TCocoaPen.Create; begin inherited Create; Width:=1; end; { TCocoaBrush } procedure TCocoaBrush.Apply(cg:CGContextRef); begin if cg = nil then Exit; CGContextSetRGBFillColor(cg, R,G,B, 1); end; { TCocoaTextLayout } constructor TCocoaTextLayout.Create; begin inherited Create; end; { TCocoaGDIObject } procedure TCocoaGDIObject.AddRef; begin if RefCount>=0 then inc(RefCount); end; procedure TCocoaGDIObject.Release; begin if RefCount>0 then Dec(RefCount) else if RefCount=0 then Free; end; end.