[DajSięPoznać#14] Wydruki PDF w WebAPI: pdfmake i Edge.js

Wstęp

Hasło “dobre bo polskie” w IT obowiązuje jak najbardziej. Tym razem dwie biblioteki napisane przez Polaków właśnie: pdfmake i Edge.js. Pierwsza z nich służy do budowy pdfów, a z jej autroem, Barkiem, mam okazję na co dzień współpracować. Edge.js to kapitalne narzędzie do hostowania CLR w V8 (node.js) lub na odwrót. Mówi się o nim ostatnio nawet w kontekście Azure Functions (tt).

Definicja dokumentu PDF

Pdfmake jako definicję dokumentu przyjmuje obiekt Javascript. Kolejne sekcje dokumentu możemy dowolnie komponować z list, układów kolumnowych czy tabelarycznych. Biblioteka daje również sporą dowolność w definiowaniu styli do dokumentu z możliwością ich reużywania. Fragment kodu budującego definicję poniżej:

var dd = {
    content: [],
    styles: {
        header: {
            fontSize: 24,
            bold: true
        },
        content: {
            fontSize: 12
        },
        columns: {
            fontSize: 16
        }
    }
};

dd.content.push({
    text: advert.Title,
    style: 'header',
    alignment: 'center',
    margin: [0, 0, 0, 30]
});

var optionalColumnItems = [];

if (advert.NumberOfRooms)
    optionalColumnItems.push(createColumnItem("Liczba pokoi: ", advert.NumberOfRooms));

if (advert.Tier)
    optionalColumnItems.push(createColumnItem("Piętro: ", advert.Tier));

dd.content.push({
    columns: [
        [
            createColumnItem("Powierzchnia: ", advert.TotalPrice, " zł"),
            createColumnItem("Cena: ", advert.Area, " m2")
        ], optionalColumnItems
    ],
    style: 'columns'
});

 

Kod po stronie .NET

W moim przypadku proces V8 z node.js zostanie shostowany w CLR, dlatego rozpoczynamy od zainstalowania odpowiedniego NuGeta.

edge

Endpoint drukujący treść ogłoszenia dla danego id wygląda następująco:

member x.Get([<FromUri>]md5:string) =
    let func = Edge.Func(PrintController.PdfMakeCode)
    let advert = x.GetAdvertJson(md5)      
    let edgeResp = func.Invoke(advert) |> Async.AwaitTask |> Async.RunSynchronously
    let bytes = edgeResp :?> byte []
    let response = new HttpResponseMessage();
    response.Content <- new StreamContent(new MemoryStream(bytes))
    response.Content.Headers.Add("Content-type", "application/pdf")
    response

 

Przyjęty w Edge.js model komunikacji zakłada, że odpowiadają sobie następujące konstrukcje:

Func<obj, Task<obj>> : function(payload,callback)

Edge.Func przyjmuje string z kodem node’a i zwraca funkcję, która dla argumentu zwróci Taska z odpowiedzią.

Kod po stronie Node.js

Pdfmake zwraca Stream node’owy (dokument pdfkit). Założenia Edge.js są takie, że Stream należy przetransformować do typu Buffer i po przekazaniu do CLR będzie marshallowany do tablicy bajtów. I tu pojawiły się problemy, gdyż standardowy patent na tworzenie bufora ze streamu działał fajnie po wywołaniu w czystym node.js, ale zdarzenie “data” z jakichś przyczyn nie chciało się pojawić, gdy ten sam kod był wołany poprzez Edge.js.

Pytanie o przyczyny takiego zachowania na razie wisi bez odpowiedzi na stackoverflow, ale metodą prób i błędów doszedłem do działającego rozwiązania: stream z pdf-em jest pipe’owany do innego strumienia, takiego, na którym można wywołać flush.

var PdfPrinter = require('pdfmake');
var printer = new PdfPrinter(fonts);
var zlib = require('zlib');
var gzip = zlib.createGzip();

return function(data, cb){

    var printer = new PdfPrinter(fonts);
    var docDefinition = createDocumentDefinition(data);

    var pdfDoc = printer.createPdfKitDocument(docDefinition);

    var bufs = [];
    pdfDoc.on('data', function (d) { bufs.push(d); });
    pdfDoc.on('end', function(){
        var buf = Buffer.concat(bufs);
        cb(null, buf);
    });

    pdfDoc.pipe(gzip);
    pdfDoc.end();
    gzip.flush();
}

 

Rozwiązanie działa bez zarzutów. Warto pamiętać o tym, że jeżeli wypiszemy process.cwd z poziomu node (current working directory), to niezależnie od tego, gdzie leży plik .js, wypisze nam się directory procesu hosta, a więc na przykład folder IISExpress. Jest to o tyle ważne, że pdfmake przyjmuje zewnętrzną referencję do plików z fontami.

Advertisements

One thought on “[DajSięPoznać#14] Wydruki PDF w WebAPI: pdfmake i Edge.js

  1. Pingback: [DajSięPoznać] Podsumowanie | When the smoke is going down

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s