What is a good blog (and blogger) to me?

What makes a good blog?  What blogs, and bloggers, do I like and why?  This question gained extra relevancy for me recently as I have seen some examples of what I believe are not good blogs.  To me, a good blog is not:

  • a list of recanned company advertisements designed to push traffic to your web site
  • a series of articles that just republishes common information on the web
  • a retweet of someone else’s blog
  • a pseudo spambot for filling up your RSS feeds with multiple posts per day
  • relentlessly negative about Delphi

To me, a good blog provides articles that either educate or entertain.  They provide new perspectives on issues or new information.  They share the hard-earned wisdom of experienced Delphi coders.

When I started this blog, I gave myself a few goals for the blog:

  • Share information on Delphi issues that may be useful to others
  • Give myself a voice to argue, hopefully respectfully, for Delphi features and code styles
  • Occasionally, provide in-depth articles on compelling features about RiverSoftAVG products where a blog format is more appropriate than a help topic
  • Hopefully blog often enough to make the blog useful

Now, obviously, a blogger won’t always be successful at this, and beauty, as they say, is in the eyes of the beholder.  But those are the goals I set myself and are the blogs I pay attention to. I have failed occasionally (and ironically this post probably fails these goals 🙂 ) but I try to uphold those goals.

Examples of blogs I like are Delphi Code Monkey and David Millington’s Parnassus blog.  There are quite a few Embarcadero blogs I like.  Of course, David I’s Sip from the Firehose is a go-to blog.  Other examples: Sarina DuPont’s blog is always full of in-depth articles about features of Delphi as are Pawel Glowacki’s and Stephan Ball’s.  Closer to the entertainment side, Jim McKeeth’s Podcast at Delphi is good. (Note that not all blogs I like are listed, these are just notable ones to me)

My go-to aggregator’s are Begin End and FMX Express.  Begin End is my favorite Delphi site.  If someone has a Delphi’s blog, it will show up there.  And if a blog shows up there too often or is from someone I dislike, I can quiet that blog.  However, I hate to do that.  I believe a person will probably, eventually write something useful.  Sadly, if a blog is stopping me from seeing useful and good blogs from other people though, it has got to go.  But I would much rather a blogger respect their readers and not have to go that route.

Well, now that I have gotten that off my chest, back to Coding blogs. 🙂  Happy CodeSmithing!

Quick Tip: Fixing “Could not convert variant of type (Null) into type…”

I often use the XML Data Binding wizard in Delphi.  However, it doesn’t seem to have been given a lot of attention from Borland/Inprise/Borland/CodeGear/Embarcadero/Idera.  And unfortunately, out of the box what it generates is often error prone, apparently not supporting optional elements/attributes.

When the generated code tries to read an optional element or attribute, you will get a “Could not convert variant of type (Null) into type” exception.  The offending code usually looks like this:

function TXMLMyType.Get_OptionalElement: Single;
begin
 Result := ChildNodes['OptionalName'].NodeValue;
end;

If you do a little googling, you will see that people are still asking questions about this even pretty recently.  The suggested fix you will often discover is labor intensive if you have a lot of optional elements/attributes and will get wiped out if you rerun the XML Data Binding wizard:

 if VarIsNull(ChildNodes['selected'].NodeValue) then
    Result := 0; // or false or empty string, etc
  else
    Result := ChildNodes['selected'].NodeValue;

Hilariously in my mind, there is still an open ticket from 2002 about this issue: http://qc.embarcadero.com/wc/qcmain.aspx?d=2434

However, it seems the <insert-company-name-which-owns-Delphi> addressed this issue, probably years ago, and the fix/workaround is easy.  You need to include the Variants unit and set the NullStrictConvert global variable to false:

NullStrictConvert := False

As the documentation states:

<strong class="selflink">NullStrictConvert</strong> determines the outcome of attempts to convert <a title="System.Variants.Null" href="http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Variants.Null">Null</a> variants to other types. If <strong class="selflink">NullStrictConvert</strong> is true (default), attempting to convert a <a title="System.Variants.Null" href="http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Variants.Null">Null</a> variant raises a<a title="System.Variants.EVariantTypeCastError" href="http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Variants.EVariantTypeCastError">EVariantTypeCastError</a>, unless the conversion is to a custom variant that defines a conversion from <a title="System.Variants.Null" href="http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Variants.Null">Null</a>. If <strong class="selflink">NullStrictConvert</strong> is false, then conversion from <a title="System.Variants.Null" href="http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Variants.Null">Null</a> follows the following rules

Now, the XML Data Binding code will silently convert NULL to 0, false, or empty string without a problem.  I wanted to publicize this fix.  I have been bitten by this exception more times than I can count and if I had known of the workaround, it would have made my life much easier.

That’s it for today.  I hope everyone is enjoying their Summer (or Winter in the southern hemisphere).  Happy CodeSmithing!

Use Supersampling for offscreen bitmaps on Delphi Mobile

A common method for painting drawings is to draw to an offscreen bitmap and then draw the bitmap to your canvas (say a TPaintBox) as needed.  This is generally used when you create a drawing that does not change often; drawing once to an offscreen bitmap and then as needed on repaints to the real canvas can be very efficient.  However, if you have ever used offscreen bitmaps with Delphi on iOS or Android, you have quickly realized that the quality of the DrawBitmap method is awful.  There seems to be no anti-aliasing performed at all with DrawBitmap and the typical output looks terrible:

The quality of DrawBitmap from an offscreen bitmap to the screen is terrible on mobile platforms with no anti-aliasing

The quality of DrawBitmap from an offscreen bitmap to the screen is terrible on mobile platforms with no anti-aliasing

As you can see, the curve of the ellipse and the diagonal lines look jaggy, with no smooth transition or blur between the lines and the colors around them.

The image above comes from a sample project where I create an offscreen bitmap the same size as a TPaintBox.  Then, I draw the bitmap to the paintbox on its OnPaint event.

It is easy to not realize how bad the DrawBitmap is on mobile until late in your development as the output looks great on the desktop (Windows and OSX) platforms.  Unfortunately, there seem to be no way to improve the quality of the TCanvas.DrawBitmap function directly.  The best you can do is improve the quality of the entire form by changing its Quality property to HighQuality.  However, even this is often not enough:

High Quality forms do not fix the problem

High Quality forms do not fix the problem

Note that not using an offscreen bitmap drastically improves the quality over drawing to a bitmap and then drawing the bitmap to the final canvas.  If you just draw directly to the TPaintBox in its OnPaint event, the output looks pretty good:

Drawing directly to the final canvas has good quality on Mobile

Drawing directly to the final canvas has good quality on Mobile

