lazarus/components/lazreport/source/lr_ngcanvas.pas
2020-09-19 17:56:34 +00:00

1181 lines
30 KiB
ObjectPascal

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 (i<nodes-1) do
begin
if (nodeX[i]>nodeX[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<nodes do
begin
if (nodeX[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.