Wiladams / svgandme

SVG Parser and rendering using blend2d for graphics
MIT License
7 stars 4 forks source link

Working example app? #16

Open stuta opened 2 months ago

stuta commented 2 months ago

I tried to find build instructions on how to build a minimal shared library that transforms SVG to some bitmap format in OSX, in Linux, and Windows using Clang. Then I tried /testy/svgimage/svgimage.cpp but it seems that all example apps have lots of errors (old code?) in them.


I created this minimal app using #8 mmapfile.h and was able to build it with: g++ svglib.cpp -o svglib -std=c++14 -I svg -I app -I blend2d -L. -l blend2d && install_name_tool -change @rpath/libblend2d.dylib @loader_path/libblend2d.dylib svglib. During the build, there are 91 warnings generated, mostly in bithacks.h and in bspan.h: warning: duplicate 'static' declaration specifier [-Wduplicate-decl-specifier].

When I run it with ./svglib gallery/Spiderman.svg I get errors:

SVGDocument.loadFromXmlIterator : ERROR - unexpected element:
?xml version="1.0" encoding="iso-8859-1"?
SVGDocument.loadFromXmlIterator : ERROR - unexpected element:
 Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)
Done!

When I delete <?xml version="1.0" encoding="iso-8859-1"?> and <!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> from the svg file then the program runs without errors. I tested with printXml() and it prints all elements, so the file is read correctly.

Here is the test app:

#include "mmapfile.h"
#include "svg.h"
#include "xmlutil.h" // this line MUST be after svg.h
using namespace waavs;

// Create one of these first, so factory constructor will run
SVGFactory gSVG;
// Reference to currently active document
std::shared_ptr<SVGDocument> gDoc { nullptr };
FontHandler gFontHandler {};

int printXml(ByteSpan span) {
    // iterate over the XML elements printing each one
    XmlElementIterator iter(span);
    while (iter.next()) {
        printXmlElement(*iter);
    }
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("Usage: svglib <xml file>  [output file]\n");
        return -1;
    }
    // create an mmap for the specified file
    const char* filename = argv[1];
    mmapfile* mapped = mmapfile_open(filename);
    if (nullptr == mapped) {
        return -1;
    }
    ByteSpan span(mapped->start, mapped->length);
    // printXml(span);
    int canvasWidth = 800;
    int canvasHeight = 800;
    int systemPpi = 92;
    gDoc = SVGDocument::createFromChunk(span, &gFontHandler, canvasWidth, canvasHeight, systemPpi);
    mmapfile_close(mapped); // close the mapped file
    if (gDoc == nullptr) {
        printf("Failed to create document\n");
        return -1;
    }
    // how to convert the document to a bitmap?
    printf("Done!\n");
    return 0;
}
Wiladams commented 2 months ago

I have just made a major upgrade to the code. Although almost every area has been touched, the primary focus has been separating the creation of the bitmap from the document creation. So, you can create a document once, and render into different sized bitmaps as you need.

The svgimage case is the best example.

The README.md shows how you can compile it on Windows from a Visual Studio command line.

The file svgimage.cpp shows the basic structure.

The rest of that code, to convert to bitmap, would be:

    // Create a drawing context to render into
    SvgDrawingContext ctx(&gFontHandler);
    BLImage img(surfaceFrame.w, surfaceFrame.h, BL_FORMAT_PRGB32);

    ctx.begin(img);
    ctx.clearAll();
    //ctx.fillAll(BLRgba32(0xFFFFFFFF));

    // apply the viewport's sceneToSurface transform to the context
    ctx.setTransform(vp.sceneToSurfaceTransform());

    // Render the document into the context
    gDoc->draw(&ctx, gDoc.get());
    //ctx.setStrokeStyle(BLRgba32(0xFFff0000));
    //ctx.setStrokeWidth(2.0);
    //ctx.strokeLine(0, 0, 640, 640);
    ctx.flush();
    ctx.end();

    // Save the image from the drawing context out to a file
    const char* outfilename = nullptr;
    printf("argc: %d\n", argc);

    if (argc >= 3)
        outfilename = argv[2];
    else 
        outfilename = "output.png";

    img.writeToFile(outfilename);