However, presumably if you are reading this post, you need to use the Offscreen bitmap for various reasons such as its speed efficiency, so what else can we do?

An old technique, supersampling, can drastically improve the quality of the output and be more targeted than the blanket TForm.Quality property.  Supersampling is a brute force anti-aliasing technique where you draw your image on your offscreen bitmap at a much higher resolution (2x, 4x, 8x) than the one being displayed and then it is shrunk back down when it is drawn to your final canvas. The result is a downsampled image with smooth lines and no jaggies:

Drawing to an offscreen bitmap that is twice the width and height dramatically improves quality

Drawing to an offscreen bitmap that is twice the width and height dramatically improves quality which is arguably even better quality than drawing directly to the final canvas.

To perform supersampling, you create a bitmap that is twice as big in width and height (or 4x, 8x etc):

 OffscreenBitmap := TBitmap.Create;
 OffscreenBitmap.SetSize(trunc(PaintBox1.Width*ScaleFactor),
 trunc(PaintBox1.Height*ScaleFactor));
 OffscreenBitmap.Clear(TAlphaColorRec.Null);

Then you need to scale all your draw operations to the bigger bitmap.  You might think that this technique requires a more complex drawing routine, as you need to scale every draw and fill operation by the scale factor.  However, that is not the case.  By scaling the matrix of the TCanvas, you do not need to change your drawing routines at all:

procedure TForm1.Draw(aRect: TRectF; aCanvas: TCanvas; ScaleFactor: Integer);
var
 aMatrix: TMatrix;
begin
 aMatrix := aCanvas.Matrix;
 aCanvas.BeginScene;
 try
  <strong>aCanvas.SetMatrix(aCanvas.Matrix*TMatrix.CreateScaling(ScaleFactor, ScaleFactor));</strong>
  aCanvas.Fill.Kind := TBrushKind.Solid;
  aCanvas.Fill.Color := TAlphaColorRec.Blue;
  aCanvas.Stroke.Kind := TBrushKind.Solid;
  aCanvas.Stroke.Thickness := 3;
  aCanvas.Stroke.Color := TAlphaColorRec.Green;
  aCanvas.DrawRect(aRect, 10, 10, AllCorners, 1);
  aCanvas.FillEllipse(aRect, 1);
  aCanvas.DrawEllipse(aRect, 1);
  aCanvas.Stroke.Color := TAlphaColorRec.Red;
  aCanvas.Stroke.Thickness := 5;
  aCanvas.DrawLine(aRect.TopLeft, aRect.BottomRight, 0.7);
  aCanvas.DrawLine(PointF(aRect.Right, 0), PointF(0, aRect.Bottom), 0.7);
  aCanvas.Stroke.Color := TAlphaColorRec.Red;
  aCanvas.Stroke.Thickness := 1;
  aCanvas.DrawLine(PointF(aRect.Width / 2, 0), PointF(aRect.Right, aRect.Bottom / 2), 1);
  aCanvas.DrawLine(PointF(aRect.Right, aRect.Bottom / 2), PointF(aRect.Width / 2, aRect.Bottom), 1);
  aCanvas.DrawLine(PointF(aRect.Width / 2, aRect.Bottom), PointF(0, aRect.Bottom / 2), 1);
  aCanvas.DrawLine(PointF(0, aRect.Bottom / 2), PointF(aRect.Width / 2, 0), 1);
 finally
  aCanvas.EndScene;
  aCanvas.SetMatrix(aMatrix);
 end;
end;

The aRect parameter is the size of the final TPaintBox.ClipRect.  The aCanvas parameter is the offscreen bitmaps canvas.  Here is how the routine is called:

 Draw(RectF(0, 0, PaintBox1.Width, PaintBox1.Height),
 OffscreenBitmap.Canvas,
 (Sender as TRadioButton).Tag);
 PaintBox1.Repaint; // changed the offscreen bitmap, trigger the paint

Finally, in the TPaintBox.OnPaint event, draw the offscreen bitmap to the canvas:

procedure TForm1.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var
 aRect: TRectF;
begin
 aRect := RectF(0,0,OffscreenBitmap.Width,OffscreenBitmap.Height);
 Canvas.DrawBitmap(OffscreenBitmap, aRect, PaintBox1.ClipRect, 1, False);
end;

The scale factor can be as large as you want but in practice going above 4x or at the most 8x is unneccessary.  Here is the 4x output.

Drawing to a 4x bitmap looks almost perfect

Drawing to a 4x bitmap looks almost perfect

The output looks fantastic, but there are downsides.  The biggest is that this is a memory intensive technique.  Doubling the width and height of your bitmap quadruples the amount of memory you use.   Quadrupling the width and height uses 16x the amount of memory over your original offscreen bitmap!  However, since this can be a targeted technique, you only have to increase the size of the offscreen bitmaps for the important painting.

Another major issue with this technique can be speed.  Since you are drawing 4x or 16x more pixels, the offscreen drawing is naturally going to take longer.  And then, of course, drawing back to the main canvas adds time as well.  However, this is offset by the fact that you generally use offscreen bitmaps for things that don’t change as often and the draw bitmap function can still be much faster than drawing directly to the canvas depending on the complexity of your drawing.

The code for the example is here: Super Sampling Example Project.  Feel free to use as you wish.

Note that the RiverSoftAVG SVG Component Library and RiverSoftAVG IMPACT multimedia instrument package add-on use this technique.  There is a Quality property that provides the scale factor for most controls.  By default in FMX, when drawing the TRSSVGImage control to a buffer (Buffered=True), rendering the SVG to an image list, or drawing an instrument, the Quality property is 2 which provides a good balance between memory, speed, and quality.  Change the Quality property from 1 (no supersampling) to 8 (8x supersampling consuming 64x the memory).  The Quality property can also improve the look for extremely small final bitmaps (16×16, 32×32).  Since the memory usage is minimal at these small sizes, this property can really help with small bitmaps.

That is all for today.  Hopefully this tip is useful to some of you.  Happy CodeSmithing!

Video of IMPACT in Action

So I am trying a new thing with the release of RiverSoftAVG IMPACT multimedia gauges and gadgets.  RiverSoftAVG.com now has a youtube channel!  I have created a short 5 minute video about the product for your information and entertainment.

 

Please let me know what you think, good and bad, as it turned out to be a lot more work than I expected.  I want to know if people find these useful or not.  If people like them, I might start creating tutorials and other videos.

Thanks and Happy CodeSmithing!

RiverSoftAVG Summer Sale

