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 > 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.

<g id="NeedleGroup">
  <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 "/>
  <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 "/>
  <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 "/>
</g>

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.

 <g id="OuterNeedleGroup" transform="translate(5 -10)">
   <g id="NeedleGroup">
    <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 "/>
    <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 "/>
    <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 "/>
   </g>
 </g>

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)

TSequential.For A Happy Leap Day

Leap Day has me in a whimsical mood. I wanted to write a light-hearted blog post for this special day but couldn’t think of any ideas. First, I thought about how cool it would have been if Delphi had been born on Feb 29, 1996 instead of Feb 14, 1995 (it would still be so young! 🙂 ), but ultimately decided that was not a great blog post. In the nick of time, along comes Marco with this blog post about creating a For loop with a step value greater than 1. His version works perfectly, but my first thought is that if they have a TParallel.For, they should have a TSequential.For… and so a blog post is born.

I quickly whipped up a class after breakfast to provide a sequential For iterator with step size, based on the TParallel model. I added For functions for Integer and Int64, but it can easily be extended to Singles or Doubles, or to add ForDownTo functions. The user would use the class like so:

procedure TForm7.Button1Click(Sender: TObject);
begin
 TSequential.For(0, ListBox1.Items.Count-1, 2, (procedure (const Index: Integer)
 begin
  ListBox1.Items[Index] := 'Leap!'+ListBox1.Items[Index];
 end));
end;

I wanted the user to be able to have Break and Continue like a regular For loop. Continue is obviously easy (just Exit). Break was a little harder. I decided to implement a TSequential.Break method that would silently break the user out of their loop. It raises a ESequentialAbort under the hood which is caught by the TSequential calling routine. The user would break like so:

procedure TForm7.Button1Click(Sender: TObject);
begin
 TSequential.For(0, ListBox1.Items.Count-1, 2, (procedure (const Index: Integer)
 begin
  if ContainsText( ListBox1.Items[Index], 'Day' ) then
   TSequential.Break;
  ListBox1.Items[Index] := 'Leap!'+ListBox1.Items[Index];
 end));
end;

Obviously, this class is not a marvel of efficiency as it uses a function call for each iteration as well as setting up a try…except block once. But it works, is clean, was fun to make, and as a bonus returns the final For loop index. You are welcome to use it as you like. If nothing else, I hope it lightened your day somewhat.

Happy Leap Day and Happy CodeSmithing!

The entire code is below or the file may be downloaded:

unit RSSequential;
//=== File Prolog ============================================================
// This code was developed by RiverSoftAVG (www.RiverSoftAVG.com)
//
//--- Notes ------------------------------------------------------------------
//
//--- Development History ---------------------------------------------------
//
// 02/29/2016 T. Grubb
// Initial version.
//
// File Contents:
//
//--- Warning ----------------------------------------------------------------
// This software is property of RiverSoftAVG. Unauthorized use or
// duplication of this software is strictly prohibited. Authorized users
// are subject to the following restrictions:
// * RiverSoftAVG is not responsible for
// any consequence of the use of this software.
// * The origin of this software must not be misrepresented either by
// explicit claim or by omission.
// * Altered versions of this software must be plainly marked as such.
// * This notice may not be removed or altered.
//
// © 2016, Thomas G. Grubb
//
//=== End File Prolog ========================================================

interface

uses
 System.Types, System.SysUtils, System.Classes, System.Generics.Collections,
 System.Generics.Defaults, System.SysConst;

type
 TFunctionEvent&lt;T&gt; = function (Sender: TObject): T of object;
 TSequential = class sealed
 public
 type
 ESequentialAbort = class(EAbort);
 TProcInt = reference to procedure (const Index: Integer);
 TProcInt64 = reference to procedure (const Index: Int64);
 TIteratorEvent = procedure (Sender: TObject; AIndex: Integer) of object;
 TIteratorEvent64 = procedure (Sender: TObject; AIndex: Int64) of object;
 protected
 class function ForWorker(Sender: TObject; AEvent: TIteratorEvent;
 const AProc: TProcInt; ALowInclusive, AHighExclusive: Integer;
 AStride: Integer = 1): Integer; static;
 class function ForWorker64(Sender: TObject; AEvent: TIteratorEvent64;
 const AProc: TProcInt64; ALowInclusive, AHighExclusive: Int64;
 AStride: Int64 = 1): Int64; static;
 public
 class procedure Break;
 class function &amp;For(ALowInclusive, AHighInclusive: Integer; const AIteratorEvent: TProcInt): Integer; overload; static; inline;
 class function &amp;For(ALowInclusive, AHighInclusive, AStride: Integer; const AIteratorEvent: TProcInt): Integer; overload; static; inline;
 class function &amp;For(ALowInclusive, AHighInclusive: Int64; const AIteratorEvent: TProcInt64): Int64; overload; static; inline;
 class function &amp;For(ALowInclusive, AHighInclusive, AStride: Int64; const AIteratorEvent: TProcInt64): Int64; overload; static; inline;
 end;

implementation

{ TSequential }

class function TSequential.&amp;For(ALowInclusive, AHighInclusive, AStride: Integer;
 const AIteratorEvent: TProcInt): Integer;
begin
 result := ForWorker(nil, nil, AIteratorEvent, ALowInclusive, AHighInclusive, AStride);
