czwartek, 18 września 2014

Generowanie PDF z wykorzystaniem Nancy, Pechkin, wkhtmltopdf

Jednym z zadań, jakie dostałem było wygenerowanie raportu, który równocześnie miał się wyświetlać jako html, jak i pdf. Moją pierwszą myślą, było stworzenie tego raportu jako www, a nast. zamiana tego www w pdf. Dodatkowo, chciałem dać użytkownikowi, aby sam wybrał, czy rezultat raportu chce otrzymać jako html, czy jako pdf. Jako web frameworku wykorzystałem nancyfx.

Jako bibliotekę, która zamienia html na pdf, wykorzystałem darmową bibliotekę, dostarczoną przez google, czyli wkhtmltopdf. Ta biblioteka jest dużo lepsza, od konkurencyjnej itextsharp ponieważ itextsharp na chwilę obecną nie radzi sobie z bardziej skomplikowanymi html'ami (w moim przypadku był to brak chart'ów o których wspomniałem w moim poprzednim wpisie).

Aby korzystać płynnie z wkhtmltopdf w .NET, skorzystałem z darmowej biblioteki Pechkin (link do githuba). Przykład wykorzystania pechkin'a z wkhtmltopdf można znaleźć na githubie ale również np. na blogu Ulises Reyes.
Ulises po za kilkoma przykładami użycia, informuje nas również o kilku ważnych aspektach korzystania z Pechkina:

  • It's trivial, here are some gotchas though:
  • GIF Images are not supported
  • Paths to images must be fully qualified as URLs (http://example.com/a/b/image.jpg) or file URIs (file://host/path)
  • Resources over HTTPS cannot be fetched through URLs. You can get around this by specifying the path as a file URIs.
  • References CSS files are supported, I found it easier to write inline CSS (classes in the same document enclosed in a style tag)
Ok. Mamy bibliotekę (napisaną w C), do zamiany html'a w pdf. Mamy adapter, aby wykorzystać tą bibliotekę w .NET. Brakuje nam jeszcze odpowiedniego kodu, aby wykorzystać to w naszej ukochanej Nancy. Na szczęście, natknąłem się na podobny wpis na blogu Shane Mitchell link. Tam wprawdzie była wykorzystywana inna biblioteka, ale nic nie stało na przeszkodzie, aby wszystko połączyć w jedną całość i utworzyć klasę, generującą odpowiedni widok:


using Nancy;
using Nancy.Responses;
using Nancy.ViewEngines;
using Pechkin;
using Pechkin.Synchronized;
using System.Drawing.Printing;
using System.IO;

namespace Nancy.MVC
{
    public static class ResponseAsPDF
    {
        public static Response RenderToPdfPechkin(NancyModule module, string viewName, dynamic model)
        {
            string content = GetPageContent(module, viewName, model);
            SynchronizedPechkin sc = new SynchronizedPechkin(new GlobalConfig().SetMargins(new Margins(0, 0, 0, 0))
                .SetDocumentTitle("Ololo").SetCopyCount(1).SetImageQuality(100)
                .SetLosslessCompression(true).SetMaxImageDpi(-1).SetOutlineGeneration(true).SetOutputDpi(-1).SetPaperOrientation(true)
                .SetPaperSize(PaperKind.Letter));
         
            byte[] pdfBuf = sc.Convert(new ObjectConfig().SetLoadImages(true)
            .SetPrintBackground(true)
            .SetAllowLocalContent(true)
            .SetScreenMediaType(true)
            .SetRunJavascript(true)
            .SetCreateExternalLinks(true), content);
         
            return new StreamResponse(() =>
            {
                var pdfOutput = new MemoryStream(pdfBuf);
                pdfOutput.Seek(0, SeekOrigin.Begin);
                return pdfOutput;
            }, "application/pdf");
        }
        private static string GetPageContent(NancyModule module, string viewName, dynamic model)
        {
            ViewLocationContext viewLocationContext = new ViewLocationContext()
            {
                Context = module.Context,
                ModuleName = module.GetType().Name,
                ModulePath = module.ModulePath
            };
            var rendered = module.ViewFactory.RenderView(viewName, model, viewLocationContext);
            var content = "";
            using (var ms = new MemoryStream())
            {
                rendered.Contents.Invoke(ms);
                ms.Seek(0, SeekOrigin.Begin);
                using (TextReader reader = new StreamReader(ms))
                {
                    content = reader.ReadToEnd();
                }
            };
            return content;
        }
    }
}






Brak komentarzy:

Prześlij komentarz