lazarus/lcl/interfaces/customdrawn/cocoagdiobjects.pas

1341 lines
37 KiB
ObjectPascal

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
SysUtils, MacOSAll, // for CGContextRef
LCLtype, LCLProc,
CocoaAll, CocoaUtils,
Classes, Types;
type
TCocoaBitmapAlignment = (
cbaByte, // each line starts at byte boundary.
cbaWord, // each line starts at word (16bit) boundary
cbaDWord, // each line starts at double word (32bit) boundary
cbaQWord, // each line starts at quad word (64bit) boundary
cbaDQWord // each line starts at double quad word (128bit) boundary
);
TCocoaBitmapType = (
cbtMono, // mask or mono bitmap
cbtGray, // grayscale bitmap
cbtRGB, // color bitmap 8-8-8 R-G-B
cbtARGB, // color bitmap with alpha channel first 8-8-8-8 A-R-G-B
cbtRGBA, // color bitmap with alpha channel last 8-8-8-8 R-G-B-A
cbtBGR, // color bitmap 8-8-8 B-G-R (windows compatible)
cbtBGRA // color bitmap with alpha channel 8-8-8-8 B-G-R-A (windows compatible)
);
const
cbtMask = cbtMono;
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;
constructor CreateDefault;
end;
{ TCocoaBitmap }
TCocoaBitmap = class(TCocoaGDIObject)
private
FData: Pointer;
FAlignment: TCocoaBitmapAlignment;
FFreeData: Boolean;
FDataSize: Integer;
FBytesPerRow: Integer;
FDepth: Byte;
FBitsPerPixel: Byte;
FWidth: Integer;
FHeight: Integer;
FType: TCocoaBitmapType;
// Cocoa information
FbitsPerSample: NSInteger; // How many bits in each color component
FsamplesPerPixel: NSInteger;// How many color components
public
image: NSImage;
imagerep: NSBitmapImageRep;
constructor Create(AWidth, AHeight, ADepth, ABitsPerPixel: Integer;
AAlignment: TCocoaBitmapAlignment; AType: TCocoaBitmapType;
AData: Pointer; ACopyData: Boolean = True);
destructor Destroy; override;
procedure SetInfo(AWidth, AHeight, ADepth, ABitsPerPixel: Integer;
AAlignment: TCocoaBitmapAlignment; AType: TCocoaBitmapType);
public
// property BitsPerComponent: Integer read GetBitsPerComponent;
property BitmapType: TCocoaBitmapType read FType;
// property BytesPerRow: Integer read FBytesPerRow;
// property CGImage: CGImageRef read FCGImage write SetCGImage;
// property ColorSpace: CGColorSpaceRef read GetColorSpace;
property Data: Pointer read FData;
property DataSize: Integer read FDataSize;
property Depth: Byte read FDepth;
// property Info: CGBitmapInfo read GetInfo;
property Width: Integer read FWidth;
property Height: Integer read FHeight;
end;
{ TCocoaTextLayout }
TCocoaTextLayout = class(TObject)
public
constructor Create; virtual; abstract;
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;
{ TASTUITextLayout }
// legacy layout used for Mac OS X 10.4
TASTUITextLayout = class(TCocoaTextLayout)
private
fBuffer : WideString;
fUTF8 : String;
FDX : PIntegerArray;
FLayout : ATSUTextLayout;
FStyle : ATSUStyle;
FTextBefore : ATSUTextMeasurement;
FTextAfter : ATSUTextMeasurement;
FAscent : ATSUTextMeasurement;
FDescent : ATSUTextMeasurement;
FValidSize : Boolean;
procedure RecountSize;
procedure DoJustify(iLineRef: ATSULineRef; var Handled: Boolean);
public
constructor Create; override;
destructor Destroy; override;
procedure SetFont(AFont: TCocoaFont); override;
procedure SetText(UTF8Text: PChar; ByteSize: Integer); override;
function GetSize: TSize; override;
procedure Draw(cg: CGContextRef; X, Y: Integer; DX: PInteger); override;
end;
{ TCoreTextLayout }
//TCoreTextLayout = class(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);
public
ContextSize : TSize;
ctx : NSGraphicsContext;
cgctx : CGContextRef;
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; BackgroundAlpha: Single);
function GetTextExtentPoint(AStr: PChar; ACount: Integer; var Size: TSize): Boolean;
function GetTextMetrics(var TM: TTextMetric): Boolean;
procedure DrawBitmap(X,Y: Integer; ABitmap: TCocoaBitmap);
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;
function CheckDC(dc: HDC): TCocoaContext;
function CheckDC(dc: HDC; Str: string): Boolean;
function CheckGDIOBJ(obj: HGDIOBJ): TCocoaGDIObject;
function CheckBitmap(ABitmap: HBITMAP; AStr: string): Boolean;
implementation
//todo: a better check!
function CheckDC(dc: HDC): TCocoaContext;
begin
Result:=TCocoaContext(dc);
end;
function CheckDC(dc: HDC; Str: string): Boolean;
begin
Result:=dc<>0;
end;
function CheckGDIOBJ(obj: HGDIOBJ): TCocoaGDIObject;
begin
Result:=TCocoaGDIObject(obj);
end;
function CheckBitmap(ABitmap: HBITMAP; AStr: string): Boolean;
begin
Result := ABitmap <> 0;
end;
constructor TCocoaFont.CreateDefault;
begin
inherited Create({False});
end;
{ 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:
// https://wiki.freepascal.org/FPC_PasCocoa/Differences#Sending_messages_to_id
// https://wiki.lazarus.freepascal.org/FPC_PasCocoa#Category_declaration
NSBitmapImageRepFix = objccategory external(NSBitmapImageRep)
function initWithBitmapDataPlanes_pixelsWide_pixelsHigh__colorSpaceName_bytesPerRow_bitsPerPixel(planes: PPByte; 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: PPByte; 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 Cocoa bitmap with the specified characteristics
------------------------------------------------------------------------------}
constructor TCocoaBitmap.Create(AWidth, AHeight, ADepth, ABitsPerPixel: Integer;
AAlignment: TCocoaBitmapAlignment; AType: TCocoaBitmapType;
AData: Pointer; ACopyData: Boolean);
var
HasAlpha: Boolean;
BitmapFormat: NSBitmapFormat;
DataPointer: Pointer;
begin
{$ifdef VerboseBitmaps}
DebugLn(Format('[TCocoaBitmap.Create] AWidth=%d AHeight=%d ADepth=%d ABitsPerPixel=%d'
+ ' AAlignment=%d AType=%d AData=? ACopyData=%d',
[AWidth, AHeight, ADepth, ABitsPerPixel, Integer(AAlignment), Integer(AType), Integer(ACopyData)]));
{$endif}
SetInfo(AWidth, AHeight, ADepth, ABitsPerPixel, AAlignment, AType);
// Copy the image data, if necessary
if ACopyData then
begin
System.GetMem(FData, FDataSize);
FFreeData := True;
if AData <> nil then
System.Move(AData^, FData^, FDataSize) // copy data
else
FillDWord(FData^, FDataSize shr 2, 0); // clear bitmap
DataPointer := @FData;
end
else if (AData = nil) then
begin
FData := AData;
FFreeData := False;
DataPointer := nil;
end
else
begin
FData := AData;
FFreeData := False;
DataPointer := @FData;
end;
HasAlpha := AType in [cbtARGB, cbtRGBA, cbtBGRA];
BitmapFormat := NSAlphaNonpremultipliedBitmapFormat;
if AType = cbtARGB then
BitmapFormat := BitmapFormat or NSAlphaFirstBitmapFormat;
{$ifdef VerboseBitmaps}
DebugLn(Format('[TCocoaBitmap.Create] NSBitmapImageRep.alloc HasAlpha=%d',
[Integer(HasAlpha)]));
{$endif}
// Create the associated NSImageRep
imagerep := NSBitmapImageRep(NSBitmapImageRep.alloc.initWithBitmapDataPlanes_pixelsWide_pixelsHigh__colorSpaceName_bitmapFormat_bytesPerRow_bitsPerPixel(
DataPointer, // planes, BitmapDataPlanes
FWidth, // width, pixelsWide
FHeight,// height, PixelsHigh
FbitsPerSample,// bitsPerSample, bps
FsamplesPerPixel, // samplesPerPixel, sps
HasAlpha, // hasAlpha
False, // isPlanar
NSCalibratedRGBColorSpace, // colorSpaceName
BitmapFormat, // bitmapFormat
FBytesPerRow, // bytesPerRow
FBitsPerPixel //bitsPerPixel
));
// Create the associated NSImage
image := NSImage.alloc.initWithSize(NSMakeSize(AWidth, AHeight));
image.addRepresentation(imagerep);
end;
destructor TCocoaBitmap.Destroy;
begin
//CGImageRelease(FCGImage);
if FFreeData then System.FreeMem(FData);
inherited Destroy;
end;
procedure TCocoaBitmap.SetInfo(AWidth, AHeight, ADepth,
ABitsPerPixel: Integer; AAlignment: TCocoaBitmapAlignment;
AType: TCocoaBitmapType);
const
ALIGNBITS: array[TCocoaBitmapAlignment] of Integer = (0, 1, 3, 7, $F);
var
M: Integer;
begin
if AWidth < 1 then AWidth := 1;
if AHeight < 1 then AHeight := 1;
FWidth := AWidth;
FHeight := AHeight;
FDepth := ADepth;
FBitsPerPixel := ABitsPerPixel;
FType := AType;
FAlignment := AAlignment;
if (FType in [cbtMono, cbtGray]) and (FDepth=0) then
FDepth:=FBitsPerPixel;
FBytesPerRow := ((AWidth * ABitsPerPixel) + 7) shr 3;
M := FBytesPerRow and ALIGNBITS[AAlignment];
if M <> 0 then Inc(FBytesPerRow, ALIGNBITS[AAlignment] + 1 - M);
FDataSize := FBytesPerRow * FHeight;
// Cocoa information
case ABitsPerPixel of
// Strangely, this might appear
0:
begin
FbitsPerSample := 0;
FsamplesPerPixel := 0;
end;
// Mono
1:
begin
FbitsPerSample := 1;
FsamplesPerPixel := 1;
end;
// Gray scale
8:
begin
FbitsPerSample := 8;
FsamplesPerPixel := 1;
end;
// ARGB
32:
begin
FbitsPerSample := 8;
FsamplesPerPixel := 4;
end;
else
// Other RGB
FbitsPerSample := ABitsPerPixel div 3;
FsamplesPerPixel := 3;
end;
end;
{ TASTUITextLayout }
function IntToFix(i: integer): Integer; inline;
begin
Result:=i shl 16;
end;
function FixToInt(f: Integer): Integer; inline;
begin
Result:=Round(Fix2X(F));
end;
procedure TASTUITextLayout.RecountSize;
begin
ATSUGetUnjustifiedBounds(FLayout, kATSUFromTextBeginning, kATSUToTextEnd,
FTextBefore, FTextAfter, FAscent, FDescent);
end;
constructor TASTUITextLayout.Create;
begin
// create text layout
ATSUCreateTextLayout(FLayout);
SetText(#0, 1);
ATSUSetTextLayoutRefCon(FLayout, URefCon(Self));
ATSUCreateStyle(FStyle);
// allow font substitution for exotic glyphs
ATSUSetTransientFontMatching(FLayout, True);
end;
destructor TASTUITextLayout.Destroy;
begin
ATSUDisposeTextLayout(FLayout);
ATSUDisposeStyle(FStyle);
inherited Destroy;
end;
const
DefaultFont = 'Lucida Grande';
DefaultSize = 13;
function FindATSUFontID(const FontName: String): ATSUFontID;
var
fn : String;
begin
Result := 0;
if IsFontNameDefault(FontName) then fn:=DefaultFont else fn:=FontName;
if (fn <> '') then
ATSUFindFontFromName(@fn[1], Length(fn),
kFontFullName, kFontMacintoshPlatform, kFontRomanScript,
kFontEnglishLanguage, Result);
end;
procedure TASTUITextLayout.SetFont(AFont:TCocoaFont);
var
Attr: ATSUAttributeTag;
M: ATSUTextMeasurement;
O: ATSStyleRenderingOptions;
B: Boolean;
S: ByteCount;
A: ATSUAttributeValuePtr;
ID: ATSUFontID;
const
ATSStyleRenderingOption: array [Boolean] of ATSStyleRenderingOptions =
(kATSStyleNoAntiAliasing, kATSStyleApplyAntiAliasing);
begin
if not Assigned(AFont) then Exit;
ID := FindATSUFontID(AFont.Name);
if ID <> 0 then
begin
Attr := kATSUFontTag;
A := @ID;
S := SizeOf(ID);
ATSUSetAttributes(FStyle, 1, @Attr, @S, @A);
end;
Attr := kATSUSizeTag;
M := IntToFix(Abs(AFont.Size));
A := @M;
S := SizeOf(M);
ATSUSetAttributes(FStyle, 1, @Attr, @S, @A);
S := SizeOf(B);
Attr := kATSUQDBoldfaceTag;
B := cfs_Bold in AFont.Style;
A := @B;
ATSUSetAttributes(FStyle, 1, @Attr, @S, @A);
Attr := kATSUQDItalicTag;
B := cfs_Italic in AFont.Style;
A := @B;
ATSUSetAttributes(FStyle, 1, @Attr, @S, @A);
Attr := kATSUQDUnderlineTag;
B := cfs_Underline in AFont.Style;
A := @B;
ATSUSetAttributes(FStyle, 1, @Attr, @S, @A);
Attr := kATSUStyleStrikeThroughTag;
B := cfs_Strikeout in AFont.Style;
A := @B;
ATSUSetAttributes(FStyle, 1, @Attr, @S, @A);
Attr := kATSUStyleRenderingOptionsTag;
O := ATSStyleRenderingOption[AFont.Antialiased];
A := @O;
S := SizeOf(O);
ATSUSetAttributes(FStyle, 1, @Attr, @S, @A);
FValidSize:=False;
end;
procedure TASTUITextLayout.SetText(UTF8Text: PChar; ByteSize: Integer);
begin
if (ByteSize=length(fUTF8)) and (fUTF8<>'') and
(CompareChar(UTF8Text^, fUTF8[1], ByteSize)=0) then Exit; // same buffer, nothing to change!
SetLength(fUTF8, ByteSize);
if ByteSize>0 then
System.Move(UTF8Text^, fUTF8[1], ByteSize)
else
fUTF8:='';
fBuffer:=UTF8Decode(fUTF8);
if fBuffer='' then fBuffer:=#0;
ATSUSetTextPointerLocation(FLayout, @fBuffer[1], 0, length(fBuffer), length(fBuffer));
ATSUSetRunStyle(FLayout, FStyle, kATSUFromTextBeginning, kATSUToTextEnd);
FValidSize:=False;
end;
function TASTUITextLayout.GetSize:TSize;
begin
if not FValidSize then RecountSize;
Result.cx := FixToInt(FTextAfter - FTextBefore);
Result.cy := FixToInt(FDescent + FAscent);
end;
var
ATSUDirectUPP : ATSUDirectLayoutOperationOverrideUPP = nil; //NewATSUDirectLayoutOperationOverrideUPP(@ATSUCallback)
function ATSUCallback(iCurrentOperation: ATSULayoutOperationSelector; iLineRef: ATSULineRef; iRefCon: URefCon; iOperationCallbackParameterPtr: UnivPtr;
var oCallbackStatus: ATSULayoutOperationCallbackStatus ): OSStatus; {$ifdef DARWIN}mwpascal;{$endif}
var
Buffer : TASTUITextLayout;
Handled : Boolean;
begin
Result := noErr;
Buffer := TASTUITextLayout(iRefCon);
oCallbackStatus:=kATSULayoutOperationCallbackStatusHandled;
if Assigned(Buffer) then
Buffer.DoJustify(iLineRef, Handled);
end;
procedure TASTUITextLayout.DoJustify(iLineRef: ATSULineRef; var Handled: Boolean);
type
ATSLayoutRecord1 = packed record
glyphID: ATSGlyphRef;
flags: ATSGlyphInfoFlags;
originalOffset: ByteCount;
realPos: Fixed;
end;
type
TATSLayoutRecordArray = array [Word] of ATSLayoutRecord1;
PATSLayoutRecordArray = ^TATSLayoutRecordArray;
var
i, ofs : Integer;
Layouts : PATSLayoutRecordArray;
LayCount : ItemCount;
begin
if not Assigned(FDX) then Exit;
Laycount:=0;
ATSUDirectGetLayoutDataArrayPtrFromLineRef( iLineRef,
kATSUDirectDataLayoutRecordATSLayoutRecordVersion1, true, @Layouts, Laycount);
if Assigned(Layouts) and (Laycount>0) then
begin
ofs:=0;
for i:=0 to LayCount-1 do
begin
Layouts^[i].realPos:=Long2Fix(ofs);
inc(ofs, FDX^[i]);
end;
end;
ATSUDirectReleaseLayoutDataArrayPtr(iLineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, @Layouts );
Handled:=True;
end;
procedure TASTUITextLayout.Draw(cg:CGContextRef;X,Y:Integer;DX:PInteger);
var
MX, MY : Integer;
Tag : ATSUAttributeTag;
Size : ByteCount;
Value : ATSUAttributeValuePtr;
OverSpec : ATSULayoutOperationOverrideSpecifier;
begin
if not Assigned(cg) then Exit;
if not FValidSize then RecountSize;
MX:=0;
MY:=0;
Tag := kATSUCGContextTag;
Size := sizeOf(CGContextRef);
Value := @cg;
ATSUSetLayoutControls(FLayout, 1, @Tag, @Size, @Value);
Tag := kATSULayoutOperationOverrideTag;
Size := sizeof (ATSULayoutOperationOverrideSpecifier);
Value := @OverSpec;
FillChar(OverSpec, sizeof(OverSpec), 0);
if Assigned(Dx) then begin
FDX := PIntegerArray(Dx);
OverSpec.operationSelector := kATSULayoutOperationPostLayoutAdjustment;
if not Assigned(ATSUDirectUPP) then ATSUDirectUPP:=NewATSUDirectLayoutOperationOverrideUPP(@ATSUCallback);
OverSpec.overrideUPP := ATSUDirectUPP;
end else
FDX:=nil;
ATSUSetLayoutControls (FLayout, 1, @Tag, @Size, @Value);
ATSUDrawText(FLayout, kATSUFromTextBeginning, kATSUToTextEnd,
IntToFix(X)- FTextBefore + MX, IntToFix(Y) - FAscent + MY);
end;
{ TCocoaContext }
function TCocoaContext.CGContext:CGContextRef;
begin
if ctx = nil then Result := cgctx
else 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
FFont := TCocoaFont.CreateDefault;
FFont.AddRef;
FText := TASTUITextLayout.Create;
end;
destructor TCocoaContext.Destroy;
begin
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;
// for BackgroundAlpha 1 = opaque 0 = transparent
procedure TCocoaContext.TextOut(X,Y:Integer;UTF8Chars:PChar;Count:Integer;
CharsDelta:PInteger; BackgroundAlpha: Single);
var
cg: CGContextRef;
ns: NSString;
dic: NSDictionary;
begin
{ // Text rendering with Cocoa only
ns:=NSStringUtf8(UTF8Chars);
// dic := NSDictionary.dictionary();
ns.drawAtPoint_withAttributes(GetNSPoint(10, 10), nil);
// dic.release;
ns.release;}
// Text rendering with Carbon mixed (but it doesn't seam to work because cg returns nil)
cg:=CGContext;
if not Assigned(cg) then Exit;
CGContextScaleCTM(cg, 1, -1);
CGContextTranslateCTM(cg, 0, -ContextSize.cy);
CGContextSetRGBFillColor(cg, TR, TG, TB, BackgroundAlpha);
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.DrawBitmap(X,Y:Integer; ABitmap: TCocoaBitmap);
begin
NSGraphicsContext.saveGraphicsState();
NSGraphicsContext.setCurrentContext(ctx);
ABitmap.imagerep.drawAtPoint(NSMakePoint(X, Y));
NSGraphicsContext.restoreGraphicsState();
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;
{ 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.