end;

class function TSequential.&amp;For(ALowInclusive, AHighInclusive: Integer;
 const AIteratorEvent: TProcInt): Integer;
begin
 result := ForWorker(nil, nil, AIteratorEvent, ALowInclusive, AHighInclusive);
end;

class procedure TSequential.Break;
begin
 raise ESequentialAbort.CreateRes(@SOperationAborted) at ReturnAddress;
end;

class function TSequential.&amp;For(ALowInclusive, AHighInclusive, AStride: Int64;
 const AIteratorEvent: TProcInt64): Int64;
begin
 result := ForWorker64(nil, nil, AIteratorEvent, ALowInclusive, AHighInclusive, AStride);
end;

class function TSequential.ForWorker(Sender: TObject; AEvent: TIteratorEvent;
 const AProc: TProcInt; ALowInclusive, AHighExclusive,
 AStride: Integer): Integer;
begin
 if AHighExclusive &lt;= ALowInclusive then
 begin
 Result := ALowInclusive;
 Exit;
 end;
 result := ALowInclusive;
 if AStride = 0 then AStride := 1;
 try
 while result &lt;= AHighExclusive do
 begin
 AProc(result);
 result := result + AStride;
 end;
 except
 On ESequentialAbort do;
 end;
end;

class function TSequential.ForWorker64(Sender: TObject;
 AEvent: TIteratorEvent64; const AProc: TProcInt64; ALowInclusive,
 AHighExclusive, AStride: Int64): Int64;
begin
 if AHighExclusive &lt;= ALowInclusive then
 begin
 Result := ALowInclusive;
 Exit;
 end;
 result := ALowInclusive;
 if AStride = 0 then AStride := 1;
 try
 while result &lt;= AHighExclusive do
 begin
 AProc(result);
 result := result + AStride;
 end;
 except
 On ESequentialAbort do;
 end;
end;

class function TSequential.&amp;For(ALowInclusive, AHighInclusive: Int64;
 const AIteratorEvent: TProcInt64): Int64;
begin
 result := ForWorker64(nil, nil, AIteratorEvent, ALowInclusive, AHighInclusive);
end;

end.

Class/Record Helpers – Custom Syntactic Sugar for the Delphi Class Hierarchy

I find that I really like class/record helpers.  In the right situation, they help me just make my code look nicer and more intuitive.  They allow me to bundle useful functions together and scope them to exactly where they are needed, and to crack classes (access protected methods and properties) in an official manner that doesn’t look like a hack 🙂  The code I write that calls class/record helpers ends up looking very clean and intuitive, at least to me.  In other words, class/record helpers allow me to add syntactic sugar to the Delphi class hierarchy.

FMX TTreeView Example

I don’t know about you, but I find it inexplicable some of the missing methods in the FMX class hierarchy code.  For example, it is super annoying that the TTreeView class in FMX doesn’t provide an easy way to create a TTreeViewItem and add it into the tree hierarchy.  Instead, you have to create the TTreeViewItem class, set its properties, and then add it to the tree view hierarchy, e.g.,

var
 Item: TTreeViewItem;
begin
 Item := TTreeViewItem.Create(TreeView1);
 Item.Text := aText;
 Item.Parent := Self; // or TreeView1.AddObject(Item);
end;

After years of the VCL hierarchy, I just don’t find this intuitive.  I want to write:

TreeView1.Add('My First Node');
TreeView1.AddObject('My Second Node', aObject);
TreeView1.Add('My Third Node').Add('And Child');

Class Helpers to the rescue!  By writing two class helpers, one for TCustomTreeView and one for TTreeViewItem, we can add these methods and so much more:

TTreeViewCompareFunction = reference to function (const Item: TTreeViewItem): Boolean;
TTreeViewItemHelper = class helper for TTreeViewItem
 private
 { private declarations }
 protected
 { protected declarations }
 public
 { public declarations }
 function Add( const aText: String ): TTreeViewItem; overload;
 function Add( const aText, aData: String ): TTreeViewItem; overload;
 function AddObject( const aText: String; const aData: TObject ): TTreeViewItem; overload;
 function ItemBy( const Compare: TTreeViewCompareFunction ): TTreeViewItem; overload;
 function ItemByTag( const aText: String ): TTreeViewItem; overload;
 function ItemByTag( const aData: TObject ): TTreeViewItem; overload;
end;

TTreeViewHelper = class helper for TCustomTreeView
 private
 { private declarations }
 protected
 { protected declarations }
 public
 { public declarations }
 function Add( const aText: String ): TTreeViewItem; overload;
 function Add( const aText, aData: String ): TTreeViewItem; overload;
 function AddObject( const aText: String; const aData: TObject ): TTreeViewItem; overload;
 function ItemBy( const Compare: TTreeViewCompareFunction ): TTreeViewItem; overload;
 function ItemByTag( const aText: String ): TTreeViewItem; overload;
 function ItemByTag( const aData: TObject ): TTreeViewItem; overload;
end;

[...]

{ TTreeViewHelper }

function TTreeViewHelper.Add(const aText: String): TTreeViewItem;
begin
 result := TTreeViewItem.Create(Self);
 result.Text := aText;
 result.Parent := Self;