In the US, memorial day weekend traditionally kicks off the start of Summer.  Amazingly, after days and days and days of rain, mother nature has finally decided to relent, and the sun has started to shine in the last couple days.  To celebrate, RiverSoftAVG is kicking off our Summer Sale! 🙂

Everything is on sale, from 10-30% off.  Unusually, this even includes site licenses and bundle prices (already a great deal).  This even include sale prices on top of the recently introduced lower prices on the RiverSoftAVG SVG Component Library and the RiverSoftAVG Charting Component Suite (Commercial License).  I didn’t discuss these prices in my last blog post, but these new, permanently reduced lower prices occurred as part of the RiverSoftAVG IMPACT Instrument Package Add-on launch.  The SVG library price was slashed around 17%, changing the price from $80 to $66.  The Charting suite price dropped even more dramatically by 50%, going from $200 to $100 for a commercial license.  During this sale, these great prices are now also 10% off.

It is a sunny day and truly a great time to buy RiverSoftAVG products.  But even if you don’t, I hope you get out, enjoy the weather (which is hopefully nice where you live) and have a wonderful weekend!

Happy CodeSmithing!

Announcing RiverSoftAVG IMPACT

RiverSoftAVG IMPACT Screenshot

Screenshot from RiverSoftAVG IMPACT, showing all instruments in the package.

I am really excited to be announcing a new add-on product available right now in our Early Experience Program, RiverSoftAVG IMPACT.  RiverSoftAVG IMPACT is an instrument package add-on for our RiverSoftAVG SVG Component Library (RSCL) and provides high quality, resolution independent, and easily customizable instrument components.  IMPACT comes with a large suite of gauges and gadgets, including clocks, compasses, batteries, speedometers, lights, an altimeter, barometer, and more.

This release is a culmination of a long process for me.  Years ago, I wanted to leverage the breadth and depth of SVG assets that are on the net in my own Delphi programs.  I wanted to create something extremely easy to use, dynamic, and with high-quality.  Unfortunately, it turned out the SVG specification is really, really hard 🙂  Many years later and more hours than I want to admit, I finally have my dream.  IMPACT embeds royalty-free SVGs as resource files inside Delphi programs and allows easy editing from the object inspector and through events for almost unlimited customization.  Most SVGs are from www.openclipart.org (heavily modified), which has an unlimited commercial license and the SVGs are released into the public domain.  Others SVGs were created specifically for this component suite.

Shows six variations of the basic Barometer instrument

Shows six variations of the basic Barometer instrument created in seconds

Each instrument comes with a large number of properties to configure the look and feel of the component. These includes properties to change the color of specific elements of an instrument to properties that change the entire look of the instrument quickly.  For example, most components have a Design property, which quickly switches between detailed and simpler versions of the instrument. The simple option removes extra detail (such as bevels, glass look, bolts or screws, etc) in the gauge to provide a more basic and faster to draw version of the gauge.  The EnableGradients property removes gradients from the gauge. This option improves speed when drawing the component, and gives the gauges a generally flatter look.  There is a Parts property which allows you to configure which parts of the instrument that IMPACT should draw.  Finally, all components have color properties for the major elements in the gauge.  Change the frame color, backface color, needles or hands, markers, text, etc.

For example, the red barometer on the right in the image above could be made with code like this:

RSBarometer1.FrameColor := TAlphaColorRec.Red;
RSBarometer1.Decoration := dcStormy;
RSBarometer1.DecorationFillColor := TAlphaColorRec.Purple;
RSBarometer1.Value := 951;
RSBarometer1.MajorMarker := gmCircle;
RSBarometer1.MinorMarker := gmCircle;
RSBarometer1.StartAngle := 290;
RSBarometer1.StopAngle := 540;
RSBarometer1.Font.Family := 'Arial';

For more information, you can go to IMPACT page.  There are demo programs on the page as well as more information and screenshots.  Note that IMPACT requires the RiverSoftAVG SVG Component Library.

Happy CodeSmithing!

RiverSoftAVG Products now support Delphi 10.1 Berlin

Just a quick post to announce new versions of all of our products, adding support for the new RAD Studio and Delphi 10.1 Berlin. There have also been several bug fixes for the RiverSoftAVG SVG Component Library and the Inference Engine Component Suite.  The following products have been updated:

Please see the FLCL version history,  GACL_version_history,  IECS_version_history,  RCCS_version_history,  RSCL version history and the RCCL version_history for more details.

Registered users, please go to the Support page to get the latest versions.  Evaluation versions and the full version of the RCCS (for non-IECS owners) is available from the Downloads page.

 

Bad Delphi Code

David Millington of Parnassus had a fun little Bad Delphi Code contest and just announced the results.  My submission got an honorable mention.  (Nothing like getting an honorable mention in bad code writing to give people confidence in my software.  “Hurry and buy today!” 🙂 )

Seriously, it was a lot of fun.  Apparently, results were judged on creativity, deviousness, and backstory, to submit “the worst believable code snippet or small app that you can [write], in the spirit of amusing, entertaining, or horrifying the reader.”

All of the submissions were entertaining, but I especially liked the Break entry.  Apparently, Break and Continue are not reserved words in Delphi, and are pseudo-implemented in the System unit.  It is easy to define your own Break and Continue functions and, because of scoping rules, cause your Break/Continue command to be called instead of the regular Delphi command in subsequent code, breaking everyone’s nice loop structures 🙂

To me, this one exemplifies truly “evil” (not just bad) code, because it betrays user’s expectations.  Bad code is easy to write and, admit it, all of us have done it.  Under the pressure of deadlines or just plain laziness (or sometimes incompetence), we have written long, muddled, and just plain wrong code that is a nightmare to debug and maintain.  However, if it does work, people may never even see it or have to think about it.  But when you betray user’s expectations, that is true evil code.  Component writers must guard most closely against this type of code.  Because not only do we break our code, we break our customers.

For example, one I encountered recently is in the FMX TControl3D code.  In FMX, there is a TControl.Position property.  To have 2 controls in the same position, you can easily assign to the position property:

Label1.Position := Button1.Position;

Everything we have learned with the VCL and Delphi style guides condition us to expect that the button1’s position object is copied to Label1’s position property and everything just works.

In the FMX 3D library, there is also a TControl3D.Position property.  However, it betrays our expectations.  Here is the declaration of the Position property:

 property Position: TPosition3D read FPosition write FPosition;

If we do code like:

Cube1.Position := Sphere1.Position

Instead of copying the TPosition3D object, it just sets the reference to the same TPosition3D (and throws away without freeing the old TPosition3D object).  The code appears to work at first.  However, not only have we leaked memory, we have tied the two objects’ positions together and changing one will move the other.  “Luckily,” this code blows up on desktop (though it wouldn’t with ARC code) when we try to free both objects as they both try to free the same TPosition3D object.

