Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Objective-C

Text to SVG Path Utility in U++

5.00/5 (20 votes)
22 Jun 2021CPOL5 min read 28.2K   1.1K  
Creating a simple utility for converting text to SVG path in U++
In this article, we use the power of U++ to create a niche but useful utility to convert the text to SVG path in about 20 minutes and 140 lines of code.

Introduction

While developing some graphic code, I came to need some font glyphs as SVG path so that I can draw them reasonably in the code. After examining the options, I decided to write my own utility, using U++ framework, which took whole 20 minutes. While quite a niche tool, I think that both this utility and description of the code can be useful for the wider public.

Image 1

TextToSvgPath Function

SVG path is a simple text format that describes graphics shape using move/line/curve primitives. Meanwhile, all current font systems also draw glyphs using exactly the same primitives. So if we are about to write conversion between font and SVG path, we basically need to extract this curve information from the font for the particular glyph and convert it to SVG path textual representation.

In U++, fonts are represented by Font type. Font provides:

C++
void   Render(FontGlyphConsumer& sw, double x, double y, int ch) const;

method, which renders glyph representing unicode codepoint ch to the pure virtual interface FontGlyphConsumer:

C++
struct FontGlyphConsumer {
    virtual void Move(Pointf p) = 0;
    virtual void Line(Pointf p) = 0;
    virtual void Quadratic(Pointf p1, Pointf p2) = 0;
    virtual void Cubic(Pointf p1, Pointf p2, Pointf p3) = 0;
    virtual void Close() = 0;
};

Now the obvious task is to implement this interface by the class that produces SVG path output:

C++
struct TextToSvg : FontGlyphConsumer {
    String t;           // here we accumulate the SVG path text
    
    void Put(Pointf p); // put the point as text coordinates to SVG path text
    
    virtual void Move(Pointf p);
    virtual void Line(Pointf p);
    virtual void Quadratic(Pointf p1, Pointf p2);
    virtual void Cubic(Pointf p1, Pointf p2, Pointf p3);
    virtual void Close();
};

void TextToSvg::Put(Pointf p)
{
    t << Format("%.2f %.2f ", p.x, p.y);
}

void TextToSvg::Move(Pointf p)
{
    t << 'M';
    Put(p);
}

void TextToSvg::Line(Pointf p)
{
    t << 'L';
    Put(p);
}

void TextToSvg::Quadratic(Pointf p1, Pointf p)
{
    t << 'Q';
    Put(p1);
    Put(p);
}

void TextToSvg::Cubic(Pointf p1, Pointf p2, Pointf p)
{
    t << 'C';
    Put(p1);
    Put(p2);
    Put(p);
}

void TextToSvg::Close()
{
    t << 'Z';
}

With this done, we can now implement a function that actually converts the whole text, with given font, into the SVG path:

C++
String TextToSvgPath(double x, double y, const char *text, Font fnt, bool singleline)
{
    WString ws = ToUnicode(text, CHARSET_DEFAULT);
    TextToSvg t;
    for(const wchar *s = ~ws; *s; s++) {
        fnt.Render(t, x, y, *s);
        x += fnt[*s]; // move the reference point
        if(!singleline)
            t.t << "\n";
    }
    return t.t;
}

Adding UI

To complete the utility, we shall add a GUI dialog. It will allow the user to change the font and text, show SVG path in read only editor area, copy the SVG path to clipboard. Also, we will provide the preview of rendered path.

We start by designing dialog layout in U++ layout designer:

Image 2

Note that we keep preview field 'untyped', that means we shall provide the type of that widget later.

We include the layout file into the code with (this line can then generate for us):

C++
#define LAYOUTFILE <TextToSvgPath/TextToSvgPath.lay>
#include <CtrlCore/lay.h>

Now let us prepare the preview widget class:

C++
struct Preview : Ctrl {
    String svgpath;
    
    virtual void Paint(Draw& w);
};

Obviously, this is no rocket science, we just need to keep a copy of generated SVG path here and Paint it on demand:

C++
void Preview::Paint(Draw& w)
{
    DrawPainter sw(w, GetSize());
    sw.Clear(SWhite());
    sw.Path(svgpath).Fill(SBlack());
}