end;

function TTreeViewHelper.Add(const aText, aData: String): TTreeViewItem;
begin
 result := TTreeViewItem.Create(Self);
 result.Text := aText;
 result.TagString := aData;
 result.Parent := Self;
end;

function TTreeViewHelper.AddObject(const aText: String;
 const aData: TObject): TTreeViewItem;
begin
 result := TTreeViewItem.Create(Self);
 result.Text := aText;
 result.TagObject := aData;
 result.Parent := Self;
end;

function TTreeViewHelper.ItemBy(const Compare: TTreeViewCompareFunction): TTreeViewItem;
var
 I: Integer;
begin
 Result := nil;
 for I := 0 to Count-1 do
 begin
  if Compare(Items[I]) then
  begin
   Result := Items[I];
   Break;
  end;
  Result := Items[I].ItemBy(Compare);
  if Result &lt;&gt; nil then Break;
 end;
end;

function TTreeViewHelper.ItemByTag(const aData: TObject): TTreeViewItem;
var
 I: Integer;
begin
 Result := nil;
 for I := 0 to Count-1 do
 begin
  if Items[I].TagObject = aData then
  begin
   Result := Items[I];
   Break;
  end;
  Result := Items[I].ItemByTag(aData);
  if Result &lt;&gt; nil then Break;
 end;
end;

function TTreeViewHelper.ItemByTag(const aText: String): TTreeViewItem;
var
 I: Integer;
begin
 Result := nil;
 for I := 0 to Count-1 do
 begin
  if Items[I].TagString = aText then
  begin
   Result := Items[I];
   Break;
  end;
  Result := Items[I].ItemByTag(aText);
  if Result &lt;&gt; nil then Break;
 end;
end;

{ TTreeViewItemHelper }

function TTreeViewItemHelper.Add(const aText: String): TTreeViewItem;
begin
 result := TTreeViewItem.Create(Self);
 result.Text := aText;
 result.Parent := Self;
end;

function TTreeViewItemHelper.Add(const aText, aData: String): TTreeViewItem;
begin
 result := TTreeViewItem.Create(Self);
 result.Text := aText;
 result.TagString := aData;
 result.Parent := Self;
end;

function TTreeViewItemHelper.AddObject(const aText: String;
 const aData: TObject): TTreeViewItem;
begin
 result := TTreeViewItem.Create(Self);
 result.Text := aText;
 result.TagObject := aData;
 result.Parent := Self;
end;

function TTreeViewItemHelper.ItemBy(const Compare: TTreeViewCompareFunction): TTreeViewItem;
var
 I: Integer;
begin
 Result := nil;
 for I := 0 to Count-1 do
 begin
  if Compare(Items[I]) then
  begin
   Result := Items[I];
   Break;
  end;
  Result := Items[I].ItemBy(Compare);
  if Result &lt;&gt; nil then Break;
 end;
end;

function TTreeViewItemHelper.ItemByTag(const aData: TObject): TTreeViewItem;
var
 I: Integer;
begin
 Result := nil;
 for I := 0 to Count-1 do
 begin
  if Items[I].TagObject = aData then
  begin
   Result := Items[I];
   Break;
  end;
  Result := Items[I].ItemByTag(aData);
  if Result &lt;&gt; nil then Break;
 end;
end;

function TTreeViewItemHelper.ItemByTag(const aText: String): TTreeViewItem;
var
 I: Integer;
begin
 Result := nil;
 for I := 0 to Count-1 do
 begin
  if Items[I].TagString = aText then
  begin
   Result := Items[I];
   Break;
  end;
  Result := Items[I].ItemByTag(aText);
  if Result &lt;&gt; nil then Break;
 end;
end;

Ahhh, much better and so much more useful!  If you noticed, I added bonus methods, including one to search your tree view by anything.  Using an anonymous method, you can easily search for tree view items that start with some text:

procedure TForm1.Edit1ChangeTracking(Sender: TObject);
begin
 TreeView1.Selected := TreeView1.ItemBy(function (const Item: TTreeViewItem): Boolean
                                        begin
                                          result := StartsText((Sender as TEdit).Text, Item.Text);
                                        end);
end;

You can download this file here.

Alternatively, when the RiverSoftAVG SVG Component Library (RSCL) is officially released at the end of this month, you will be able to find this class helper bundled with the RiverSoftAVG Common Classes Library (RCCL).  The RCCL is part of any of our products, such as the RSCL and the free RiverSoftAVG Charting Component Suite.

Streaming Non-Published TPersistent Properties Example

For another example, in a post from December, I discussed using the Delphi streaming classes, TReader and TWriter, to stream non-published TPersistent properties.  I did some judicious hacking of the TWriter class (to call the protected TWriter.WriteValue method with vaCollection) and of the TReader class (to call the protected TReader.ReadProperty method) to stream TPersistent properties.  I wrote two functions, ReadPersistent and WritePersistent, to take a TReader/TWriter and read/write a TPersistent instance to and from a stream.

By using class helpers, we can clean up this code and make its use more intuitive.  For the TWriter, we create a TWriterHelper class helper and refactor the WritePersistent function as a method:

TWriterHelper = class helper for TWriter
 private
 { private declarations }
 protected
 { protected declarations }
 public
 { public declarations }
 procedure WritePersistent( Instance: TPersistent );
