doublecmd/components/Image32/source/Img32.Fmt.SVG.pas
2025-03-23 21:11:06 +03:00

477 lines
14 KiB
ObjectPascal

unit Img32.Fmt.SVG;
(*******************************************************************************
* Author : Angus Johnson *
* Version : 4.7 *
* Date : 6 January 2025 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2019-2025 *
* Purpose : SVG file format extension for TImage32 *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************)
interface
{$I Img32.inc}
uses
{$IFDEF MSWINDOWS} Windows, {$ENDIF}
{$IF NOT DEFINED(NEWPOSFUNC) OR DEFINED(FPC)} StrUtils, {$IFEND}
{$IFDEF UNICODE} AnsiStrings, {$ENDIF}
SysUtils, Classes, Math,
{$IFDEF XPLAT_GENERICS} Generics.Collections, Generics.Defaults, {$ENDIF}
Img32, Img32.Vector, Img32.SVG.Core, Img32.SVG.Reader
{$IF DEFINED(USING_LCL)}, Types{$IFEND}
;
type
TImageFormat_SVG = class(TImageFormat)
public
class function IsValidImageStream(stream: TStream): Boolean; override;
function LoadFromStream(stream: TStream;
img32: TImage32; imgIndex: integer = 0): Boolean; override;
// SaveToStream: not implemented for SVG streams
procedure SaveToStream(stream: TStream;
img32: TImage32; quality: integer = 0); override;
class function CanCopyToClipboard: Boolean; override;
class function CopyToClipboard(img32: TImage32): Boolean; override;
class function CanPasteFromClipboard: Boolean; override;
class function PasteFromClipboard(img32: TImage32): Boolean; override;
end;
TSvgListObject = class
xml : string;
name : string;
end;
TSvgImageList32 = class(TInterfacedObj, INotifySender)
private
fReader : TSvgReader;
{$IFDEF XPLAT_GENERICS}
fList : TList<TSvgListObject>;
{$ELSE}
fList : TList;
{$ENDIF}
fDefWidth : integer;
fDefHeight : integer;
fRecipientList : TRecipients;
fUpdateCnt : integer;
{$IFDEF MSWINDOWS}
fResName : string;
procedure SetResName(const resName: string);
{$ENDIF}
procedure SetDefWidth(value: integer);
procedure SetDefHeight(value: integer);
protected
procedure Changed; virtual;
procedure BeginUpdate;
procedure EndUpdate;
procedure NotifyRecipients(notifyFlag: TImg32Notification);
public
constructor Create;
destructor Destroy; override;
procedure Clear;
function Count: integer;
function Find(const aName: string): integer;
procedure AddRecipient(recipient: INotifyRecipient);
procedure DeleteRecipient(recipient: INotifyRecipient);
function CreateImage(index: integer): TImage32;
procedure GetImage(index: integer; image: TImage32); overload;
procedure GetImage(index: integer; image: TImage32; out aName: string); overload;
procedure Add(const aName, xml: string);
procedure AddFromFile(const aName, filename: string);
procedure AddFromResource(const aName, resName: string; resType: PChar);
procedure Insert(index: integer; const name, xml: string);
procedure Move(currentIndex, newIndex: integer);
procedure Delete(index: integer);
property DefaultWidth: integer read fDefWidth write SetDefWidth;
property DefaultHeight: integer read fDefHeight write SetDefHeight;
{$IFDEF MSWINDOWS}
property ResourceName: string read fResName write SetResName;
{$ENDIF}
end;
implementation
//------------------------------------------------------------------------------
// Three routines used to enumerate a resource type
//------------------------------------------------------------------------------
function Is_IntResource(lpszType: PChar): Boolean;
begin
Result := NativeUInt(lpszType) shr 16 = 0;
end;
//------------------------------------------------------------------------------
function ResourceNameToString(lpszName: PChar): string;
begin
if Is_IntResource(lpszName) then
Result := '#' + IntToStr(NativeUInt(lpszName)) else
Result := lpszName;
end;
//------------------------------------------------------------------------------
function EnumResNameProc(hModule: HMODULE; lpszType, lpszName: PChar;
lParam: NativeInt): Boolean; stdcall;
var
n: string;
begin
n:= ResourceNameToString(lpszName);
TSvgImageList32(lParam).AddFromResource(n, n, lpszType);
Result := true;
end;
//------------------------------------------------------------------------------
// TSvgImageList32
//------------------------------------------------------------------------------
constructor TSvgImageList32.Create;
begin
fReader := TSvgReader.Create;
{$IFDEF XPLAT_GENERICS}
fList := TList<TSvgListObject>.Create;
{$ELSE}
fList := TList.Create;
{$ENDIF}
end;
//------------------------------------------------------------------------------
destructor TSvgImageList32.Destroy;
begin
NotifyRecipients(inDestroy);
Clear;
fList.Free;
fReader.Free;
inherited;
end;
//------------------------------------------------------------------------------
{$IFDEF MSWINDOWS}
procedure TSvgImageList32.SetResName(const resName: string);
begin
if fResName = resName then Exit;
fResName := resName;
BeginUpdate;
try
Clear;
EnumResourceNames(HInstance, PChar(resName), @EnumResNameProc, lParam(self));
finally
EndUpdate;
end;
end;
//------------------------------------------------------------------------------
{$ENDIF}
function TSvgImageList32.Count: integer;
begin
result := fList.Count;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.Clear;
var
i: integer;
begin
for i := 0 to fList.Count -1 do
TSvgListObject(fList[i]).Free;
fList.Clear;
Changed;
end;
//------------------------------------------------------------------------------
function TSvgImageList32.Find(const aName: string): integer;
var
i: integer;
begin
for i := 0 to fList.Count -1 do
with TSvgListObject(fList[i]) do
if SameText(name, aName) then
begin
Result := i;
Exit;
end;
Result := -1;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.GetImage(index: integer; image: TImage32; out aName: string);
begin
if not Assigned(image) or (index < 0) or (index >= count) then Exit;
if image.IsEmpty then
image.SetSize(fDefWidth, fDefHeight);
with TSvgListObject(fList[index]) do
begin
fReader.LoadFromString(xml);
aName := name;
end;
fReader.DrawImage(image, true);
end;
//------------------------------------------------------------------------------
function TSvgImageList32.CreateImage(index: integer): TImage32;
begin
Result := TImage32.Create(DefaultWidth, DefaultHeight);
GetImage(index, Result);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.GetImage(index: integer; image: TImage32);
var
dummy: string;
begin
GetImage(index, image, dummy);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.Add(const aName, xml: string);
begin
Insert(count, aName, xml);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.AddFromFile(const aName, filename: string);
begin
if not FileExists(filename) then Exit;
with TStringList.Create do
try
LoadFromFile(filename);
Self.Insert(Self.Count, aName, Text);
finally
Free;
end;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.AddFromResource(const aName, resName: string; resType: PChar);
var
rs: TResourceStream;
ansi: AnsiString;
begin
rs := TResourceStream.Create(hInstance, resName, resType);
try
SetLength(ansi, rs.Size);
rs.Read(ansi[1], rs.Size);
Self.Insert(Self.Count, aName, string(ansi));
finally
rs.Free;
end;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.Insert(index: integer; const name, xml: string);
var
lo: TSvgListObject;
begin
if index < 0 then index := 0
else if index > Count then index := Count;
lo := TSvgListObject.Create;
lo.name := name;
lo.xml := xml;
fList.Insert(index, lo);
Changed;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.Move(currentIndex, newIndex: integer);
begin
fList.Move(currentIndex, newIndex);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.Delete(index: integer);
begin
TSvgListObject(fList[index]).Free;
fList.Delete(index);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.BeginUpdate;
begin
inc(fUpdateCnt);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.EndUpdate;
begin
dec(fUpdateCnt);
if fUpdateCnt = 0 then Changed;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.Changed;
begin
if (fUpdateCnt = 0) then
NotifyRecipients(inStateChange);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.SetDefWidth(value: integer);
begin
if fDefWidth = value then Exit;
fDefWidth := value;
Changed;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.SetDefHeight(value: integer);
begin
if fDefHeight = value then Exit;
fDefHeight := value;
Changed;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.AddRecipient(recipient: INotifyRecipient);
var
len: integer;
begin
len := Length(fRecipientList);
SetLength(fRecipientList, len+1);
fRecipientList[len] := Recipient;
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.DeleteRecipient(recipient: INotifyRecipient);
var
i, highI: integer;
begin
highI := High(fRecipientList);
i := highI;
while (i >= 0) and (fRecipientList[i] <> Recipient) do dec(i);
if i < 0 then Exit;
if i < highI then
System.Move(fRecipientList[i+1], fRecipientList[i],
(highI - i) * SizeOf(INotifyRecipient));
SetLength(fRecipientList, highI);
end;
//------------------------------------------------------------------------------
procedure TSvgImageList32.NotifyRecipients(notifyFlag: TImg32Notification);
var
i: integer;
begin
if fUpdateCnt > 0 then Exit;
for i := High(fRecipientList) downto 0 do
try
//when destroying in a finalization section
//it's possible for recipients to have been destroyed
//without their destructors being called.
fRecipientList[i].ReceiveNotification(self, notifyFlag);
except
end;
end;
//------------------------------------------------------------------------------
// Loading (reading) SVG images from file ...
//------------------------------------------------------------------------------
function TImageFormat_SVG.LoadFromStream(stream: TStream;
img32: TImage32; imgIndex: integer = 0): Boolean;
var
r: TRectWH;
sx: double;
begin
with TSvgReader.Create do
try
Result := LoadFromStream(stream);
if not Result then Exit;
r := RootElement.viewboxWH;
img32.BeginUpdate;
try
if img32.IsEmpty then
begin
with RootElement do
if Width.IsValid and Height.IsValid then
img32.SetSize(
Round(Width.GetValue(defaultSvgWidth, 0)),
Round(Height.GetValue(defaultSvgHeight, 0)))
else if not r.IsEmpty then
img32.SetSize(Round(r.Width), Round(r.Height))
else
img32.SetSize(defaultSvgWidth, defaultSvgHeight);
end
else if not r.IsEmpty then
begin
// scale the SVG to best fit the image dimensions
sx := GetScaleForBestFit(r.Width, r.Height, img32.Width, img32.Height);
img32.SetSize(Round(r.Width * sx), Round(r.Height * sx));
end;
//draw the SVG image to fit inside the canvas
DrawImage(img32, True);
finally
img32.EndUpdate;
end;
finally
Free;
end;
end;
//------------------------------------------------------------------------------
// Saving (writing) SVG images to file (not currently implemented) ...
//------------------------------------------------------------------------------
class function TImageFormat_SVG.IsValidImageStream(stream: TStream): Boolean;
var
i, savedPos, len: integer;
buff: array [1..1024] of AnsiChar;
begin
Result := false;
savedPos := stream.Position;
len := Min(1024, stream.Size - savedPos);
stream.Read(buff[1], len);
stream.Position := savedPos;
for i := 1 to len -4 do
begin
if buff[i] < #9 then Exit
else if (buff[i] = '<') and
(buff[i +1] = 's') and
(buff[i +2] = 'v') and
(buff[i +3] = 'g') then
begin
Result := true;
break;
end;
end;
end;
//------------------------------------------------------------------------------
procedure TImageFormat_SVG.SaveToStream(stream: TStream;
img32: TImage32; quality: integer);
begin
//not enabled
end;
//------------------------------------------------------------------------------
class function TImageFormat_SVG.CanCopyToClipboard: Boolean;
begin
Result := false;
end;
//------------------------------------------------------------------------------
class function TImageFormat_SVG.CopyToClipboard(img32: TImage32): Boolean;
begin
Result := false;
end;
//------------------------------------------------------------------------------
class function TImageFormat_SVG.CanPasteFromClipboard: Boolean;
begin
Result := false;
end;
//------------------------------------------------------------------------------
class function TImageFormat_SVG.PasteFromClipboard(img32: TImage32): Boolean;
begin
Result := false;
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
initialization
TImage32.RegisterImageFormatClass('SVG', TImageFormat_SVG, cpLow);
end.