(* ImageEn Build 7.0.0.06.2637 @ 7-4-17 14:58:42.679 *) (* Copyright (c) 1998-2017 by Carlotta Calandra. All rights reserved. Copyright (c) 2011-2017 by Xequte Software. This software comes without express or implied warranty. In no case shall the author be liable for any damage or unwanted behavior of any computer hardware and/or software. Author grants you the right to include the component in your application, whether COMMERCIAL, SHAREWARE, or FREEWARE. ImageEn, IEvolution and ImageEn ActiveX may not be included in any commercial, shareware or freeware libraries or components. www.ImageEn.com *) (* File: iexSVG.pas Description: Exporting to Scalable Vector Graphics format File version 1001 Doc revision 1001 *) unit iexSVG; {$R-} {$Q-} {$I ie.inc} interface uses {$ifdef IEHASUITYPES} System.UITypes, {$endif} Windows, Messages, Graphics, Controls, Forms, Classes, SysUtils, StdCtrls, ExtCtrls, Contnrs, hyiedefs, iexBitmaps, ieview, hyieutils; procedure WriteSVGFileOrStream(const FileName: String; Stream: TStream; IEView: TIEView; Bitmap: TIEBitmap; ImageFormat: TIOFileType; var xProgress: TProgressRec); function CreateSVGImageTag(Bitmap: TIEBitmap; SaveFormat: TIOFileType; X, Y, Width, Height: Integer; BorderWidth: Integer = 1; BorderColor: TColor = clNone_; Rotate: Double = 0; Transparency: Integer = 255): string; function CreateSVGShapeTag(Shape: TIEShape; X, Y, Width, Height: Integer; BorderWidth: Integer; BorderColor: TColor; FillColor: TColor; Rotate: Double; Transparency: Integer): string; function CreateSVGLineTag(Point1, Point2: TPoint; LineWidth: Integer; LineColor: TColor; Transparency: Integer): string; function CreateSVGPolylineTag(Points: array of TPoint; PointCount: Integer; Closed: Boolean; BorderWidth: Integer; BorderColor: TColor; FillColor: TColor; Rotate: Double; RotateCenter: TPoint; Transparency: Integer): string; function CreateSVGTextTag(Text: string; Font: TFont; X, Y, Width, Height: Integer; TextAlign: TIEAlignment; TextLayout: TIELayout; TextAngle: Double; BorderWidth: Integer; BorderColor: TColor; FillColor: TColor; BorderShape : TIEShape; Transparency: Integer): string; implementation uses {$ifdef DelphiXE5orNewer}System.Types,{$endif} imageenview, math, imageenio, iexCanvasUtils, ietextc, iesettings, ievect {$IfDef UNICODE}, AnsiStrings {$EndIf}; const SVG_Opening_Tag = ''; SVG_Closing_Tag = ''; SVG_Image_Tag = ''; SVG_Rectangle_Tag = ''; SVG_RoundRect_Tag = ''; SVG_Ellipse_Tag = ''; SVG_Polygon_Tag = ''; SVG_Polyline_Tag = ''; SVG_Text_Tag = '%s'; SVG_Line_Tag = ' '; XML_Comment_Block = ''; function ColorToSVG(Color: TColor): string; begin if Color = clNone_ then Result := 'none' else Result := ColorToHex( Color ); end; // Note: Result is prefixed with space function GetSVGRotationElement(Angle: Double; RotateCenter: TPoint): string; const SVG_Trans_Rotation_Element = ' transform="rotate(%d %d %d)"'; begin Result := ''; if Angle <> 0 then Result := format( SVG_Trans_Rotation_Element, [ -Round( Angle ), RotateCenter.X, RotateCenter.Y ]); end; // Transparency: 255=opaque, 0=transparent // Note: Result is prefixed with space function GetSVGTransparencyElement(Transparency: Integer): string; const SVG_Opacity_Element = ' opacity="%s"'; begin Result := ''; if Transparency <> 255 then Result := format( SVG_Opacity_Element, [ IEFloatToFormatString( Transparency / 255, 2, True )]); end; function CreateSVGLineTag(Point1, Point2: TPoint; LineWidth: Integer; LineColor: TColor; Transparency: Integer): string; begin Result := format( SVG_Line_Tag, [ Point1.X, Point1.Y, Point2.X, Point2.Y, ColorToSVG( LineColor ), LineWidth, GetSVGTransparencyElement( Transparency ) ]); end; function CreateSVGPolylineTag(Points: array of TPoint; PointCount: Integer; Closed: Boolean; BorderWidth: Integer; BorderColor: TColor; FillColor: TColor; Rotate: Double; RotateCenter: TPoint; Transparency: Integer): string; var i: Integer; pointStr: string; begin Result := ''; pointStr := ''; for i := 0 to PointCount - 1 do pointStr := pointStr + format( '%d,%d ', [ Points[i].X, Points[i].Y ]); if pointStr = '' then exit; SetLength( pointStr, Length( pointStr ) - 2 ); if Closed then Result := format( SVG_Polygon_Tag, [ pointStr, ColorToSVG( BorderColor ), BorderWidth, ColorToSVG( FillColor ), GetSVGRotationElement( Rotate, RotateCenter ) + GetSVGTransparencyElement( Transparency ) ]) else Result := format( SVG_Polyline_Tag, [ pointStr, ColorToSVG( BorderColor ), BorderWidth, GetSVGRotationElement( Rotate, RotateCenter ) + GetSVGTransparencyElement( Transparency ) ]); end; function CreateSVGShapeTag(Shape: TIEShape; X, Y, Width, Height: Integer; BorderWidth: Integer; BorderColor: TColor; FillColor: TColor; Rotate: Double; Transparency: Integer): string; const Rect_Rounding = 20; var arPts : Array[ 0 .. Shape_Array_Max_Points ] of TPoint; pts: Integer; RotateCenter: TPoint; begin Result := ''; if (( BorderWidth = 0 ) or ( BorderColor = clNone_ )) and ( FillColor = clNone_ ) then exit; // Nothing to see RotateCenter := Point( X + Width div 2, Y + Height div 2 ); case Shape of iesRectangle : Result := format( SVG_Rectangle_Tag, [ X, Y, Width, Height, ColorToSVG( BorderColor ), BorderWidth, ColorToSVG( FillColor ), GetSVGRotationElement( Rotate, RotateCenter ) + GetSVGTransparencyElement( Transparency ) ]); iesRoundRect : Result := format( SVG_RoundRect_Tag, [ X, Y, Width, Height, Rect_Rounding, Rect_Rounding, ColorToSVG( BorderColor ), BorderWidth, ColorToSVG( FillColor ), GetSVGRotationElement( Rotate, RotateCenter ) + GetSVGTransparencyElement( Transparency ) ]); iesEllipse : Result := format( SVG_Ellipse_Tag, [ X + Width div 2, Y + Height div 2, Width div 2, Height div 2, ColorToSVG( BorderColor ), BorderWidth, ColorToSVG( FillColor ), GetSVGTransparencyElement( Transparency ) ]); // No rotation of ellipses else try IEGenerateShapePoints( arPts, pts, Shape, X, Y, Width, Height ); Result := CreateSVGPolylineTag( arPts, pts, True, BorderWidth, BorderColor, FillColor, Rotate, RotateCenter, Transparency ); except // UNSUPPORTED SHAPE end; end; end; function CreateSVGImageTag(Bitmap: TIEBitmap; SaveFormat: TIOFileType; X, Y, Width, Height: Integer; BorderWidth: Integer = 1; BorderColor: TColor = clNone_; Rotate: Double = 0; Transparency: Integer = 255): string; var io: TImageEnIO; ms: TMemoryStream; base64Img: Ansistring; fileType: string; RotateCenter: TPoint; begin // todo: support masking https://www.w3.org/TR/SVG/masking.html#MaskProperty Result := ''; if ( Bitmap.Width < 2 ) or ( Bitmap.Height < 2 ) then exit; ms := TMemoryStream.Create(); io := TImageEnIO.Create( nil ); try io.AttachedIEBitmap := Bitmap; io.SaveToText( ms, SaveFormat, ietfBase64); SetString( base64Img , PAnsiChar( ms.Memory), ms.Size ); finally io.AttachedIEBitmap := nil; FreeAndNil( io ); FreeAndNil( ms ); end; fileType := IEFileFormatGetInfo( SaveFormat ).SuitableExtension; RotateCenter := Point( X + Width div 2, Y + Height div 2 ); if ( Length( base64Img ) > 2 ) and ( base64Img[ Length( base64Img ) - 1] = #13 ) and ( base64Img[ Length( base64Img )] = #10 ) then SetLength( base64Img, Length( base64Img ) -2 ); Result := format( SVG_Image_Tag, [ X, Y, Width, Height, fileType, base64Img, GetSVGRotationElement( Rotate, RotateCenter ) + GetSVGTransparencyElement( Transparency )]) + CreateSVGShapeTag( iesRectangle, X, Y, Width, Height, BorderWidth, BorderColor, clNone_, Rotate, Transparency ); end; function IEEncodeHTMLString(Text: String): String; var i: integer; begin result := ''; for i := 1 to length( Text ) do begin if Text[i] = '"' then result := result + '"' else if Text[i] = '<' then result := result + '<' else if Text[i] = '>' then result := result + '>' else if Text[i] = '''' then result := result + ''' else if Text[i] = '&' then result := result + '&' else result := result + Text[i]; end; end; function CreateSVGTextTag(Text: string; Font: TFont; X, Y, Width, Height: Integer; TextAlign: TIEAlignment; TextLayout: TIELayout; TextAngle: Double; BorderWidth: Integer; BorderColor: TColor; FillColor: TColor; BorderShape : TIEShape; Transparency: Integer): string; const Use_Dominant_Baseline = False; // Supported by Chrome and Firefox, but not IE Fallback_Fonts = ', Arial, Helvetica'; // Fonts to use if the specified font is not supported var borderPadding: Integer; fontFamily: string; fontStyle, fontWeight: string; shapeTag, textTag: string; textX, textY: Integer; textAnchor, alignBaseline: string; RotateCenter: TPoint; begin Result := ''; borderPadding := 0; if BorderColor <> clNone_ then borderPadding := BorderWidth + 3; case TextAlign of iejLeft : begin textX := X + borderPadding; textAnchor := 'start'; end; iejRight : begin textX := X + Width - borderPadding; textAnchor := 'end'; end; else begin textX := X + Width div 2; textAnchor := 'middle'; end; end; alignBaseline := 'hanging'; if Use_Dominant_Baseline then case TextLayout of ielTop : begin textY := Y + borderPadding; alignBaseline := 'hanging'; end; ielBottom : begin textY := Y + Height - borderPadding; alignBaseline := 'baseline'; end; else begin textY := Y + Height div 2; alignBaseline := 'middle'; end; end else case TextLayout of ielTop : textY := Y + borderPadding + abs( Font.Height ); ielBottom : textY := Y + Height - borderPadding; else textY := Y + Height - ( Height - abs( Font.Height )) div 2; end; fontStyle := ''; if fsItalic in Font.Style then fontStyle := ' font-style="italic"'; fontWeight := ''; if fsBold in Font.Style then fontWeight := ' font-weight="bold"'; fontFamily := Font.Name; if Pos( ' ', fontFamily ) > 0 then fontFamily := '''' + fontFamily + ''''; RotateCenter := Point( X + Width div 2, Y + Height div 2 ); shapeTag := CreateSVGShapeTag( BorderShape, X, Y, Width, Height, BorderWidth, BorderColor, FillColor, 0 { only text rotates }, Transparency); textTag := format( SVG_Text_Tag, [ textX, textY, textAnchor, alignBaseline, fontFamily + Fallback_Fonts, Font.Size, ColorToSVG( Font.Color ), fontStyle + fontWeight + GetSVGRotationElement( TextAngle, RotateCenter ) + GetSVGTransparencyElement( Transparency ), IEEncodeHTMLString( Text )]); Result := shapeTag + textTag; end; procedure WriteSVGFileOrStream(const FileName: String; Stream: TStream; IEView: TIEView; Bitmap: TIEBitmap; ImageFormat: TIOFileType; var xProgress: TProgressRec); var commentBlock: string; ss: TStringList; iev: TImageEnView; layersRect: TIERectangle; procedure _GetLayerSVG(IEView: TImageEnView); var I: Integer; layerTag: string; begin for I := 0 to IEView.LayersCount - 1 do begin if assigned(xProgress.fOnProgress) then xProgress.fOnProgress( xProgress.Sender, Round( i / IEView.LayersCount * 100 )); if IEView.Layers[ I ].Visible and not IEView.Layers[ I ].IsMask then begin layerTag := IEView.Layers[ I ].AsSVG; if layerTag <> '' then ss.Add( layerTag ); end; end; layersRect := IEView.LayersRect; end; begin ss := TStringList.create; try if assigned( IEView ) and ( IEView is TImageEnVect ) and ( TImageEnVect( IEView ).ObjectsCount > 0 ) then begin // We are saving objects of a TImageEnVect. Convert them to TImageEnView layers iev := TImageEnView.create( nil ); iev.LegacyBitmap := False; try // Clone the content of a TImageEnVect as layers iev.IEBitmap.Assign( TImageEnVect( IEView ).IEBitmap ); TImageEnVect( IEView ).CopyAllObjectsTo( iev ); _GetLayerSVG( iev ); finally iev.Free; end; end else if assigned( IEView ) and ( IEView is TImageEnView ) then begin // We are saving Layers of a TImageEnView _GetLayerSVG( TImageEnView( IEView )); end else begin // Saving only a bitmap ss.Text := CreateSVGImageTag( Bitmap, ImageFormat, 0, 0, Bitmap.Width, Bitmap.Height ); layersRect.X := 0; layersRect.Y := 0; layersRect.Width := Bitmap.Width; layersRect.Height := Bitmap.Height; end; if ss.Text = '' then xProgress.Aborting^ := true else begin commentBlock := format( 'Created with ImageEn %s - www.ImageEn.com', [ IEMAINVERSION ]); ss.Insert( 0, format( SVG_Opening_Tag, [ layersRect.Width, layersRect.Height, layersRect.X, layersRect.Y, layersRect.Width, layersRect.Height ])); ss.Insert( 1, format( XML_Comment_Block, [ commentBlock ])); ss.Add( SVG_Closing_Tag ); if Stream <> nil then ss.SaveToStream( Stream ) else ss.SaveToFile( filename ); end; finally ss.Free; end; end; end.