end;

[...]

{ TWriterHelper }

procedure TWriterHelper.WritePersistent(Instance: TPersistent);
begin
 // write out the Instance properties as a collection. This forces the writer
 // to zero out the property path
 WriteValue(vaCollection);
 // collection is a list of items
 // we are writing out a collection of one item, the Instance
 WriteListBegin;
 WriteProperties(Instance);
 WriteListEnd;
 WriteListEnd; // match vaCollection
end;

Similarly, we move the ReadPersistent function and make it a method of a TReaderHelper class helper.

TReaderHelper = class helper for TReader
private
 { private declarations }
protected
 { protected declarations }
public
 { public declarations }
 procedure ReadPersistent( Instance: TPersistent );
end;

[...]

{ TReaderHelper }

procedure TReaderHelper.ReadPersistent(Instance: TPersistent);
begin
 // read vaCollection
 CheckValue(vaCollection);
 // read list of items, in this case, only one, the top-level instance
 ReadListBegin;
 while not EndOfList do
 ReadProperty(Instance);
 ReadListEnd;
 ReadListEnd;
end;

Much cleaner.  And now it makes the calling code look better too.

procedure THouse.ReadComparable(Reader: TReader);
begin
 Reader.ReadPersistent(FComparable);
end;

procedure THouse.WriteComparable(Writer: TWriter);
begin
 Writer.WritePersistent(FComparable);
end;

The code for this example is here or is already part of the RiverSoftAVG Common Classes Library.  As a bonus, there are a couple other methods packaged with it.

That’s all for now.  I believe that class/record helpers can really help you write better and more intuitive code, and let you concentrate on writing the hard code instead… a spoonful of syntactic sugar to make the hard, medicinal, code go down, if you will 🙂

Happy CodeSmithing!

 

 

I see your class helper and raise you mine!

Small tip: In one of my old posts, I ranted about how Embarcadero changed enumerated types in FMX to drop the prefix around XE6 and added a ton of deprecated warning messages through class helpers, i.e., instead of this:

TAlignLayout = (alNone, alTop, alLeft, alRight, alBottom, alMostTop, alMostBottom, alMostLeft, alMostRight, alClient,alContents, alCenter, alVertCenter, alHorzCenter, alHorizontal, alVertical, alScale, alFit, alFitLeft, alFitRight);

it became this:

 TAlignLayout = (None, Top, Left, Right, Bottom, MostTop, MostBottom, MostLeft, MostRight, Client, Contents, Center, VertCenter, HorzCenter, Horizontal, Vertical, Scale, Fit, FitLeft, FitRight);

 TAlignLayoutHelper = record helper for TAlignLayout
 const
 alNone = TAlignLayout.None deprecated 'Use TAlignLayout.None';
 alTop = TAlignLayout.Top deprecated 'Use TAlignLayout.Top';
 alLeft = TAlignLayout.Left deprecated 'Use TAlignLayout.Left';
 alRight = TAlignLayout.Right deprecated 'Use TAlignLayout.Right';
 alBottom = TAlignLayout.Bottom deprecated 'Use TAlignLayout.Bottom';
 alMostTop = TAlignLayout.MostTop deprecated 'Use TAlignLayout.MostTop';
 alMostBottom = TAlignLayout.MostBottom deprecated 'Use TAlignLayout.MostBottom';
 alMostLeft = TAlignLayout.MostLeft deprecated 'Use TAlignLayout.MostLeft';
 alMostRight = TAlignLayout.MostRight deprecated 'Use TAlignLayout.MostRight';
 alClient = TAlignLayout.Client deprecated 'Use TAlignLayout.Client';
 alContents = TAlignLayout.Contents deprecated 'Use TAlignLayout.Contents';
 alCenter = TAlignLayout.Center deprecated 'Use TAlignLayout.Center';
 alVertCenter = TAlignLayout.VertCenter deprecated 'Use TAlignLayout.VertCenter';
 alHorzCenter = TAlignLayout.HorzCenter deprecated 'Use TAlignLayout.HorzCenter';
 alHorizontal = TAlignLayout.Horizontal deprecated 'Use TAlignLayout.Horizontal';
 alVertical = TAlignLayout.Vertical deprecated 'Use TAlignLayout.Vertical';
 alScale = TAlignLayout.Scale deprecated 'Use TAlignLayout.Scale';
 alFit = TAlignLayout.Fit deprecated 'Use TAlignLayout.Fit';
 alFitLeft = TAlignLayout.FitLeft deprecated 'Use TAlignLayout.FitLeft';
 alFitRight = TAlignLayout.FitRight deprecated 'Use TAlignLayout.FitRight';
 end;

This was a dumb idea for multiple reasons.  The worse one being that loading a FMX form in later versions of Delphi would break the form for earlier versions (as alClient would be replaced by Client and then be an unknown value in pre-XE6 versions).  However, nearly as annoying is that when you write code that support multiple versions of Delphi, all of a sudden you would get gratuitous deprecation warnings in later versions of Delphi.  It is really annoying to sprinkle IFDEFs all over your code for small changes like this, just to get rid of some warnings.