In U++, basic rendering class Draw is quite simple, providing just enough to draw colored rectangles, text and images, with possible HW acceleration. Meanwhile, we have much more refined software renderer Painter which more or less provides all graphic primitives as needed for SVG or PDF rendering. One of the tasks this renderer can do is to render SVG path, which is exactly what we need here. We construct DrawPainter which is sort of bridge between Draw and Painter, then we clear the background (SWhite unlike plain White is color adjusted to eventual dark theme mode - in that case, it is actually black color). We render the path and fill it with SBlack color (which again is white in dark theme mode).

Now it is time to create our dialog class, which in fact is quite simple:

C++
struct TextToSvgPathDlg : public WithTextToSvgPathLayout<TopWindow> {
    Preview preview;

    void Render();
    
    TextToSvgPathDlg();
};

Here, we add the layout we have created to the TopWindow class (this represents top-level window) and use the result as base of our dialog class. This adds all widgets as member variables, except those that are untyped, which is exactly the case of preview widget, so we add it as member variable here. The only method we define here is Render, because this will be the dialog reaction to most user actions.

Then most of the work is done in the constructor:

C++
TextToSvgPathDlg::TextToSvgPathDlg()
{
    CtrlLayout(*this, "Text to SVG path converter");

CtrlLayout function calls some methods in WithTextToSvgPathLayout to actually place all widgets on the Dialog.

Now we shall populate font face selector (DropList widget). Font faces in U++ are simply indexed. Index 0 is reserved for default GUI font, so we start with 1:

C++
for(int i = 1; i < Font::GetFaceCount(); i++)
    if(Font::GetFaceInfo(i) & Font::SCALEABLE)
        face.Add(i, Font::GetFaceName(i));

DropList selector entries can have two values - "real" value, which then represented the value of widget, if selected, and "display" value that is what user actually sees. So we pass the index of face as real value and its text description obtained by Font::GetFaceName as display value.

 

We preset SANSSERIF face, which is predefined face index that corresponds to Arial font in Win32 hosts.

C++
face <<= Font::SANSSERIF;

(operator <<= in U++ is overloaded for widgets and means "assign value") and then go on to setup height selector with limits, predefined list of values and initial setting of 128 pixels.

C++
height.MinMax(6, 500);

for(int i = 4; i < 500; i += i < 16 ? 1 : i < 32 ? 4 : i < 48 ? 8 : 16)
    height.AddList(i);

height <<= 128;

Now we make editor field we are going to use to display SVG path read-only:

C++
svgpath.SetReadOnly();

and we add some borders to our preview field:

C++
preview.SetFrame(ViewFrame());

Now it is time for the core action. What we actually want is that if users change any settings or the text, we re-render the svg path and display it in svgpath and preview widgets. As this is "any settings", we will be lazy and simply iterate through all widgets in the dialog and assign rendering action to all of them, unless it is the Button:

C++
for(Ctrl *q = GetFirstChild(); q; q = q->GetNext())
    if(!dynamic_cast<Button *>(q))
        *q << [=] {
            Font fnt(~face, ~height);
            fnt.Bold(~bold);
            fnt.Italic(~italic);

            svgpath <<= preview.svgpath =
                        TextToSvgPath(0, 0, (String)~text, fnt, ~singleline);
            preview.Refresh();
         };

Operator << adds the default action to widget that is invoked when user changes the widget value or status. In this case, the action constructs Font instance based on values of widgets in the dialog, then uses TextToSvgPath to convert the text with given font into the SVG path. This String is then asigned to both svgpath editor field and as preview svg path. Finally, Refresh invokes repainting of preview.

Final touches are to setup button to copy the path to the clipboard:

C++
copy.SetImage(CtrlImg::copy());
copy << [=] {
    WriteClipboardText(preview.svgpath);
};

and make the dialog resizeable:

C++
    Sizeable().Zoomable();
}

Finally, we need to write U++ equivalent of main function, which is trivial:

C++
GUI_APP_MAIN
{
    TextToSvgPathDlg().Run();
}

Conclusion

Truth be told, I could have probably found a way to do this with existing tools or found similar utility. On the other hand, using U++ this was so trivial that perhaps I even saved some tiny bit of time by reinventing the wheel. Either way, it was fun to develop this piece of code and putting it on the internet means anybody in need of something similar can download from here.

Useful Links

History

  • 20th March, 2020: Initial version
  • 29th September, 2020: Added link to Getting started... article
  • 14th November, 2020: Useful links updated
  • 23th June, 2021: Useful links updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)