This is truly evil and incompetent code.  We have led our users astray and made their code bad through our own incompetence.

My Bad Delphi Code Submission

But enough about that.  I saw that David’s readers wanted access to the code for every submission.  Here is my submission:

This code is inspired by Embarcadero's FMX TPathData.SetPathString 
method, which parses a path for you.  In this code, they had a loop 
for getting the next command in the path (move, line, etc) where 
they would compare the current token against 18 commands, every 
single time even when there was a match found earlier:
 while TokenBuilder.Length &gt; 0 do
 begin
  Token := TokenBuilder.Chars[0];
  TokenBuilder.Remove(0, 1);
  if Token.IsInArray(['z', 'Z']) then
   ClosePath;
  if Token = 'M' then
  begin
   […]
  end;
  if Token = 'm' then
  begin
   […]
  end;
  if Token = 'L' then
  begin
   […]
  end;
  […]

Finally, in XE8, they fixed it and used a case statement.

I thought I would "improve" on their code by doing the same thing 
but with longer strings and adding in unnecessary code for not 
finding the string.  I made sure that the short circuit for the 
if statements didn't work as well (wrong order).
procedure TForm1.StringCompareUnOpt(aString: String);
var
 Found: Boolean;
begin
  Found := False;
  if aString = 'A' then
  begin
    Log('B');
    Found := True;
  end;
  if (aString = 'One') and (not Found) then
  begin
    Log('Two');
    Found := True;
  end;
  if (aString = 'Foo') and (not Found) then
  begin
    Log('Bar');
    Found := True;
  end;
  if (aString = 'Four Score') and (not Found) then
  begin
    Log('And Seven Years Ago');
    Found := True;
  end;
  if (aString = 'Hello') and (not Found) then
  begin
    Log('World');
    Found := True;
  end;
  if (aString = 'Question') and (not Found) then
  begin
    Log('Answer');
    Found := True;
  end;

  if not Found then
    Log('Unknown');
end;

Now, to make it interesting, I decided to "optimize" the code by 
creating a constant array of hashes for each string and then 
comparing the current string's hash code to the constant array values.
procedure TForm1.StringCompareOpt(aString: String);
begin
  // "Optimized" string compare
  // Pros:
  //    Shorter looking code (if you disregard setting up the constants)
  //    "Looks" like it could be faster as case is just a integer comparison instead of string comparison
  // Cons:
  //    Wrong (case insensitive vs original case sensitive compare),
  //    Brittle (what if Strings const changes?  Need to regenerate hashcodes.
  //             what is GetHashCode implementation changes?  Wrong answers)
  //    Slower (GetHashCode can be slow)
  case aString.GetHashCode of
    StringA:         Log('B');
    StringOne:       Log('Two');
    StringFoo:       Log('Bar');
    StringFour:      Log('And Seven Years Ago');
    StringHello:     Log('World');
    StringQuestion:  Log('Answer');
  else
    Log('Unknown');
  end;
end;

What I like about this code is that it *almost* looks brilliant.  The 
code is much shorter and in theory using a case statement is faster.  
However, as mentioned in the code it is wrong sometimes, brittle, 
and slower as well as being a lot more work to set up. FTW! 🙂

Tom

The full code, including test program, can be downloaded here.  Happy CodeSmithing!

World Wide Web of SVGs

Scalable Vector Graphics (SVG) are awesome.  Not only because they are scaleable, but perhaps even more importantly, because they are editable.  When you download a bitmap or png from the web, your options for editing it are limited.  With a lot of work and expertise in Photoshop, you can perhaps edit them, but usually not perfectly and changing them on the fly is just not possible.

Final SVG Gauge with customized elements

Final SVG Gauge with customized elements

SVGs are different.  Each piece of the graphic is editable.  You can change colors, rotations, visibility, etc.  With the RiverSoftAVG SVG Component Library (RSCL), you get access to a whole world wide web of customizable graphics for your applications.

In this blog post, I am going to show you how to download, prepare, and use a SVG to create incredible customizable graphics for your applications.  I will take you through the exact steps in my journey, the easy and the hard, to find and customize an SVG to provide a great new gauge, that can be used in our applications.  For those who don’t own the RSCL, there is an evaluation version available from the RiverSoftAVG Download Page that you can use to follow along.

Finding the SVG

Some say that the first step on a journey is the hardest; in this case, they are definitely right.  The hardest part about this whole process is finding the right SVG.  Finding good SVGs is easy.  Even finding good, free SVGs is easy.  But finding good, free SVGs that can be easily customized (whether on purpose or by accident) is not quite as easy.

The first thing I did was go to www.openclipart.org.  OpenClipArt.org is a great repository for clip art that have been released into the public domain and licensed for unlimited commercial use.

I had no idea what I was going to create for this blog post, but I knew I wanted a gauge, so I typed “gauge” into their search box and browsed the results.  I finally settled on the speedometer https://openclipart.org/detail/99937/speedometer created and uploaded by rg1024 back in 2010.

Speedometer from www.openclipart.org

Speedometer from www.openclipart.org

It had a clean look and it looked like it had a nice discrete object for the needle (at least I hoped so).  My idea was that by rotating the needle I would have a nice, dynamic speedometer gauge.

I downloaded the SVG, which was called velocimetro.svg.  First, I opened the SVG using the SVGPaintBoxVCL demo and it looked great, but I needed to do some pre-processing work to make the gauge useful as a dynamic object.  I started the SVGEditorFMX demo application provided with the RSCL and opened the velocimetro.svg file.

Finding and changing the needle in speedometer SVG using the SVG Editor Demo application included with the RSCL

Finding and changing the needle in speedometer SVG using the SVG Editor Demo application included with the RSCL

Clicking on the needle in the SVG selected the glass shine in front of the needle, but it got me in the right area.  By clicking on elements in the left tree view, I finally found 3 paths that made up the needle: the needle itself, its shadow, and the highlight for the needle.  I changed the names of the elements to Needle, NeedleShadow, and NeedleHighlight to make them easier to access in code and saved the SVG.  I would need to group the three paths to make it easier to rotate, but the SVGEditorFMX does not have a way to add a svg group yet so I loaded the SVG in NotePad++.  I searched for “Needle” and quickly created a svg group around the three paths.