In one of those slap-your-hand-against-your-forehead-why-didn’t-I-think-of-this-earlier moments, I finally realized a few weeks ago that I could use a limitation of record/class helpers (there can only be one) to override their record/class helper in my code with my new and improved class helper that gets rid of the deprecations:

 TMyAlignLayoutHelper = record helper for TAlignLayout
 const
 alNone = TAlignLayout.None;
 alTop = TAlignLayout.Top;
 alLeft = TAlignLayout.Left;
 alRight = TAlignLayout.Right;
 alBottom = TAlignLayout.Bottom;
 alMostTop = TAlignLayout.MostTop;
 alMostBottom = TAlignLayout.MostBottom;
 alMostLeft = TAlignLayout.MostLeft;
 alMostRight = TAlignLayout.MostRight;
 alClient = TAlignLayout.Client;
 alContents = TAlignLayout.Contents;
 alCenter = TAlignLayout.Center;
 alVertCenter = TAlignLayout.VertCenter;
 alHorzCenter = TAlignLayout.HorzCenter;
 alHorizontal = TAlignLayout.Horizontal;
 alVertical = TAlignLayout.Vertical;
 alScale = TAlignLayout.Scale;
 alFit = TAlignLayout.Fit;
 alFitLeft = TAlignLayout.FitLeft;
 alFitRight = TAlignLayout.FitRight;
 end;

Voila, no more deprecation warnings in my code and no need for lots of IFDEFs (the forms problem is another matter).  I am passing along this tip for those who need it.  I know that it cleaned up my code a great deal.

Now I am sure that someone will be upset that I am subverting their deprecation warnings and that they must be there for some good reason.  My response… ah, no, they are not.  It was stupid.  And even though Embarcadero may drop their deprecated enumerated values someday, my record helper will be there to add the functionality right back in; no need to ever deprecate a constant.  Of course, this is my opinion and you are welcome to yours.  For me, I am tempted to submit Embarcadero’s code to Parnassus’ Bad Delphi Code Competition 🙂

Happy CodeSmithing!

Rendering SVGs with RSCL

The RiverSoftAVG SVG Component Library (RSCL) makes it incredibly easy to incorporate scalable vector graphics (SVGs) into your VCL and FMX Delphi applications for Windows, OSX, iOS, and Android.  However, the RSCL provides many choices of how you want to display SVGs within your apps, and it can get confusing to know which is better.  This blog post will discuss the choices and tradeoffs for drawing SVGs with different classes and components in the RSCL.

The RSCL provides 5 methods to display and interact with SVGs, each with advantages and disadvantages:

I’ve listed the 5 choices generally from the simplest to the most complex to code.

Load an SVG into a TBitmap/TGraphic

This is one of the simplest ways to display an SVG in your programs.  The RSCL hooks itself into the TPicture/TGraphic architecture of VCL and the TBitmap codecs of FMX.  In practice, this means you just have to the include RSImaging.SVGImage.pas (VCL) or FMX.RS.SVGCodec.pas (FMX) unit.  Then, you need to read the SVG into the TPicture/TGraphic or TBitmap.

To load an SVG into a TImage control, the code is really easy:

var
  Image1: TImage;
[...]
// VCL
Image1.Picture.LoadFromFile('c:\mysvg.svg'); 
// FMX
Image1.Bitmap.LoadFromFile('c:\mysvg.svg');

To load an SVG into a bitmap is easy as well.  There are no changes to do this in FMX, though with VCL it is slightly more involved:

var
 Bitmap: TBitmap;
 Picture: TPicture;
begin
 Bitmap := TBitmap.Create;
 try
  Picture := TPicture.Create;
  try
   Picture.LoadFromFile('c:\mysvg.svg');
   Picture.Graphic.SetSize(1000,1000);
   Bitmap.Assign(Picture.Graphic);
   PaintBox1.Canvas.Draw(0, 0, Bitmap);
  finally
   Picture.Free;
  end;
 finally
  Bitmap.Free;
 end;
end;

Incredibly simple, though you give up a lot of control.  Everything is rendered using defaults.  The size of the bitmap is determined from the SVG (SVGs may optionally specify their intended width and height or the RSCL will calculate the size).  Additionally, what you get is a bitmap, you cannot change colors of individually elements, detect elements being clicked, or anything else.  The result is a simple bitmap.

The VCL TGraphic architecture is more powerful in this case.  The TGraphic architecture allows you to change the size of the graphic after it is loaded.  The RSCL will redraw the SVG in the new size to keep everything looking sharp and perfect.

However, in FMX, this is not true.  If the specified or calculated size of the SVG is small, you can easily see jaggies as the bitmap is scaled up.  In addition, it should be noted that on mobile platforms, the FMX text output to bitmaps is poor.

Use a TRSSVGImageList to manage multiple SVGs

A variation of the first method is to use the TRSSVGImageList (VCL/FMX) control.  The TRSSVGImageList component provides a powerful means to manage a list of SVGs in your application.  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.

In VCL, you can change the TRSSVGImageList.Width and Height at any time and the SVGs will be re-rendered.

With the FMX platform, the TRSSVGImageList is even more powerful.  It defines a TImageList composed entirely of SVGs to enable compact, graphical support of different screen resolutions and densities.

