简体   繁体   中英

adding a google visualization areachart svg to a pdf using ITextSharp

Am in the process of creating a printer friendly pdf version of a webpage and have run into some difficulties trying to add a Google areachart too the pdf.

the areachart is generated using JavaScript and rendered as an svg on the page, I have tried a number of different approaches for adding the chart including importing the html for the div that contains the chart (so far all I get using this approach is the title for the chart and not the actual chart), I have looked into converting the svg to an image format (png, jpeg, gif etc) and then saving the image and adding to pdf as image.

Does anyone have any ideas on how I could implement this?

Any advice would be very much appreciated.

Using jQuery and https://github.com/vvvv/SVG I assume you're using MVC

Javascript:

$("#makePdf").click(function (e) {
    $.ajax({
        type: 'POST',
        url: '/GeneratePdf',
        dataType: 'json',
        data: JSON.stringify({ svgXml: $("<div />").append($("#chart svg").clone()).html() }),
        contentType: 'application/json;charset=utf-8',
        success: function (data) {
            window.open('/GetPdf?pdfId=' + data.pdfId, '_blank');
        }
    });
    e.preventDefault();
});

$("<div />").append($("#chart svg").clone()).html() is needed because there is no way to get the xml source of a current element, this is the most elegant way I could find to do it, append it to a new div and get the html of its contents.

C#:

using System;
using System.IO;
using System.Drawing.Imaging;
using iTextSharp.text;
using iTextSharp.text.pdf;
using Svg;

// ...

public static MemoryStream PngFromSvg(string svgXml)
{
    MemoryStream pngStream = new MemoryStream();
    if (!string.IsNullOrWhiteSpace(svgXml))
    {
        byte[] byteArray = Encoding.ASCII.GetBytes(svgXml);
        using (MemoryStream stream = new MemoryStream(byteArray))
        {
            SvgDocument svgDocument = SvgDocument.Open(stream);
            System.Drawing.Bitmap bitmap = svgDocument.Draw();
            bitmap.Save(pngStream, System.Drawing.Imaging.ImageFormat.Png);
        }
    }
}

public static MemoryStream GeneratePdf(MemoryStream pngStream)
{
    MemoryStream pdfStream = new MemoryStream();

    using (Document document = new Document(PageSize.A4, 20, 20, 20, 20))
    {
        PdfWriter pdfWriter = PdfWriter.GetInstance(document, pdfStream);

        // ...
        Image chartImage = Image.GetInstance(System.Drawing.Image.FromStream(pngStream), ImageFormat.Png);
        PdfPCell chartCell = new PdfPCell(chartImage, true);
        chartCell.Border = Rectangle.NO_BORDER;
        //...

        document.Close();
        pdfWriter.Close();
    }

    return pdfStream;
}

public class SvgData
{
    public string svgXml { get; set; }
}
public class PdfData
{
    public string pdfId { get; set; }
}

public JsonResult GeneratePdf(SvgData data)
{
    byte[] pdfBytes = GeneratePdf(PngFromSvg(data.svgXml)).ToArray();

    // ...
    // Either:
    //  :: Store PDF in database as blob and return unique ID
    //  :: Store as file and return path to file

    return Json(new PdfData{pdfId = pdf.id}, JsonRequestBehavior.AllowGet);
}

public ActionResult GetPdf(Int64 pdfId)
{   
    // retrieve pdf from database
    byte[] pdfBytes = pdf.raw;

    return File(pdfBytes, "application/pdf");
}

This code is awful, but should be self explanatory. This has given me a headache for the past 2 weeks, so I know your pain in trying to find a solution that "works". Sending the svg data to be parsed, in my opinion, is BAD! But I can think of no other way of doing it other than emulating a web browser's rendering engine!!

NB: Fonts will may render correctly when their styles are set in the style attribute, you shall need to parse these and set the fonts using the font- attributes eg

$chartSvg.find("text").each(function () {
    var $this = $(this);
    $this.attr("font-size", $this.css("font-size"));
    $this.css("font-size", "");

    $this.attr("font-style", $this.css("font-style"));
    $this.css("font-style", "");

    $this.attr("font-variant", $this.css("font-variant"));
    $this.css("font-variant", "");

    $this.attr("font-weight", $this.css("font-weight"));
    $this.css("font-weight", "");

    $this.attr("font-family", $this.css("font-family"));
    $this.css("font-family", "");
});

The quality of the png may be poor so what I did was scaled the svg to twice the size then sent it to the server:

var $chartSvg = $("#chart").find("svg");
var $chartSvgClone = $chartSvg.clone().attr("height", $chartSvg.height() * 2).attr("width", $chartSvg.width() * 2).css("transform", "scale(2)");
var $svgXml = $("<div />").append($chartSvgClone).html();
// ...

And another caveat, the svg library doesn't seem to set the font family correctly, so in your C# code pass your SvgDocument this:

public void SetFonts(SvgElement parent)
{
    try
    {
        SvgText svgText = (SvgText)parent;
        svgText.Font = new System.Drawing.Font("Arial", svgText.FontSize.Value);
    }
    catch
    {
    }

    if(parent.HasChildren())
    {
        foreach(SvgElement child in parent.Children)
        {
            SetFonts(child);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM