Skip to content

Loading drawings

Drawings reach an application as files next to it, as resources in the executable, as blobs in a database or as text from a server. This page shows how each gets into the library, and what can go wrong on the way.

Into a component

A component takes its drawing from one of three properties:

SVG2Image1.FileName := 'C:\Plant\Drawings\area-3.svg';  // a file
SVG2Image1.SVG.Text := DrawingText;                     // SVG text
SVG2Image1.SVGDoc := SVG2Doc1;                          // a shared TSVG2Doc

An assignment does not parse the drawing straight away. The component parses it when it is next painted, or when you call ParseSVG, and then raises OnAfterParse. Until then, SVGRoot still holds the previous drawing, so look up elements in OnAfterParse, or call ParseSVG first. SVG wins over FileName when both are set. Choosing a component explains which one to use when.

To load from a stream, such as a resource or a database blob, read it into SVG:

var
  Stream: TResourceStream;
begin
  Stream := TResourceStream.Create(HInstance, 'AREA3', RT_RCDATA);
  try
    SVG2Image1.SVG.LoadFromStream(Stream, TEncoding.UTF8);
  finally
    Stream.Free;
  end;
end;

Pass the encoding. See Encodings below for why.

Without a component

TSVGSaxParser reads a file, a stream or a TStrings into a document of your own, as in Your first drawing:

Root := TSVGRootVcl.Create;
Parser := TSVGSaxParser.Create(nil);
try
  Parser.Parse(Stream, Root);      // or a file name, or a TStrings
finally
  Parser.Free;
end;

Loading from a stream this way needs no encoding: the parser reads UTF-8 without a byte order mark correctly, as tool exports need.

Compressed files

Files compressed with gzip, usually named .svgz, load like any other through FileName or the parser. The library recognises the compression from the content, not from the file name, so a compressed drawing in a stream or a database blob loads through Parser.Parse(Stream, Root) as well. Do not read one into the SVG property: that holds text, and compressed data is not text.

Encodings

Most drawing tools save UTF-8 without a byte order mark. The parser and the FileName property handle that correctly. The trap is loading the text yourself first:

// Wrong: without a byte order mark this reads the file in the Windows code
// page, so "Druckbehälter" becomes "Druckbehälter"
SVG2Image1.SVG.LoadFromFile(FileName);

// Right
SVG2Image1.SVG.LoadFromFile(FileName, TEncoding.UTF8);

The same applies to LoadFromStream and to text read from a database or a web service: decode it as UTF-8, or use the encoding the XML declaration names.

When a drawing does not load

A file that is missing, or that is not well-formed XML, raises an exception while it is parsed (EDOMParseError on Delphi). The message says what is wrong and where:

end tag svg does not match start tag rect

What the application sees depends on where the drawing is parsed:

  • When a component parses it while painting, the exception is caught and the message is shown in the component instead of the drawing. The component raises OnParseError with the message, and ParseErrorMessage holds it until a drawing is parsed. (This is the default. Undefine SvgCtrlExceptionsOff in CompilerSettings.inc to have the exception raised from the paint instead.)
  • When you call ParseSVG, or TSVGSaxParser.Parse, the exception is raised to your code.

OnParseError is the simple way to log bad drawings or tell the user:

procedure TForm1.SVG2Image1ParseError(Sender: TObject; const aMessage: string);
begin
  StatusBar1.SimpleText := 'The drawing could not be loaded: ' + aMessage;
end;

To decide yourself what the user sees, parse inside try ... except instead, before the component paints:

try
  SVG2Image1.FileName := aFileName;
  SVG2Image1.ParseSVG;
except
  on E: Exception do
  begin
    SVG2Image1.FileName := '';
    ShowMessage(Format('%s could not be loaded:'#13'%s', [aFileName, E.Message]));
  end;
end;

Everything short of broken XML loads. An attribute value the library cannot read, such as a colour in a syntax it does not know, is skipped for that one element, and the element is drawn as if the attribute were not there. An element it does not know is kept in the document but not drawn.

Log messages

Some problems do not stop loading but are worth knowing about. The library reports them through the component's OnLog event, or ISVGRoot.OnLogMsg without a component. The most common one in tool exports is a repeated id:

Duplicate element id "shape", lookup by id is ambiguous.
Use FindElements to get every element with it.

Log these while drawings are being made, so the designer hears about a repeated id before the application depends on it:

procedure TForm1.SVG2Image1Log(Sender: TObject; aRoot: ISVGRoot;
  aDocument: ISVGXMLDocument; const aMsg: string);
begin
  MemoLog.Lines.Add(aMsg);
end;

Linked images and style sheets

A drawing can refer to other files: bitmaps and other SVG files in <image>, and style sheets through @import or an XHTML <link>. How the library finds them depends on how the drawing was loaded:

Loaded from A relative link such as img/pump.png is looked for
FileName, or the parser with a file name Next to the drawing: in img below the drawing's folder
SVG, a stream or a TStrings Relative to the current directory of the process

The current directory is rarely what you want, so for drawings that do not come from a file, either ask the designer to embed the bitmaps (most tools have an Embed option; the image is then part of the file) or supply the files yourself in OnLoadExternalResource. The library calls it for every linked file before it looks on disk:

procedure TForm1.SVG2Image1LoadExternalResource(Sender: TObject;
  aReferer: ISVGReferer; aIri: TSVGIri; aStream: TStream;
  var aMimeType: TSVGMimeType; var aLoaded: Boolean);
var
  FileName: string;
  FileStream: TFileStream;
begin
  // aIri.Path is the link as written in the drawing, for example 'img/pump.png'
  FileName := TPath.Combine(FDrawingFolder, string(aIri.Path).Replace('/', '\'));
  if FileExists(FileName) then
  begin
    FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
    try
      aStream.CopyFrom(FileStream, 0);
    finally
      FileStream.Free;
    end;
    aMimeType := FileMimeType(FileName);  // BVE.SVG2Types
    aLoaded := True;
  end;
end;

Leave aLoaded at False to let the library look for the file itself. The same event can serve images from a database or a resource.

Links to http and https addresses are fetched: SVGInternetAccess is on by default in CompilerSettings.inc. On FPC, https needs the OpenSSL libraries at run time; see Shipping your product.

Showing part of a drawing

One file can hold several views: a whole plant, and each area as a nested svg element. RootID renders only the svg element with that id:

SVG2Image1.RootID := 'area-3';

The whole document is still loaded, so ids elsewhere in it are still found.