As you know, FireMonkey is a multi-platform framework (Windows, iOS, Mac OS X, and Android), and therefore FireMonkey applications work on devices having different screen densities and resolutions. Embarcadero recommends using FireMonkey multi-resolution bitmaps so that your images and icons in FireMonkey applications will appear correctly in different densities and resolutions (Windows, iOS, Mac OS X, and Android). However, a multi-resolution bitmap is a collection of bitmaps providing the same image (probably), each designed for a different scale to provide the same look irregardless of the resolution of the device the app is running on. This means that multiple copies of the same image can be embedded in your app (Embarcadero recommends at least two scales for iOS, regular and retina, and four or more on Android) – most of these copies are unused as they are intended for a different scale than the one your app is running on – potentially wasting lots of disk and memory space.

Not only do you not even need to generate all these extra resolution images with the TRSSVGImageList component, but you don’t need to store them either.  The TRSSVGImageList component solves this problem by creating a TImageList composed entirely of SVGs. For each SVG, you specify the sizes that the SVG should take at each screen scale. The TRSSVGImageList component generates, at run-time, bitmaps only for the current screen scale, potentially saving lots of space.  Because the SVG is buffered as a bitmap for the current screen scale, refreshes are extremely fast after the initial setup.

Like all good things, there are limitations.  You cannot combine SVGs and regular images in the same imagelist.  Customization and interaction is limited, especially compared to the following methods.  While the SVGs are available to edit, you do not have OnDrawing/OnDrawn events to hook into and customize the appearance of elements.  There are no OnElementClicked events to hook into to see which element has been clicked.  At the level of the visual controls like TSpeedButton, the SVGs are again just bitmaps.

Finally, it is generally not recommended to use SVGs with text on non-Windows platforms unless the specified Width and Height are large. The FMX library provides poor text quality when writing to offscreen bitmaps, which is what the TRSSVGImageList does, on these platforms.

Use a TRSSVGImage to display SVGs

In most ways, the TRSSVGImage (VCL/FMX) control is the simplest means to display and interact with SVGs in the RSCL library.  Everything can be done at design-time.  I won’t repeat the help topic here, but to summarize: just drop a TRSSVGDocument (VCL)/TRSFmxSVGDocument (FMX) component on your form, set its Filename property to your SVG, drop a TRSSVGImage control on your form, and set its SVGDocument property.

Use a TRSSVGImage and TRSSVGDocument/TRSFmxSVGDocument to display SVGs quickly and easily at design-time

Use a TRSSVGImage and TRSSVGDocument (VCL) / TRSFmxSVGDocument (FMX) to display SVGs quickly and easily at design-time

The TRSSVGDocument/TRSSVGImage combination is incredibly powerful.  Using the TRSSVGDocument properties, you can change the default options for displaying and interacting with your SVG.  The SVG is always available for modification on re-rendering.  At run-time, use the TRSSVGDocument.SVG property to programmatically access the entire SVG document hierarchy.  Any changes are automatically propagated to the TRSSVGImage control.

The TRSSVGImage control can draw directly to the control’s canvas, or, for performance, set the Buffered property and let the TRSSVGImage control create a backbuffer.  As the TRSSVGImage control size changes, the SVG is always redrawn to maintain the highest resolution needed.  You can also hook into the TRSSVGDocument.OnDrawing and OnDrawn events to customize the painting of elements.

The TRSSVGImage control even allows you to capture mouse-clicks and see what element in the SVG was under the mouse through its OnClickElement event.

The TRSSVGImage/TRSSVGDocument combination provides a good combination of power, performance and ease of use.  It provides a high degree of compatibility with SVGs that the RSCL supports.

Use a TRSSVGPanel to generate controls from SVGs

One of the most interesting ways to render SVGs is to use the TRSSVGPanel control.  The TRSSVGPanel provides a panel for generating design-time or run-time FMX controls (or VCL, though the compatibility is not as good) for a SVG. Like the TRSSVGImage control, you drop a TRSSVGDocument / TRSFmxSVGDocument on your form and hook it up to the TRSSVGPanel.  Unlike the TRSSVGImage control, the TRSSVGPanel control does not draw the SVG using canvas operations. Rather, it generates the FMX (or VCL) controls, which then draw the appearance of the SVG.

For example, instead of drawing a rectangle, circle or path SVG element, it creates a TRectangle, TCircle, or TPath control for each element and configures the control to make it appear similar to the drawn SVG element.  Generally, there is one control for each SVG element.  The exception is the text elements; every text element and its child text elements (tspan, textpath, tref) are combined into one TRSSVGText object.

If you right click the TRSSVGPanel at design-time and select “Generate Controls”.  The TRSSVGPanel will generate the controls permanently and they will be streamed with the form.  At this point, you can delete the TRSSVGDocument / TRSFmxSVGDocument component and not even deploy an SVG with your application.

Configure SVG controls with the Object Inspector. For example, adding a TShadowEffect and changing the color of a control.

Configure SVG controls with the Object Inspector. For example, adding a TShadowEffect and changing the color of a control.

Generating the controls permanently is what makes this method interesting.  You can now interact with the SVG elements as normal Delphi controls.  The controls have the typical Delphi events (e.g., OnClick, OnMouseOver, etc) and the typical properties (e.g., Visible, Enabled, Width, Height, etc), which you can customize.  With FMX, you can even apply Filter Effects to any control to change its appearance.