&lt;g id="NeedleGroup"&gt;
  &lt;path id="Needle" style="fill:white" d="M391.31,343.02 L392.646,343.378 L363.659,465.898 L358.572,474.673 L355.17,473.761 L355.152,463.618 L391.31,343.018 Z "/&gt;
  &lt;path id="NeedleShadow" style="fill:black" d="M394.22,344.12 L359.114,475.14 L360.922,475.625 L366.004,466.866 L394.995,344.326 L394.219,344.118 Z "/&gt;
  &lt;path id="NeedleHighlight" style="fill:#babdb6" d="M392.22,342.62 L357.114,473.64 L358.922,474.125 L364.004,465.366 L392.995,342.826 L392.219,342.618 Z "/&gt;
&lt;/g&gt;

Saving my work, my speedometer was almost ready for its first use…

Rotating the Speedometer Needle at Run-Time

I quickly created a test project to display and hopefully edit my SVG.  I performed the following steps:

  • Created a New VCL Project
  • Dropped a TRSSVGDocument component on the form
  • Set the RSSVGDocument1.Filename property to the velocimetro.svg file
  • Dropped a TRSSVGImage component on the form
  • Set the RSSVGImage1.SVGDocument to RSSVGDocument1

I now had my SVG displaying in a project.  Now, I needed to edit the needle. For testing, I decided to use a TTrackBar to change the speed value.  From looking at the SVG output, the author seemed to want the speed to go from -5 (?!) to 245.  I needed to convert that value into a rotation angle for the needle group.  Dropping a TTrackBar onto the form, I set its min and max, and created a OnChange event handler to find the needle group by its name and then apply a rotation matrix to it.

However, there were two problems.  Firstly, rotation usually occurs around the origin which is most definitely not what we wanted here.  I needed the pivot point or base of the needle.  When I had examined the needle, I realized the bottom of the needle is in the bottom left.  Thankfully, that is really easy to get.  Every graphical element in the RSCL has a BoundsRect property.  I would use the BoundsRect.BottomLeft as the pivot point. Secondly, by default the needle was not placed pointing left or right or top or bottom.  It was at a slight angle, which I had to account for in the rotation angle. Also, I needed to constrain the angle so that the needle stopped at the bounds of the SVG.  By experimentation, I figured out that the needle needed to be rotated counter-clockwise at the most to -109.  Clockwise, the angle could not be greater than 78.

Finding the needle itself is very easy with the RSCL since I changed its ID.  I just called the TSVGDocument.AllItems property with the name of the needle.  Here is the final event handler:

procedure TfrmSpeedometer.TrackBar1Change(Sender: TObject);
const
 LowGauge = -109;
 HiGauge = 187-LowGauge;
var
 Needle: TSVGGraphicElement;
 NeedleAngle: Single;
 aRect: TSVGRect;
begin
 Label1.Caption := TTrackBar(Sender).Position.ToString;
 // Get Needle Group to manipulate
 Needle := RSSVGDocument1.SVG.AllItems['NeedleGroup'] as TSVGGraphicElement;
 aRect := Needle.BoundsRect;
 // scale trackbar value into between Min and Max
 NeedleAngle := TTrackBar(Sender).Position;
 NeedleAngle := ((NeedleAngle-TTrackBar(Sender).Min)/(TTrackBar(Sender).Max-TTrackBar(Sender).Min))*(HiGauge-LowGauge)+LowGauge;
 // rotate around bottom left
 Needle.Matrix := CreateRotationRSMatrix(RSPoint(aRect.Left, aRect.Bottom), DegToRad(NeedleAngle));
end;

Running the application, it worked but there was horrible flicker.  I set the form’s DoubleBuffered property to True and that fixed that issue.

However, there was one issue left.  Annoyingly, it turned out that the SVG was not created with the needle properly centered.  When the needle was a low number like 40, it would overlap the markers.  However, when the needle was a high number like 220, the needle wouldn’t reach the markers.  To fix this issue, I decided to create another group around our NeedleGroup, this group would translate (move in X and Y) the needle group over so everything would line up just so.

 &lt;g id="OuterNeedleGroup" transform="translate(5 -10)"&gt;
   &lt;g id="NeedleGroup"&gt;
    &lt;path id="Needle" style="fill:white" d="M391.31,343.02 L392.646,343.378 L363.659,465.898 L358.572,474.673 L355.17,473.761 L355.152,463.618 L391.31,343.018 Z "/&gt;
    &lt;path id="NeedleShadow" style="fill:black" d="M394.22,344.12 L359.114,475.14 L360.922,475.625 L366.004,466.866 L394.995,344.326 L394.219,344.118 Z "/&gt;
    &lt;path id="NeedleHighlight" style="fill:#babdb6" d="M392.22,342.62 L357.114,473.64 L358.922,474.125 L364.004,465.366 L392.995,342.826 L392.219,342.618 Z "/&gt;
   &lt;/g&gt;
 &lt;/g&gt;

I ran the project, and moved the Track bar left and right.  The needle smoothly updated from -5 to 245 and nicely centered.  Fantastic!  The hard part was done.

Customizing the Speedometer

Besides the problems with the needle, this SVG turned out to be a very good SVG for customization.  Everything was nicely ordered if not identified well.  By spelunking around with the SVG Editor, I identified and labeled the other parts of the speedometer such as the frame, the backface and the markers.  About the only limitation with this SVG is that the author didn’t use text elements for the speed labels (40, 80, 120, etc).  Instead, the author used paths so I cannot change those labels easily.  I also ended up changing the IDs for the gradients (e.g., linearGradientFrame) to more easily identify and find them later.

After I finished identifying all the parts of the speedometer, I wanted to test it out.  I dropped a TColorBox on my form and set up an event handler to change the color of the needle.  The only trick is that I needed to change the needle’s color and the needle’s highlight:

procedure TfrmSpeedometer.ColorBox2Change(Sender: TObject);
var
 Needle: TSVGGraphicElement;
 NewColor: TSVGColorRec;
begin
 NewColor := (Sender as TColorBox).Selected;
 RSSVGDocument1.SVG.BeginUpdate;
 try
 // get needle
 Needle := RSSVGDocument1.SVG.AllItems['Needle'] as TSVGGraphicElement;
 // change its color
 Needle.Brush.Color := NewColor;
 // dim colors
 NewColor.R := trunc(NewColor.R * 0.5);
 NewColor.G := trunc(NewColor.G * 0.5);
 NewColor.B := trunc(NewColor.B * 0.5);
 // get highlight or shaded portion
 Needle := RSSVGDocument1.SVG.AllItems['NeedleHighlight'] as TSVGGraphicElement;
 // change its color
 Needle.Brush.Color := NewColor;
 finally
 RSSVGDocument1.SVG.EndUpdate;
 end;
end;

