unit lr_ngcanvas; {$mode objfpc}{$H+} {$DEFINE DEBUG} interface uses Classes, SysUtils, Math, Types, LCLType, LCLProc, LazLogger, Graphics, FileUtil, fpjson, jsonparser, FPimage, FPImgCanv, FPCanvas, FPWriteBMP, FPWritePNG, fpparsettf, EasyLazFreeType, LazFreeTypeFontCollection, LazFreeTypeFPImageDrawer; const FONTS_CACHE_FILE = '.fonts.cache'; FONT_ARIAL = 'Arial'; FONT_TIMES = 'Times New Roman'; FONT_COURIER = 'Courier New'; FONT_DEJAVU_SANS = 'DejaVu Sans'; FONT_DEJAVU_SERIF = 'DejaVu Serif'; FONT_DEJAVU_MONO = FONT_DEJAVU_SANS + ' Mono'; FONT_LIB_SANS = 'Liberation Sans'; FONT_FREE_SANS = 'FreeSans'; type TVirtualBitmap = class; { TFontItem } TFontItem = class public fontName: string; fontFile: string; fontID: Integer; data: TObject; end; { TFontCache } TFontCache = class private type { TTTFFontInfo } TTTFFontInfo = class(TTFFileInfo) protected // only interested in Head and Name tables procedure ParseHhea({%H-}AStream : TStream); override; procedure ParseMaxp({%H-}AStream : TStream); override; procedure ParseHmtx({%H-}AStream : TStream); override; procedure ParseCmap({%H-}AStream : TStream); override; procedure ParseOS2({%H-}AStream : TStream); override; end; private fFontList: array of TFontItem; fSearchPath: string; function GetCount: Integer; function GetFonts(aIndex: Integer): TFontItem; function BuildFontCache: TJsonObject; function FindCachedFont(cache: TJsonObject; fontName: string; bold, italic: boolean): Integer; function MatchCachedFont(cache: TJSonObject; fontName:string; bold, italic: boolean): Integer; protected function GetCachedName(aName:string; bold,italic:boolean): string; function IndexOfCachedFont(aName:string): Integer; function FindFile(aName:string; bold,italic:boolean): string; public destructor destroy; override; function IndexOfFile(aName:string; bold,italic:boolean): Integer; property Count:Integer read GetCount; property Fonts[aIndex:Integer]: TFontItem read GetFonts; default; property SearchPath:string read fSearchPath write fSearchPath; end; { TFontManager } TFontManager = class private fFontCache: TFontCache; fLastFont: TFreeTypeFont; function GetSearchPath: string; procedure SetSearchPath(AValue: string); public constructor Create; destructor destroy; override; function GetFont(fontName:string; bold, italic: boolean): TFreeTypeFont; function TextExtent(const Text: string): TSize; function TextHeight(const Text: string): Integer; function TextWidth(const Text: string): Integer; property SearchPath: string read GetSearchPath write SetSearchPath; property Font: TFreeTypeFont read fLastFont; end; TFontQuality = (fqMono, fqLow, fqHigh); { TNoGuiImageCanvas } TNoGuiImageCanvas = class(TFPImageCanvas) protected procedure DoPolygonFill (const points:array of TPoint); override; end; { TVirtualCanvas } TVirtualCanvas = class(TCanvas) private fImg: TFPMemoryImage; fImgCanvas: TNoGuiImageCanvas; fFontQuality: TFontQuality; fFontHinted: boolean; fFontClearType: boolean; fDrawer: TFPImageFreeTypeDrawer; procedure UpdateFontProperties; procedure UpdateBrushProperties; procedure UpdatePenProperties; protected procedure CreateBrush; override; procedure CreateFont; override; procedure CreateHandle; override; procedure CreatePen; override; procedure DeselectHandles; override; procedure RealizeAntialiasing; override; procedure DoMoveTo(x, y: integer); override; procedure DoLineTo(x, y: integer); override; procedure DoLine(x1, y1, x2, y2: integer); override; public procedure Rectangle(X1,Y1,X2,Y2: Integer); override; procedure FillRect(const ARect: TRect); override; procedure Fill(color: TFPColor); procedure Polygon(Points: PPoint; NumPts: Integer; {%H-}Winding: boolean = False); override; procedure Polyline(Points: PPoint; NumPts: Integer); override; procedure TextOut(X,Y: Integer; const Text: String); override; procedure TextRect(ARect: TRect; X, Y: integer; const Text: string; const Style: TTextStyle); override; function TextExtent(const Text: string): TSize; override; function TextHeight(const Text: string): Integer; override; constructor create(image: TFpMemoryImage); procedure Draw(X,Y: Integer; SrcGraphic: TVirtualBitmap); reintroduce; procedure StretchDraw(const DestRect: TRect; SrcGraphic: TVirtualBitmap); reintroduce; destructor destroy; override; property FontQuality: TFontQuality read fFontQuality write fFontQuality; property FontHinted: boolean read fFontHinted write fFontHinted; end; { TRasterImageHelper } TRasterImageHelper = class helper for TRasterImage public function RequestRawStream(out rawStream: TMemoryStream): boolean; end; { TVirtualBitmap } TVirtualBitmap = class private fImg: TFPMemoryImage; fCanvas: TVirtualCanvas; function GetCanvas: TCanvas; function GetHeight: Integer; function GetStream: TMemoryStream; function GetWidth: Integer; procedure SetHeight(AValue: Integer); procedure SetWidth(AValue: Integer); public constructor create; destructor destroy; override; procedure SaveToFile(filename: string; Writer: TFPCustomImageWriter=nil); // writer=nil will be a bmp file procedure SaveToStream(aStream: TStream; Writer: TFPCustomImageWriter=nil); // writer=nil will be a bmp stream procedure SetSize(aWidth, aHeight: integer); procedure LoadFromGraphic(Graphic: TGraphic); function GetStreamAsFormat(formatExtension: string; useAlpha:boolean=false): TMemoryStream; procedure Clear; property Stream: TMemoryStream read GetStream; property Canvas: TCanvas read GetCanvas; property Width: Integer read GetWidth write SetWidth; property Height: Integer read GetHeight write SetHeight; end; function DrawTextNoGui(Canvas: TCanvas; text:string; var Rect: TRect; flags: DWord): Integer; var FontManager: TFontManager = nil; implementation var fontCacheBuilt: boolean = false; procedure JSonToFile(Obj: TJSONData; aFilename: string; Opt: TFormatOptions = DefaultFormat); var l: TStringList; begin l := TStringList.Create; l.Text := Obj.FormatJSON(Opt); l.SaveToFile(aFilename); l.Free; end; function JsonFromFile(aFilename: string): TJSonData; var stream: TFileStream; begin stream := TFileStream.Create(aFilename, fmOpenRead); try result := GetJSON(stream); finally stream.free; end; end; { TlrFontManager.TTTFFontInfo } function CompareFontCacheItems(Item1, Item2: Pointer): Integer; var name1,name2: string; bold1,bold2: Integer; italic1,italic2: Integer; begin name1 := TJSonObject(Item1).Strings['familyname']; name2 := TJSonObject(Item2).Strings['familyname']; bold1 := ord(TJSonObject(Item1).Booleans['bold']); bold2 := ord(TJSonObject(Item2).Booleans['bold']); italic1 := Ord(TJSonObject(Item1).Booleans['italic']); italic2 := Ord(TJSonObject(Item2).Booleans['italic']); result := CompareText(name1,name2); if result=0 then begin result := bold1 - bold2; if result = 0 then begin result := italic1 - italic2; end; end; end; function DrawTextNoGui(Canvas: TCanvas; text: string; var Rect: TRect; flags: DWord): Integer; var aSize: TSize; begin if DT_CALCRECT and flags<>0 then begin; aSize := Canvas.TextExtent(text); Rect.Width := aSize.Width; Rect.Height := aSize.Height; result := aSize.Height; end else result := 0; end; { TRasterImageHelper } function TRasterImageHelper.RequestRawStream(out rawStream: TMemoryStream ): boolean; begin // make direct use of the saved original stream to avoid re-copying // this should be ok as it is very unlikely to change in the future. rawStream := FSharedImage.SaveStream; result := rawStream<>nil; if result then rawStream.Position := 0; end; { TNoGuiImageCanvas } // This code is from LazCanvas, which implements the algorithm found in // http://alienryderflex.com/polygon_fill/ procedure TNoGuiImageCanvas.DoPolygonFill(const points: array of TPoint); var lBoundingBox: TRect; x, y, i: integer; // faster version nodes, j, swap, polyCorners: Integer; nodeX: array of Integer; begin if Brush.Style = bsClear then Exit; // Find the Bounding Box of the Polygon lBoundingBox := Rect(0, 0, 0, 0); for i := low(Points) to High(Points) do begin lBoundingBox.Left := Min(Points[i].X, lBoundingBox.Left); lBoundingBox.Top := Min(Points[i].Y, lBoundingBox.Top); lBoundingBox.Right := Max(Points[i].X, lBoundingBox.Right); lBoundingBox.Bottom := Max(Points[i].Y, lBoundingBox.Bottom); end; // Loop through the rows of the image. polyCorners := Length(points); for y := lBoundingBox.Top to lBoundingBox.Bottom do begin // Build a list of nodes. nodes := 0; j := polyCorners-1; for i := 0 to polyCorners-1 do begin if (points[i].Y < y) and (points[j].Y >= y) or (points[j].Y < y) and (points[i].Y >= Y) then begin SetLength(nodeX{%H-}, nodes+1); nodeX[nodes] := Round(points[i].X + (y-points[i].Y) / (points[j].Y-points[i].Y) * (points[j].X-points[i].X)); Inc(nodes); end; j := i; end; // Sort the nodes, via a simple “Bubble” sort. i := 0; while (inodeX[i+1]) then begin swap := nodeX[i]; nodeX[i] := nodeX[i+1]; nodeX[i+1] := swap; if (i <> 0) then Dec(i); end else Inc(i); end; // Fill the pixels between node pairs. i := 0; while i= lBoundingBox.Right) then break; if (nodeX[i+1] > lBoundingBox.Left) then begin if (nodeX[i ] < lBoundingBox.Left) then nodeX[i] := lBoundingBox.Left; if (nodeX[i+1] > lBoundingBox.Right) then nodeX[i+1] := lBoundingBox.Right; for X := nodeX[i] to nodeX[i+1]-1 do SetColor(X, Y, Brush.FPColor); end; i := i + 2; end; end; end; { TFontManager } function TFontManager.GetSearchPath: string; begin result := fFontCache.SearchPath; end; procedure TFontManager.SetSearchPath(AValue: string); var L: TStringList; begin L := TStringList.Create; try L.StrictDelimiter := true; L.Delimiter := ';'; L.DelimitedText := fFontCache.SearchPath; if L.IndexOf(AValue)<0 then L.Add(AValue); fFontCache.SearchPath := L.DelimitedText; finally L.Free; end; end; constructor TFontManager.Create; begin inherited create; fFontCache := TFontCache.Create; end; destructor TFontManager.destroy; var i: Integer; begin for i:=0 to fFontCache.Count-1 do fFontCache[i].data.free; fFontCache.Free; inherited destroy; end; function TFontManager.GetFont(fontName: string; bold, italic: boolean ): TFreeTypeFont; var index: Integer; item: TFontItem; begin index := fFontCache.IndexOfFile(fontName, bold, italic); item := fFontCache[index]; result := TFreeTypeFont(item.Data); if result=nil then begin // register new font //WriteLn('Registering ', item.fontFile,' for ', item.fontName); result := TFreeTypeFont.create; result.Name := FontCollection.AddFile(item.fontFile).Family.FamilyName; item.data := result; end; fLastFont := result; end; function TFontManager.TextExtent(const Text: string): TSize; begin result.cx := TextWidth(Text); result.cy := TextHeight(Text); end; function TFontManager.TextHeight(const Text: string): Integer; begin if fLastFont=nil then raise Exception.Create('Font was not initialized'); result := Round(FLastFont.TextHeight(Text)); end; function TFontManager.TextWidth(const Text: string): Integer; begin if fLastFont=nil then raise Exception.Create('Font was not initialized'); result := Round(FLastFont.TextWidth(Text)) end; { TFontCache.TTTFFontInfo } procedure TFontCache.TTTFFontInfo.ParseHhea(AStream: TStream); begin end; procedure TFontCache.TTTFFontInfo.ParseMaxp(AStream: TStream); begin end; procedure TFontCache.TTTFFontInfo.ParseHmtx(AStream: TStream); begin end; procedure TFontCache.TTTFFontInfo.ParseCmap(AStream: TStream); begin end; procedure TFontCache.TTTFFontInfo.ParseOS2(AStream: TStream); begin end; { TFontCache } function TFontCache.GetFonts(aIndex: Integer): TFontItem; begin result := FFontList[aIndex]; end; function TFontCache.BuildFontCache: TJsonObject; var pathList, fontList: TStringList; i: Integer; arr: TJsonArray; item: TJSONObject; cacheFile: string; fileInfo: TTTFFontInfo; isBold, isItalic, isFixed: boolean; begin cacheFile := {GetUserDir + }FONTS_CACHE_FILE; if not FileExists(cacheFile) or ((GetEnvironmentVariable('FONTS_CACHE_REBUILD')<>'') and (not fontCacheBuilt)) then begin pathList := TStringList.Create; fontList := TStringList.Create; fileInfo := TTTFFontInfo.Create; try pathList.StrictDelimiter := true; pathList.Delimiter := ';'; pathList.DelimitedText := SearchPath; for i := 0 to pathList.Count-1 do FindAllFiles(fontList, pathList[i], '*.ttf;*.otf'); arr := TJsonArray.Create; for i := 0 to fontList.Count-1 do begin //WriteLn(i:4,' ',fontList[i]); try fileInfo.FamilyName := ''; fileInfo.PostScriptName := ''; fileInfo.HumanFriendlyName := ''; fileInfo.LoadFromFile(fontList[i]); item := TJSONObject.Create; item.Add('filename', fontList[i]); if fileInfo.FamilyName<>'' then item.Add('familyname', fileInfo.FamilyName) else item.Add('familyname', ChangeFileExt(ExtractFileName(fontlist[i]), '')); item.Add('psname', fileInfo.PostScriptName); item.Add('friendlyname', fileInfo.HumanFriendlyName); isBold := fileInfo.Head.MacStyle and 1 <> 0; isItalic := fileInfo.Head.MacStyle and 2 <> 0; isFixed := fileInfo.PostScript.isFixedPitch<>0; item.Add('regular', not isFixed); item.Add('bold', isBold); item.Add('italic', isItalic); item.Add('fixedwidth', isFixed); arr.add(item); except end; end; arr.Sort(@CompareFontCacheItems); result := TJsonObject.Create; result.Add('fonts', arr); if arr.Count>0 then JSonToFile(result, cacheFile); fontCacheBuilt := true; finally fileInfo.free; pathList.Free; fontList.Free; end; end else result := TJSonObject(JSonFromFile(cacheFile)); end; function TFontCache.FindCachedFont(cache: TJsonObject; fontName: string; bold, italic: boolean): Integer; var I, L, R, CompareRes: Integer; key: TJSonObject; arr: TJSonArray; begin arr := cache.Arrays['fonts']; key := TJsonObject.Create; try key.Add('familyname', fontName); key.Add('bold', bold); key.Add('italic', italic); result := -1; // binary search from TStringList ... L := 0; R := arr.Count - 1; while (L<=R) do begin I := L + (R - L) div 2; CompareRes := CompareFontCacheItems(key, arr[I]); if (CompareRes>0) then L := I+1 else begin R := I-1; if (CompareRes=0) then begin result := I; L := I; break; end; end; end; finally key.Free; end; end; function TFontCache.MatchCachedFont(cache: TJSonObject; fontName: string; bold, italic: boolean): Integer; var L: TStringList; testFont: String; aliasFont: boolean; begin testFont := fontName; aliasFont := true; // aliases case lowercase(testFont) of '', 'default', 'sans', 'helvetica', 'arial': testFont := FONT_ARIAL+';'+FONT_DEJAVU_SANS+';'+FONT_LIB_SANS+';'+FONT_FREE_SANS; 'times', 'serif': testFont := FONT_TIMES+';'+FONT_DEJAVU_SERIF; 'courier', 'mono', 'fixed': testFont := FONT_COURIER+';'+FONT_DEJAVU_MONO; 'dejavu sans': testFont := FONT_DEJAVU_SANS+';'+FONT_ARIAL+';'+FONT_LIB_SANS+';'+FONT_FREE_SANS; 'liberation sans': testFont := FONT_LIB_SANS+';'+FONT_DEJAVU_SANS+';'+FONT_ARIAL+';'+FONT_FREE_SANS; 'freesans': testFont := FONT_FREE_SANS+';'+FONT_LIB_SANS+';'+FONT_DEJAVU_SANS+';'+FONT_ARIAL; else aliasFont := false; end; L := TStringList.Create; try L.StrictDelimiter := true; L.Delimiter := ';'; L.DelimitedText := testFont; result := -1; for testFont in L do begin result := FindCachedFont(cache, testFont, bold, italic); if (result<0) and bold and italic then begin result := FindCachedFont(cache, testFont, true, false); if result<0 then result := FindCachedFont(cache, testFont, false, true); if result<0 then result := FindCachedFont(cache, testFont, false, false); end else if (result<0) and bold then result := FindCachedFont(cache, testFont, false, false); if result>=0 then break; end; if (result<0) and not aliasFont then begin result := MatchCachedFont(Cache, FONT_ARIAL, bold, italic); end; finally L.Free; end; end; function TFontCache.GetCount: Integer; begin result := Length(FFontList); end; function TFontCache.GetCachedName(aName: string; bold, italic: boolean ): string; begin result := format('%s_%d_%d',[aName, ord(bold), ord(italic)]); end; function TFontCache.IndexOfCachedFont(aName: string): Integer; var i: Integer; begin result := -1; for i:=0 to Length(FFontList)-1 do begin if FFontList[i].fontName=aName then begin result := i; break; end; end; end; function TFontCache.FindFile(aName: string; bold, italic: boolean): string; var index: Integer; cache: TJSonObject; arr: TJSONArray; cachedName: string; fontItem: TFontItem; begin cachedName := GetCachedName(aName, bold, italic); index := IndexOfCachedFont(cachedName); if index>=0 then begin result := fFontList[index].fontFile; exit; end; // remove the foundry if exists index := pos('[', aName); if index>0 then begin Delete(aName, index, Length(aName)); aName := Trim(aName); end; cache := BuildFontCache; index := MatchCachedFont(cache, aName, bold, italic); if index>=0 then begin arr := cache.Arrays['fonts']; with TJSonObject(arr[index]) do begin result := Strings['filename'] end end else begin result := aName; end; fontItem := TFontItem.Create; fontItem.fontName := cachedName; fontItem.fontFile := result; fontItem.fontID := -1; fontItem.data := nil; index := Length(fFontList); SetLength(fFontList, index+1); fFontList[index] := fontItem; cache.Free; end; destructor TFontCache.destroy; var i: Integer; begin for i:=0 to Length(fFontList)-1 do fFontList[i].Free; inherited destroy; end; function TFontCache.IndexOfFile(aName: string; bold, italic: boolean): Integer; var cachedFontName: String; begin cachedFontName := GetCachedName(aName, bold, italic); result := IndexOfCachedFont(cachedFontName); if result<0 then begin FindFile(aName, bold, italic); result := Count-1; end; end; { TVirtualBitmap } function TVirtualBitmap.GetCanvas: TCanvas; begin if fCanvas=nil then fCanvas := TVirtualCanvas.Create(fImg); result := fCanvas; end; function TVirtualBitmap.GetHeight: Integer; begin result := fImg.Height; end; function TVirtualBitmap.GetStream: TMemoryStream; var Writer: TFPWriterBMP; begin Writer:=TFPWriterBMP.create; result := TMemoryStream.Create; writer.ImageWrite(result, fImg); result.position := 0; Writer.Free; end; function TVirtualBitmap.GetWidth: Integer; begin result := fImg.Width; end; procedure TVirtualBitmap.SetHeight(AValue: Integer); begin fImg.Height := AValue; end; procedure TVirtualBitmap.SetWidth(AValue: Integer); begin fImg.Width := AValue; end; constructor TVirtualBitmap.create; begin inherited create; fImg := TFpMemoryImage.Create(0, 0); fImg.UsePalette := false; end; destructor TVirtualBitmap.destroy; begin fCanvas.Free; fImg.Free; inherited destroy; end; procedure TVirtualBitmap.SaveToFile(filename: string; Writer: TFPCustomImageWriter); var workStream: TStream; begin if Writer<>nil then workStream := TFileStream.Create(filename, fmCreate) else workStream := Stream; try if Writer<>nil then SaveToStream(workStream, writer) else TMemoryStream(workStream).SaveToFile(filename); finally workStream.Free; end; end; procedure TVirtualBitmap.SaveToStream(aStream: TStream; Writer: TFPCustomImageWriter); var workStream: TMemoryStream; begin if Writer<>nil then writer.ImageWrite(aStream, fImg) else begin workStream := Stream; try workStream.SaveToStream(aStream); finally workStream.Free; end; end; end; procedure TVirtualBitmap.SetSize(aWidth, aHeight: integer); begin fImg.SetSize(aWidth, aHeight); end; // this is basically the same function as ConvertGraphicToFPImage from PowerPDF // PDFImageLazTools.pas, including TRasterImageHelper, if you change this dont // forget to update that, and viceversa. procedure TVirtualBitmap.LoadFromGraphic(Graphic: TGraphic); var fpImg: TFPMemoryImage; rawImgStream: TMemoryStream = nil; useOriginalStream: boolean = false; begin if (Graphic is TRasterImage) then useOriginalStream := TRasterImage(Graphic).RequestRawStream(rawImgStream); if not useOriginalStream then begin rawImgStream := TMemoryStream.Create; Graphic.SaveToStream(rawImgStream); rawImgStream.Position := 0; end; try fpImg := TFPMemoryImage.Create(0, 0); fpImg.UsePalette := false; try fpImg.LoadFromStream(rawImgStream); fImg.Free; fImg := fpImg; except fpImg.Free; end; finally if not useOriginalStream then rawImgStream.Free; end; end; function TVirtualBitmap.GetStreamAsFormat(formatExtension: string; useAlpha:boolean): TMemoryStream; var writerClass: TFPCustomImageWriterClass; Writer: TFPCustomImageWriter; begin writerClass := fImg.FindWriterFromExtension(formatExtension); if writerClass=nil then result := nil else begin result := TMemoryStream.Create; writer := WriterClass.Create; if useAlpha and (Writer is TFPWriterPNG) then TFPWriterPNG(writer).UseAlpha := useAlpha; fImg.SaveToStream(result, writer); writer.Free; end; end; procedure TVirtualBitmap.Clear; begin SetSize(0, 0); end; { TVirtualCanvas } procedure TVirtualCanvas.UpdateFontProperties; var aFont: TFreeTypeFont; begin aFont := FontManager.GetFont(Font.Name, fsBold in Font.Style, fsItalic in Font.Style); aFont.SmallLinePadding := Font.Size<0; aFont.SizeInPoints := IfThen(Font.Size=0, 10, Abs(Font.Size)); aFont.DPI := Font.PixelsPerInch; // TODO: convert Font.Quality case fFontQuality of fqMono: aFont.Quality := grqMonochrome; fqLow: aFont.Quality := grqLowQuality; fqHigh: aFont.Quality := grqHighQuality; end; aFont.Hinted := fFontHinted; aFont.ClearType := fFontClearType; aFont.UnderlineDecoration := fsUnderline in Font.Style; aFont.Orientation := Font.Orientation; end; procedure TVirtualCanvas.UpdateBrushProperties; begin fimgCanvas.Brush.FPColor := TColorToFPColor(Brush.Color); fimgCanvas.Brush.Style := Brush.Style; end; procedure TVirtualCanvas.UpdatePenProperties; begin fImgCanvas.Pen.FPColor := TColorToFPColor(Pen.Color); fImgCanvas.Pen.Style := Pen.Style; fImgCanvas.Pen.Width := Pen.Width; fImgCanvas.Pen.Mode := Pen.Mode; end; procedure TVirtualCanvas.CreateBrush; begin end; procedure TVirtualCanvas.CreateFont; begin if FontManager=nil then begin FontManager := TFontManager.Create; // TODO: mac and user supplied font dir FontManager.SearchPath := 'fonts'; {$IFDEF MSWindows} FontManager.SearchPath := 'c:\windows\fonts'; {$ELSE} FontManager.SearchPath := '/usr/share/fonts/truetype'; {$ENDIF} end; end; procedure TVirtualCanvas.CreateHandle; begin fImgCanvas := TNoGuiImageCanvas.create(fImg); fDrawer := TFPImageFreeTypeDrawer.Create(fImg); SetHandle(1); end; procedure TVirtualCanvas.CreatePen; begin end; procedure TVirtualCanvas.DeselectHandles; begin end; procedure TVirtualCanvas.RealizeAntialiasing; begin //inherited RealizeAntialiasing; end; procedure TVirtualCanvas.FillRect(const ARect: TRect); begin RequiredState([csHandleValid, csBrushValid]); UpdateBrushProperties; fimgCanvas.FillRect(ARect); end; // the conversion from TFPColor to TColor remove the alpha component from // the color which ruins it, so plain canvas.FillRect can't be used for this. procedure TVirtualCanvas.Fill(color: TFPColor); var oldColor: TFPColor; begin RequiredState([csHandleValid]); oldColor := fimgCanvas.Brush.FPColor; fimgCanvas.Brush.FPColor := color; fImgCanvas.FillRect(0, 0, fImg.Width, fImg.Height); fimgCanvas.Brush.FPColor := oldColor; end; procedure TVirtualCanvas.Polygon(Points: PPoint; NumPts: Integer; Winding: boolean); var arr: array of TPoint; i: Integer; begin if NumPts=0 then exit; RequiredState([csHandleValid, csPenValid, csBrushValid]); UpdatePenProperties; UpdateBrushProperties; SetLength(Arr{%H-}, NumPts); for i:=1 to NumPts do Arr[i-1] := Points[i-1]; fImgCanvas.Polygon(Arr); end; procedure TVirtualCanvas.Polyline(Points: PPoint; NumPts: Integer); var arr: array of TPoint; i: Integer; begin if NumPts=0 then exit; RequiredState([csHandleValid, csPenValid]); UpdatePenProperties; SetLength(Arr{%H-}, NumPts); for i:=1 to NumPts do Arr[i-1] := Points[i-1]; fImgCanvas.Polyline(Arr); end; procedure TVirtualCanvas.TextOut(X, Y: Integer; const Text: String); var aFont: TFreeTypeFont; sz: TSize; Points: array of TPoint; oldPenStyle: TFPPenStyle; procedure RotatePoints; var aSin,aCos, px, py: double; i: Integer; begin SinCos(aFont.Orientation * PI / 1800, aSin, aCos); for i:=0 to Length(points)-1 do begin px := aCos * points[i].x + -aSin * points[i].y; py := aSin * points[i].x + aCos * points[i].y; points[i].x :=round(x + px); points[i].y :=round(y - py); end; end; begin RequiredState([csHandleValid, csFontValid]); UpdateFontProperties; UpdateBrushProperties; aFont := FontManager.Font; sz := TextExtent(Text); if fImgCanvas.Brush.Style=bsSolid then begin oldPenStyle := fImgCanvas.Pen.Style; fImgCanvas.Pen.Style := psClear; SetLength(Points{%H-}, 4); Points[0] := point(0, 0); Points[1] := point(sz.Width, 0); Points[2] := point(sz.Width, -sz.Height); Points[3] := point(0, -sz.Height); RotatePoints; fImgCanvas.Polygon(Points); fImgCanvas.Pen.Style := oldPenStyle; end; SetLength(Points, 2); Points[0] := point(0, -Round(aFont.Ascent)); Points[1] := point(sz.Width, -Round(aFont.Ascent)); RotatePoints; // easy draws text from the base line // while we want to draw from the top fDrawer.DrawText(Text, aFont, Points[0].x, Points[0].y, TColorToFPColor(Font.Color)); fImgCanvas.MoveTo(Points[1].x, Points[1].y); end; procedure TVirtualCanvas.TextRect(ARect: TRect; X, Y: integer; const Text: string; const Style: TTextStyle); var aFont: TFreeTypeFont; Points: array of TPoint; oldPenStyle: TFPPenStyle; procedure RotatePoints; var aSin,aCos, px, py: double; i: Integer; begin SinCos(aFont.Orientation * PI / 1800, aSin, aCos); for i:=0 to Length(points)-1 do begin px := aCos * points[i].x + -aSin * points[i].y; py := aSin * points[i].x + aCos * points[i].y; points[i].x :=round(x + px); points[i].y :=round(y - py); end; end; begin RequiredState([csHandleValid, csFontValid]); UpdateFontProperties; UpdateBrushProperties; // TODO: complete all styles and alignment aFont := FontManager.Font; if Style.Opaque then begin oldPenStyle := fImgCanvas.Pen.Style; fImgCanvas.Pen.Style := psClear; SetLength(Points{%H-}, 4); Points[0] := point(0, 0); Points[1] := point(aRect.Width, 0); Points[2] := point(aRect.Width, -aRect.Height); Points[3] := point(0, -aRect.Height); RotatePoints; fImgCanvas.Polygon(Points); fImgCanvas.Pen.Style := oldPenStyle; end; SetLength(Points, 2); Points[0] := point(0, -Round(aFont.Ascent)); Points[1] := point(ARect.Width, -Round(aFont.Ascent)); RotatePoints; // easy draws text from the base line // while we want to draw from the top fDrawer.DrawText(Text, aFont, Points[0].x, Points[0].y, TColorToFPColor(Font.Color)); fImgCanvas.MoveTo(Points[1].x, Points[1].y); end; procedure TVirtualCanvas.DoMoveTo(x, y: integer); begin RequiredState([csHandleValid]); fImgCanvas.MoveTo(x, y); end; procedure TVirtualCanvas.DoLineTo(x, y: integer); begin RequiredState([csHandleValid, csPenValid]); UpdatePenProperties; fImgCanvas.LineTo(x, y); fImgCanvas.MoveTo(x, y); end; procedure TVirtualCanvas.DoLine(x1, y1, x2, y2: integer); begin RequiredState([csHandleValid]); UpdatePenProperties; fImgCanvas.Line(x1, y1, x2, y2); end; procedure TVirtualCanvas.Rectangle(X1, Y1, X2, Y2: Integer); begin RequiredState([csHandleValid, csPenValid, csBrushValid]); UpdatePenProperties; UpdateBrushProperties; fImgCanvas.Rectangle(X1, Y1, X2, Y2); end; constructor TVirtualCanvas.create(image: TFpMemoryImage); begin inherited create; fImg := image; fFontQuality := fqHigh; fFontHinted := true; fFontClearType := false; end; procedure TVirtualCanvas.Draw(X, Y: Integer; SrcGraphic: TVirtualBitmap); begin RequiredState([csHandleValid]); fImgCanvas.Draw(x, y, SrcGraphic.fImg); end; procedure TVirtualCanvas.StretchDraw(const DestRect: TRect; SrcGraphic: TVirtualBitmap); begin RequiredState([csHandleValid]); with DestRect do fImgCanvas.StretchDraw(left,top,width,height, SrcGraphic.fImg); end; destructor TVirtualCanvas.destroy; begin fImgCanvas.Free; fDrawer.Free; inherited destroy; end; function TVirtualCanvas.TextExtent(const Text: string): TSize; begin RequiredState([csHandleValid, csFontValid]); UpdateFontProperties; result := FontManager.TextExtent(Text); end; function TVirtualCanvas.TextHeight(const Text: string): Integer; begin RequiredState([csHandleValid, csFontValid]); UpdateFontProperties; result := FontManager.TextHeight(Text); end; initialization // simulate a standard display ScreenInfo.PixelsPerInchX := 96; ScreenInfo.PixelsPerInchY := 96; ScreenInfo.ColorDepth := 32; ScreenInfo.Initialized := true; finalization FontManager.Free; end.