Search code examples
angular.net-coresvg-edit

Svg to png base64 using HTML5 Canvas does not work reliably on IOS browsers (Chrome / Safari)


I am building a web app using asp.net-core with angular 4 templates and uses svg-edit to allow user to draw / load / save images on mobile devices particularly on the IOS devices / android devices. The svg-edit works well for what I need but I've encounter an issue on the IOS devices regarding to generate/ convert the user drawing (svg) into another format (png base64) which will be POST to the api as part of the saving process.

I use the following code which uses html canvas to convert the svg into png base64

svg-editor.js

editor.getDrawingPngBase64 = function (svgString) {
            if (!$('#export_canvas').length) {
                $('<canvas>', { id: 'export_canvas' }).hide().appendTo('body');
            }

            var canvas = $('#export_canvas')[0];
            canvas.width = $("#svgcanvas").width();  // svgCanvas.contentW;
            canvas.height = $("#svgcanvas").height();  // svgCanvas.contentH;
            var ctx = canvas.getContext("2d");

            var promise = new Promise(function (resolve, reject) {
                function drawInlineSVG(ctx, rawSVG, callback) {
                    var svg = new Blob([rawSVG], { type: "image/svg+xml;charset=utf-8" }),
                        domURL = self.URL || self.webkitURL || self,
                        url = domURL.createObjectURL(svg),
                        img = new Image;

                    img.onload = function () {
                        ctx.drawImage(this, 0, 0);
                        var base64 = canvas.toDataURL("image/png");
                       resolve(base64);
                    };

                    img.src = url;
                } 

                drawInlineSVG(ctx, svgString);
            });

            return promise;
        }

However, this only works sometime on the IOS device using chrome or safari browser. The onload method simply not getting called. I am guessing the svg image could be too big for it to convert.

I have also tried this method which doesn't work reliably either....

editor.getDrawingPngBase64 = function (svgString) {
            if (!$('#export_canvas').length) {
                $('<canvas>', { id: 'export_canvas' }).hide().appendTo('body');
            }

            var canvas = $('#export_canvas')[0];
            canvas.width = svgCanvas.contentW;
            canvas.height = svgCanvas.contentH;
            var ctx = canvas.getContext("2d");

            var promise = new Promise(function (resolve, reject) {
                function webkitNamespaceBugWorkaround(pText) {
                    var lText = pText.replace(/\ xlink=/g, " xmlns:xlink=", "g");
                    lText = lText.replace(/\ href=/g, " xlink:href=", "g");
                    return lText;
                }

                canvg(canvas, webkitNamespaceBugWorkaround(svgString), {
                    renderCallback: function () {
                        var base64 = canvas.toDataURL("image/png");
                        resolve(base64);
                    }
                });
            });

            return promise;
        }

I have also tried canvg library and it does not work reliably either

These method is called by the angular4 component.ts

    public save() {
const svgString: String = svgEditor.getSvgString();
(<any>window).svgEditor.getDrawingPngBase64(svgString).then(pngBase64: string) => {
             // Call api and save the image
         }
     }

The reason I am converting svg to png on the client side is because I am not able to install the C# SVG Rendering library on dotnet-core solution (https://www.nuget.org/packages/Svg/)

Please help!


Solution

  • I had this exact same issue previously on a dotnetcore solution except I was not using svg-edit. Here's my workaround solution for this problem....

    Create a new console app using dotnet framework...and install the Svg library on the package manager

    >> Install-Package Svg -Version 2.3.0
    

    In Program.cs for the console app, read the svg file and uses the Svg library to convert it to png and output it using Console.WriteLine

    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
                return;
    
            using (var oStream = new System.IO.MemoryStream())
            {
                string svgPath = args[0];
    
                if (string.IsNullOrWhiteSpace(svgPath))
                    return;
    
                var text = System.IO.File.ReadAllText(svgPath);
                using (var xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(text)))
                {
                    xmlStream.Position = 0;
                    var svgDocument = Svg.SvgDocument.Open<SvgDocument>(xmlStream);
    
                    SetupThumbnailImage(svgDocument, out Bitmap bitmap, out Graphics graphics, 250, 120);
                    svgDocument.Draw(graphics);
    
                    bitmap.Save(oStream, ImageFormat.Png);
    
                    string pngBase64String = Convert.ToBase64String(oStream.ToArray());
                    Console.WriteLine(pngBase64String);
                }
            }
        }
    }
    

    In your dotnet-core side, you need to post the SVG back to the api, save it on the server, run the consoleApp.exe as a process and read the base64 from the StandardOutput.

    private async Task<string> SaveThumbnailImage(string svgString)
    {
        // Save the svg
        var filePath = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SvgHelper", Path.Combine(Guid.NewGuid() + ".svg"));
        using (StreamWriter streamWriter = System.IO.File.CreateText(filePath))
        {
            streamWriter.WriteLine(svgString);
        }
    
        Process process = new Process();
        process.StartInfo.FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SvgHelper", "lib.svgToPng.exe");
        process.StartInfo.Arguments = filePath;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.CreateNoWindow = true;
        process.Start();
    
        // Read the base64String from StandardOutput
        string base64String = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
    
        // Clean up the file
        System.IO.File.Delete(filePath);
    }