The TRSSVGPanel control is the least compatible and most limited way to display SVGs, especially in the VCL version. The FMX TRSSVGPanel does do a very good job with compatibility.  However, because of the way that the SVG elements must be rendered, hundreds of controls can be generated for one SVG document, with many of them overlapping.  In VCL, there can be problems with clipping and with gradients.  In addition, the TRSSVGPanel only creates controls at the aspect ratio of the SVG, i.e., the SVG is not stretched to fit the panel (you can modify the scale of the created controls by setting the TRSSVGPanel.ScaleOriginal property).

What is an advantage on one hand, the ability to permanently generate controls and decouple the controls from the SVG, is also a disadvantage on the other hand.  Many SVGs share resources, such as “use”-ing another element (or elements) in a different location or sharing linear and radial gradient elements for fill and stroke properties.  Every SVG element that references a linear gradient element shares the same element.  Update the linear gradient and all SVG elements that reference it get updated.  With the TRSSVGPanel controls, every control that uses that linear gradient gets its own copy in its the Fill or Stroke property.  If you have deleted the SVG document and edit one control’s Fill property, no other element gets updated.

Finally, this method consumes the most memory (all of those controls can be expensive) and is the slowest to generate the SVG display the first time as it needs to create perhaps 100s of controls for the SVG document.  However, subsequent refreshes can be relatively fast as it is just controls updating.

The TRSSVGPanel control is an interesting way to render an SVG and gives you some flexibility not available through other methods.  This method can be very useful if used judiciously.  However, you sacrifice flexibility in a lot of other ways.  And finally, note that the VCL TRSSVGPanel version is the slowest, least flexible, and least compatible way to display SVGs in the RSCL.

Draw to a TCanvas directly using a TSVGDocument class

The final method to render an SVG is the most powerful and one of the fastest (though there is no buffering).  Every SVG loaded by the RSCL creates a TSVGDocument and its hierarchy of TSVGxxxElement classes. The TSVGDocument class (and actually all TSVGGraphicElement descendants) exposes a number of Draw methods for drawing the SVG to any canvas, including a printer TCanvas.  You can specify the rectangle the SVG is drawn in and provide any matrix transformation you want to affect the rendering (such as rotating or skewing).

Like the TRSSVGImage method, there are events for OnDrawing and OnDrawn that you can hook into to customize the appearance of SVG elements.  You can edit any TSVGxxxElement object and change its appearance, including inheriting (or not) style attributes from parents.  There is an ElementAtPos method you can call to determine which element is under a mouse pointer.  You can customize the TSVGDocument.Options as you like.  Also, you can specify a DefaultAspectRatio to control if the SVG is rendered proportionally or not.

Using the DrawBeforeElement, DrawElement, and DrawAfterElement methods, you can implement your own buffering scheme for buffering any part of the SVG document hierarchy.  The DrawBeforeElement method can draw in place the “older”/before siblings and ancestors of an element.  The DrawElement method will draw the specified element, in place, and its children but not ancestors or siblings.  Finally, the DrawAfterElement method will draw all siblings after the specified element.  By using these three methods, you can buffer background and foreground SVG elements to bitmaps.  As one element changes, the buffers can be drawn quickly without re-rendering SVG elements.  The included SVG Editor demo in the RSCL uses this scheme to provide excellent response as a user edits an element.

The TSVGDocument provides the most flexibility for specifying exactly what you render as well as where you render.

Differences between VCL and FMX

Besides the TRSSVGPanel and TRSSVGImageList differences, the VCL and FMX versions of the RSCL are as similar as possible and share most of the same code base.

In the VCL version, the RSCL uses the GDI+ library for software-based rendering of SVGs.  Because it is software-based, the VCL version is slower than the FMX version.  The quality of radial gradients is slightly worse in the VCL version (if you use the patch for the FMX version).  On the positive side, the GDI+ library provides irregular clipping regions, enabling generally better compatibility and sometimes faster rendering when large regions are clipped.  The GDI+ library provides much more text metrics than FMX (i.e., ascent, descent, fonts installed, etc), allowing much better text placement and font choice.

In the FMX version, the RSCL will use whichever TCanvas you choose (DirectX, OpenGL), including the hardware-based GPU TCanvas.  This means that the FMX RSCL is faster.  However, the FMX classes do not provide irregular clipping regions and minimal font and text metrics, sacrificing SVG fidelity somewhat.  The FMX version also tends to not render text well to off-screen bitmaps, especially on the mobile platforms.  On mobile (iOS and Android), or if you use the Windows FMX GPU Canvas, the RSCL must fill text instead of creating path-based representations of the text.

Differences between Path-based Text and Fill Text

In the section above, I mentioned fill text and path-based text.  The RSCL provides two methods for rendering text: path-based text and fill text. The default method is path-based text on desktop platforms and fill text on mobile platforms.

In path-based text, the text is converted to paths, cached for later use, and then drawn using the paths. It has the advantage that text can be both filled and stroked, and allows pixel level precision for selection. The path-based text method is slower than fill text for the initial draw call.  However, subsequent draw calls can be faster with complex text SVGs as all of the rotations, kerning, etc has already been performed.  Path-based text also consumes more memory than filling text.  However, path-based text rendering provides greatest compatibility with SVGs text elements on desktop platforms.