The way I build on windows is to load the Visual Studio project file in testy directory (testy.sln). From there you can build all the samples.

But, svgimage.cpp is the succinct example. svgviewer is a more involved Windows based viewer.

The code is all headers, so as long as your compiler can locate them, it should be able to deal with it.

Wiladams commented 2 months ago

Those "ERRORS" you're seeing are benign. They're just notes that the parser isn't doing anything with those nodes. It does not affect the rendering (it will still render). Just look at the complete svgimage.cpp file, and go from there.

stuta commented 2 months ago

Thank you for the instructions.

Note: -std=c++17 is needed for std::filesystem::directory_iterator to work, c++14 does not work.

Now I'm able to convert to png and pmp data, other formats like jpeg or jpg do not seem to work. I was also able to get data without writing a file by using this code:

  BLImageCodec codec;
  BL_PROPAGATE(codec.findByExtension(outFileName));
  BLArray<uint8_t> buffer;
  ret = img.writeToData(buffer, codec);
  printf(" - wrote data '%zu' bytes, return: '%d'\n", buffer.size(), ret);
  // copy data from buffer.data()

When I test the program I get high-quality results with fonts in gallery/Smith_chart.svg.

I call loadFontDirectory("/Library/fonts") and it loads '/Library/fonts/Arial.ttf' successfully. But then I get a warning: == FontHandler::selectFontFamily, NOT FOUND: Arial

Wiladams commented 2 months ago

The core of the library itself does not require C++17, as all that directory stuff is not in there. I had moved the loading of font directories out into user app because of this. So yes, if you're going to load fonts in that way (using std::filesystem), then you will need to compile with C++17.

Are you sure it's loading the Arial.ttf successfully? I'll have to look at that code and see if I'm actually swallowing an error when it does not load correctly.

I can turn off the error messages, and add a flag to indicate "verbose" if you want them to be turned on. That will require some replumbing to centralize error reporting into a 'logging' module, but it should not be too bad. On Windows, I can compile using a non-console system, and it will just swallow all the printf statements, but that's not available anywhere other than Windows.

stuta commented 2 months ago

I was using loadFontDirectory("/Library/fonts") and it had an alias to /System/Library/Fonts/Supplemental/Arial.ttf. I changed the path to "/System/Library/Fonts/Supplemental/" and now I don't get NOT FOUND: Arial -warning. Loading alias font file with gFontHandler.loadFontFace() returns success == 1 but it does not work with alias.

Converting gallery/Smith_chart.svg gives lots of errors like SVGVisualNode::bindPropertiesToGroot, ERROR - NO CLASS SELECTOR FOR fil0. There are 874 rows of errors/warnings from one file, printing them must slow down the conversion.

Can I set the default font to something else than Arial?

Wiladams commented 2 months ago

I'll turn off all those printf statements in my next upload. Same with the class selector thing. I'll look into changing the default, or just being able to set the default. Right now it's hard coded to Arial. Would you want to set the default by common name: Arial for example, or some other attribute?

Wiladams commented 2 months ago

Those errors on the class selector are innocuous., and an error in the document itself. They are references to CSS selectors that don't actually exist.

stuta commented 2 months ago

Would you want to set the default by common name: Arial for example, or some other attribute?

I don't know much about fonts.

Many fonts seem to have names like Roboto-Regular or Roboto-Medium. I guess a common name like Roboto would be easier, but having a full name could be extra feature.

Wiladams commented 2 months ago

The font-family attribute of SVG is what you're probably interested in. Most documents that use text will specify something of course. Probably going with family name is the way, and probably platform dependent, and I guess why you'd want to change it. I'll just add that to the FontHandler class, since that's where these names get resolved.