One important note about the above code.  By default, SVG elements inherit their color from their parent element.  Usually, when you write Needle.Brush, what you are getting is a customized copy of the parent’s Brush.  Setting the brush’s color will not actually change anything as the value is still inherited.  In that case, you need to turn off the inheritance of the fill value before setting the fill value, like so: Needle.Inherits.Fill := False;  However, in this case, the needle’s color is not inherited and we can just set the Needle.Brush without worry.

Running the project again, I could now change the needle’s color.

Changing the backface or the frame turned out to be slightly more challenging.  For the backface, the fill color is a gradient (e.g., <path id=”backface” style=”fill:url(#linearGradientBackface)”…).  You have to change the color of the gradient (more specifically, the gradient stops) in order to change the color of the backface.  After doing that, you need to force the backface to update its color.  Calling the Reset method will mark every resource (i.e., brush, pen, and font as dirty and make the SVG element regenerate the resources when needed.

Dropping another TColorBox on the form, here is the backface OnChange event handler:

var
 Backface: TSVGLinearGradient;
 NewColor: TSVGColorRec;
 i: Integer;
begin
 NewColor := (Sender as TColorBox).Selected;
 RSSVGDocument1.SVG.BeginUpdate;
 try
  // get Backface gradient highlight
  Backface := RSSVGDocument1.SVG.AllItems['linearGradientBackfaceHighlight'] as TSVGLinearGradient;
  for i := 0 to Backface.Items.Count - 1 do
   (Backface.Items[i] as TSVGGradientStop).StopBrush.Color := NewColor;
  // get Backface gradient
  Backface := RSSVGDocument1.SVG.AllItems['linearGradientBackface'] as TSVGLinearGradient;
  (Backface.Items[0] as TSVGGradientStop).StopBrush.Color := NewColor;
  // dim colors
  NewColor.R := trunc(NewColor.R * 0.5);
  NewColor.G := trunc(NewColor.G * 0.5);
  NewColor.B := trunc(NewColor.B * 0.5);
  (Backface.Items[1] as TSVGGradientStop).StopBrush.Color := NewColor;
  // rebuild all brushes so that they ask for new gradients
  RSSVGDocument1.SVG.Reset;
 finally
  RSSVGDocument1.SVG.EndUpdate;
 end;

I did the same thing for the frame, the markers and text, and the glassy shine.

Finally, to show how easy it is to change other properties, I added checkboxes to turn off the visibility of the different elements.  The check box handlers look like this:

procedure TfrmSpeedometer.CheckBox5Click(Sender: TObject);
begin
 RSSVGDocument1.SVG.BeginUpdate;
 try
  (RSSVGDocument1.SVG.AllItems['backface'] as TSVGGraphicElement).Visible := (Sender as TCheckBox).Checked;
  (RSSVGDocument1.SVG.AllItems['backfaceHighlight'] as TSVGGraphicElement).Visible := (Sender as TCheckBox).Checked;
 finally
  RSSVGDocument1.SVG.EndUpdate;
 end;
end;
Customized Speedometer

Customized Speedometer

The source for the project is here.  The project includes a compiled version to play around with.  If you clone the TSVGDocument, you can easily create multiple speedometers, each with their own color scheme changeable at run-time.  And with a little more work to replace the path elements with true text elements (or a little more luck in finding a more customizable SVG), you could change the scale of the speedometer so it could be any analog gauge.

Well, that is it for today.  I hope this blog post helps you more easily see the awesome possibilities of SVGs.  There is a whole world wide web of free, customizable, scalable, and high-quality content out there.  With the RiverSoftAVG SVG Component Library and a little prep work, you can easily use this content to really make your applications stand out.

Until next time, Happy CodeSmithing!

RSCL v2 Release Deep Dive

The RiverSoftAVG SVG Component Library version 2.0 was finally released yesterday (Yea! 🙂 )  Now that v2.0 is “in the can,” I wanted to investigate and explain exactly what has changed.  The RSCL v2.0 has some great new features, a lot of improvements and refactorizations, and over 60 bug fixes.  However, the History.txt for the RSCL v2.0 is over 1100 lines long for v2.0 beta releases alone, and includes “fixes” of new features, refactorizations, and regressions (all of which should not be considered bug fixes as far as final counts between v1.x and v2.0).  It can be daunting to understand what has really changed, and what really matters, between v1.x and v2.0.

In this blog post, I attempt to separate out the changes and group them logically into

  • New
  • Improved (including refactorizations)
  • Bug Fixes

The New

This is what most people are interested in, the headliner features.

  • Save SVGs to XML – RSCL v2 can now load an SVG, change its properties in code, and then save out a compliant SVG file.
  • Load and Save SVGs to Delphi Binary – a new file format based on Delphi streaming was added.
    textPath example

    textPath example

    This format provides faster loading and saving of SVGs at the expense of being able to use them with other programs (and usually larger file sizes).

  • New Text Support
    • Text on Path

      Baseline Shift Example

      Baseline Shift Example

    • Baseline Shift (i.e., superscript, subscript, shift)
    • Font Variant Support (i.e., Small Caps)
    • Letter Spacing
    • Word Spacing
    • Kerning
    • Direction (i.e., left to right, or right to left.  no bi-directional support though except with simple text elements)
    • Writing Mode (i.e., left to right top to bottom, right to left top to bottom, top to bottom left to right)
    • Glyph Orientation Horizontal (i.e., rotation of characters when going left to right or right to left)

      Writing Mode, Glyph Orientation Example

      Writing Mode, Glyph Orientation Example

    • Glyph Orientation Vertical (i.e., rotation of characters when going top to bottom)
    • X, Y, DX, DY, and Rotation of individual characters
  • New TRSSVGImageList Components (VCL and FMX XE8+) – manage a list of SVGs in your application using TRSSVGImageList.  It embeds every SVG within the image list (simplifying deployment) and re-renders the SVGs as needed.  Like a traditional TImageList, multiple controls can use the same TImageList, either different items or the same item, and it is all managed automatically for you.
  • Detect and respond to mouse events on SVG elements – call TSVGGraphicElement.ElementAtPos to find the element at a screen point or respond to TRSSVGImage.OnClickElement events

    Detect Mouse Events

    Detect Mouse Events

  • New VCL TRSSVGScreen control provides flicker-reduced TRSSVGImage-like control (note that it is not transparent though so you cannot stack TRSSVGScreen controls on top of each other without obscuring SVGs underneath
  • New TSVGDocument DrawBeforeElement, DrawElement, and DrawAfterElement methods – draw specific portions of the SVG in place so that you can buffer to bitmaps unchanging parts (such as background or foreground) to speed up redraws
  • Created RSCL Evaluation version

The Improved

There were a lot of improvements and refactorizations in v2.0.  The following list highlights the most important:

  • Speed Optimizations
    • Refactored TSVGStyle class to store Style=Value pairs in a
      hash table (reduced time to load SVGs)
    • Improved TRSSVGImage (VCL) painting efficiency by deferring bitmap update until Paint call
    • Optimized TSVGElement.Assign method by wrapping assignment in
      BeginUpdate/EndUpdate

      Improved Linear and Radial Gradients

      Improved Linear and Radial Gradients

    • Slightly optimized TRSPathData.GetNum method (used in parsing paths)
  • Linear Gradients
    • Rotate & Scale – Linear Gradients transform based on referencing element
  • Radial Gradients
    • Added Focal point as well as Center Point
  • Text Support
    • Pen Stroke

      Gradients on Text Fill and Stroke

      Gradients on Text Fill and Stroke

    • Path-Based Text Drawing
    • Text Support of GPU Canvas in FMX – the GPU Canvas has real problems with converting text to path and layouts.  The RSCL cleanly handles any difficiencies in GPU Canvas.
  • Font Support
    •  Implemented choosing generic fonts (serif, sans-serif, and monospace)
  • Path Support
    • Added TRSPathData.FormatStr property to allow formatting the floating point values, generally to make the GetPathString return a smaller string to reduce the size of saved SVG files
  • Refactoring
    • TSVGLength (and descendants) – manages a length in SVG, which includes a raw value and the unit it is enumerated in (e.g., millimeters).  To refactor the SVG from a static SVG viewer to a static SVG editor required some major changes to the TSVGxxxElement classes and their properties, generally to be able to save properties that were read (before the elements would convert the svg values and then discard any extraneous information. For example, to save a length value such as ‘1in’ requires saving the raw value (1) and the unit (in) instead of the converted value (96 pixels).
    • TSVGViewBox – Refactored ViewBox property from TSVGRect (record) to TSVGViewBox *object*
    • The behavior of TSVGDocument.OnChange event has changed. The OnChange event usually does not get called when the structure of the SVG has changed anymore; please use the  SVGDocument.OnAddElement and  SVGDocument.OnRemoveElement property instead. The Sender
      parameter is now set to the element where the change occurs.
      If the Sender is nil, this means that the change event occurred
      because of BeginUpdate/EndUpdate method calls; in this case it
      can mean that the structure of the SVG document has changed.
    • Moved basic SVG shape elements (rect, ellipse, etc) to
      RSSVG.BasicShapes.pas and FMX.RS.SVG.BasicShapes.pas
    • Moved SVG text elements to RSSVG.Text.pas and FMX.RS.SVGText.pas
  • TSVGDocument Improvements
    • Gradients property to track list of Gradients in document
    • Markers property to track list of Markers in document
    • Patterns property to track list of Patterns in document
  • TSVGElement (base class for every element in SVG) Improvements
    • BoundsRect property is writable as well
      as readable
    • Added Enabled property (Disabled elements are drawn
      grayed out and ignored in ElementAtPos method calls by default)
    • ToString method returns XML string of element and its children
    • Added OnAddElement event
    • Added OnRemoveElement event
    • Modified the behavior of OnChange event to set the Sender to the element that changed. If Sender = nil then it means the structure of the SVG has changed
    • Modified OnDrawing event signature to include a DoDraw
      parameter. If this variable is set to false, the current Element
      will NOT be drawn. However, its children may still be.
    • Added mechanism to TSVGElement to track and notify listeners if the
      current element is changed. For example, this allows TSVGUse to be notified when the elements that it uses changes and then to ask to be redrawn
  • TSVGGraphicElement (base class for every visible SVG element) Improvements
    • Modified behavior of Style Properties (e.g.,
      ClipRule, Overflow, ShapeRendering, etc) to automatically
      remove themselves from Inherits property when they are changed
  • TSVGBrush Improvements
    • Added ParentColor property
    • Added ParentOpacity property
    • Added OnIsInherited event
  • TSVGFont Improvements
    • Added IsDecorated method to return if font is decorated (underlined, strike-out or overline)
    • Added ParentFamily property
    • Added ParentFontVariant property
    • Added ParentSize property
    • Added ParentStyle property
    • Added ParentDecorated property
    • Added ParentWeight property
    • Added OnIsInherited event
  • TSVGPen Improvements
    • Added ParentColor property
    • Added ParentOpacity property
    • Added ParentDasharray property
    • Added ParentDashOffset property
    • Added ParentLinecap property
    • Added ParentLineJoin property
    • Added ParentMiterLimit property
    • Added ParentThickness property
    • Added OnIsInherited event
  • New Demo Projects
    • Printer (VCL)
    • Editor (FMX)
    • Anatomy (VCL)
    • Clock World (FMX)
    • Stream (FMX)
    • ImageList (VCL)
    • ImageList (FMX)

The Fixed

The number of bugs fixed is a great case of that there are lies, damn lies, and statistics.  🙂  There are at least 66 bug fixes in RSCL v2.0 versus v1.9.  The number is open to interpretation as a lot of ParseXXX functions were fixed with a simple copy and paste of correct function parameters.  Should these be included?  Also, fixing bugs in FMX and VCL can technically be separate bug fixes as both have to be tested.  This became a judgement call and is reflected below.

Without further ado, here is the lists of bugs fixed in RSCL v2.0:

  • Fixed edge case memory leak in TSVGDocument.Clear by freeing styles
    (Styles.Clear does not call OnValueNotify event so styles
    were not being freed)
  • Fixed edge case memory leak in TSVGDocument.Destroy by freeing styles (Styles.Clear does not call OnValueNotify event so styles
    were not being freed)
  • Fixed graphical corruption in pattern by clearing canvas before
    generating pattern in TSVGPattern.GetPattern method
  • Fixed TSVGGraphicElement.GetBoundsRect(TSVGMatrix) method to
    correctly transform the bounds rectangle (including rotation) and return the boundsrect for the transformed boundsrect
  • Fixed font-family style property support (on Windows and Mac)
    to include list of font families and supporting generic (serif,
    sans-serif, and monospace) fonts
    Note: The first font found on the system is used. The RSCL
    does not support detecting a character is not present in the
    current font and selecting the next font in the list for that
    character
  • Fixed TSVGLinearGradient.GetGradient method to account for
    transformations of the gradient
  • Fixed TSVGGraphicElement.GetClipRect method to slightly inflate
    clipping rectangle to be bigger than BoundsRect
  • Fixed value of SVGAlignNames[saNone] to equal ‘none’
  • Fixed bug in TSVGElement.Assign method where Items were SHARED
    instead of copied/cloned
  • Fixed TRSSVGDocument/TRSFmxSVGDocument.LoadSVGFromStrings method to correctly read unicode TStrings
  • Fixed floating point error in TSVGMarker.DrawMidMarker method
  • Fixed floating point error in TSVGMarker.MidMarkerAtPos method
  • Fixed SVGs to be properly clipped when Overflow attribute is hidden
  • Fixed TSVGCustomViewBox.Draw method to correctly modify the matrix
    for the ViewBox (if specified) or BoundsRect INSTEAD of modifying
    the drawing rectangle
  • Fixed markers to be properly clipped when Overflow attribute is hidden
  • Fixed correctly setting FillMode for TRSSVGPath control (VCL)
    in TSVGPolyline.AssignTo method
  • Fixed CleanText function to strip off control characters before
    seeing if any spaces should be appended to left or right
  • Fixed bug in TRSGPBrush.Assign method where TBrush.Color was
    incorrectly converted to alpha color (VCL Only)
  • Fixed bug in TRSGPPen.Assign method where TBrush.Color was
    incorrectly converted to alpha color (VCL Only)
  • Fixed bug in ToMatrix function where result matrix was not
    zeroed out before retrieving the matrix elements (VCL Only)
  • Fixed TSVGCustomViewBox.GetBoundsRect method to return children’s
    combined boundsrect if the Width and Height are not set for the
    viewbox
  • Fixed bug in TSVGGraphicElement.AssignProperties method where
    ClipRect was not being copied correctly
  • Fixed bug in TSVGElement.Assign method where incorrect Owner
    was being assigned if Self was TSVGDocument
  • Fixed TSVGElement.HasAncestor method
  • Fixed bug in TSVGDocument.ReadStyles method where descendant
    selectors were parsed as if they were grouped together (e.g.,
    ‘#alpha * {fill:green}’ would be separated into 2 styles, ‘#alpha’ and ‘*’
  • Fixed TSVGImage.DoInternalDraw method to preserve aspect ratio
    when drawing image
  • Fixed bug in TSVGCustomText where BoundsRect would be reported
    incorrectly when a font property would change (overrode
    TSVGCustomText.PropagateObjectPropChange method to force
    recalculation of TextWidth and TextHeight)
  • Fixed ParseRect function to return empty rect if Value does not
    contain 4 numbers
  • Fixed access violation when accessing TRSGPImage Codecs (VCL Only)
  • Fixed bug in installer which would not set the library path correctly
    for iOS64 in XE8+
  • Fixed TSVGGraphicElement.SetCursor method to set inherits for
    cursor to false when changed
  • Fixed TSVGElement.Destroy method so that you can just call
    Element.Free and it will correctly remove itself from its parent
  • Fixed subtle bug where setting TSVGBrush.Kind property would be overwritten if the URI contained a gradient or pattern URI (which would reset the Kind to bkGradient/bkBitmap
  • Fixed subtle bug where setting TSVGPen.Kind property would be overwritten if the URI contained a gradient or pattern URI (which would reset the Kind to bkGradient/bkBitmap
  • Fixed bug in ParseOverflow function where ‘auto’ was not correctly
    interpreted as visible
  • Fixed finding Design-time ‘Help…’ topics by searching for Unit_ClassName, not Unit.ClassName
  • Fixed TSVGRadialGradient.GetGradient method to correctly calculate
    focal point when it is a percentage
  • Fixed TSVGRadialGradient.GetGradient method to correctly calculate
    rotation center when units are csuObjectBoundingBox
  • Fixed FMX to use Shape Caching (accidentally turned off)
  • Fixed bug in TSVGUse.ShouldDraw method where it would display
    a referenced element when the TSVGUse.Visible=False and the
    Reference.Visible=inherit
  • Fixed bug in TSVGImage.Draw method where image would be drawn even if not visible
  • Fixed incorrect calculation for picas in TSVGDocument.CalcUnitFactors
    method
  • Fixed bug in TSVGGraphicElement.AssignTo method where FloatToStr
    was not using USFormatSettings (as required by SVG spec)
  • Fixed TSVGGraphicElement.FillRule default property value to frNonZero
  • Fixed bug in TSVGDocument.SupportsLanguage method to return FALSE
    if input string is empty
  • Fixed TSVGElement.HasExtensions to return FALSE when an empty string
    is supplied
  • Fixed TSVGElement.HasFeatures to return FALSE when an empty string
    is supplied
  • Fixed TSVGElement.HasLanguages to return FALSE when an empty string
    is supplied
  • Fixed bug in TSVGGraphicElement.BeginDraw method where Canvas.Font.Brush was not set in VCL
  • Improved csuObjectBoundingBox handling in TSVGPattern.GetPattern method (still incorrect but better)
  • Fixed bug in TRSFmxSVGDocument destructor where viewers are not
    notified the document is going away
  • Fixed bug in TSVGText.DoLoadFromXML method where multiple
    spaces (and other control codes) between words were not removed
    for the Text property
  • Fixed TSVGText.GetBounds to return an accurate Bounding box
    after the first time the text is drawn (DoInternalDraw caches
    TextWidth and TextHeight for later use)
  • Improved appearance of overline extended style for TSVGText
    (now a rectangle instead of thin line)
  • Fixed bug in TSVGPolyline.GetShape method where 2 ClosePaths
    could be added in a row, causing FMX to black out the entire canvas
    (However, this bug was hidden until fixed bug where FMX was not
    using Shape Caching)
  • Fixed ParseFontFamily function to return a list of font names
  • Fixed GetSVGUnit function to return suPercent if input string
    ends with ‘%’ (before input string had to be exactly equal)
  • Fixed bug in ParsePoints where if number of values was not even,
    NO points would be returned. Now, all the points except the
    missing last one are returned (follows SVG specification)
  • Fixed ParseNumbers function to also use line feeds and carriage returns
    as delimiters
  • Fixed ParseXXX function to also use line feeds and carriage returns
    as delimiters
  • Fixed bug in TRSGPPath.AddText(Pt: TPointF;…) method where
    aRect was not initialized for measuring the text properly
  • Fixed invalid parameter for GDI+ function bug in TRSGPPath.AddPolygon
    method by ensuring at least 3 points are specified
  • Fixed TRSGPFont.GetHeight method to return a value even when
    Owner is nil
  • Fixed access violation in TSVGCustomGradient.ElementNotification method where the method tried to use the Item after it had been freed
    by inherited method
  • Fixed bug in TRSPathData.GetPathString method to use USFormatSettings for FloatToStr calls
  • Fixed bug in TRSPathData.AddArcSvg method where arcs that begin
    and end at same point would not get added (now adds ellipse)