With fill text, the text is drawn directly to the canvas using fill text calls. It is faster for the first Draw call and uses much less memory.  Fill text also does a better job with small text.  It has better compatibility with non-Western fonts. However, filling text does not use the Pen/Stroke and so cannot both fill and stroke text.  In FMX, you cannot use gradients for filling text.  With complex text with lots of placement and rotation per character, fill text does not perform center and right alignment.  Selections of SVG text elements rendered with fill text use the bounding box of the text, which is much less accurate; for text paths, the bounding box is also incorrect.  Finally, rendering decorative fonts (underlined, overlined, strike-out) is not as accurate across words and characters as path-based text rendering.

Whether path-based text or fill text is used on desktop platforms depends on if soUseFillText is in the TSVGDocument.Options property.  On mobile platforms (iOS and Android), or if you use the FMX GPU Canvas (FMX.Types.GlobalUseGPUCanvas), text is always filled and not converted to paths.

Wrap-up

This was an incredibly long blog post.  Kudos to all of you who are still reading it.  🙂  However, I thought that a post like this would help explain the power as well as the tradeoffs required when using the RiverSoftAVG SVG Component Library.  The final v2.0 release of the RSCL will be within a month.  Until release, you can buy the RSCL for 30% off, and you will be able to access both the v1.x release and through our Early Experience Program the current v2.0 beta release.

That’s all for now.  Happy CodeSmithing!

 

Colorful Text with FMX

Small tip: Convert text to paths to jazz up your text rendering in FMX

The text rendering in FMX allows you to draw text by calling the TCanvas.FillText method.  However, the rendered text is very plain and doesn’t take advantage of gradient or bitmap fills and ignores the stroke properties entirely:

Boring Text Screenshot

Boring Text Screenshot

In the screenshot above, I painted inside a TPaintBox (top) by copying the font from a TText object (middle) and the Gradient Fill and Stroke properties from a TRectangle (bottom).  The TText fill text output is black (bottom) and the TPaintBox (top) outputs a dim, solid “Hello World!!!” text:

procedure TfrmFancyText.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
begin
 Canvas.Font.Assign( Text1.TextSettings.Font );
 Canvas.Fill := Rectangle1.Fill;
 Canvas.Stroke.Assign( Rectangle1.Stroke );
 Canvas.FillText(PaintBox1.ClipRect, 'Hello World!!!', False, 1, [], TTextAlign.Center );
end;

Luckily, the FMX library allows us to jazz up our text by just doing a little work.  By using the TTextLayout class to convert text to paths, we can then do anything we want with the text path, including filling and stroking.

Colorful Text Screenshot

Colorful Text Screenshot. Notice that the top text has gradients for both the fill and outline of the text

The TTextLayout.ConvertToPath method converts any text to a path using the font object you specify.  To convert text to paths, you first need to create your TTextLayout and TPathData objects.  Then you need to assign the font you want for the text, the Text to convert, and finally call the ConvertToPath method.  After you have your path data, you can do anything you want with it.

procedure TfrmFancyText.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var
 TextLayout: TTextLayout;
 TextPath: TPathData;
begin
 Canvas.Fill := Rectangle1.Fill;
 Canvas.Stroke.Assign( Rectangle1.Stroke );

 TextLayout := TTextLayoutManager.DefaultTextLayout.Create;
 try
  TextPath := TPathData.Create;
  try
   TextLayout.Font := Text1.TextSettings.Font;
   TextLayout.Text := 'Hello World!!!';
   TextLayout.ConvertToPath(TextPath);
   Canvas.FillPath(TextPath, 1);
   Canvas.DrawPath(TextPath, 1);
  finally
   TextPath.Free;
  end;
 finally
  TextLayout.Free;
 end;
end;

There are a few problems with converting text to paths.  The first problem is that your path text cannot be placed anywhere you want without a little matrix math 🙂  If you notice in the second screenshot above, the gradient-filled Hello World is in the upper left.  This is because the created path is based off the 0, 0 point and the TCanvas draws a path whereever the path points are.  To move the path, you need to create a translation matrix and either apply it to the TPathData or temporarily set the TCanvas.Matrix property:

   [...]   
   TextLayout.ConvertToPath(TextPath);
   TextPath.ApplyMatrix(TMatrix.CreateTranslation(100,50));
   Canvas.FillPath(TextPath, 1);
   Canvas.DrawPath(TextPath, 1);
   [...]

The next problem, and perhaps the biggest one for some people, is that not all FMX Canvas objects support the ConvertToPath method.  The GPU Canvas (i.e., FMX.Types.GlobalUseGPUCanvas := True) implementation of the ConvertToPath method is blank!  (Update 2016/01/26: earlier versions of Delphi FMX GPU Canvases did not support ConvertToPath; it is supported since at least XE8)

Finally, the ConvertToPath method does not support all font styles, specifically fsUnderline and fsStrikeOut.  These two styles are ignored and the line will not be converted to a path.  But to me, all of these are minor annoyances, and it is just very cool to be able to convert text to paths without a lot of work.  It also, as you have seen, allows us to get around limitations in the text rendering in FMX.  I hope you find this small tip useful.

Happy CodeSmithing!