Czech Diacritics In PDF Generation: A Base44 Issue

by Editorial Team 51 views
Iklan Headers

Hey guys! So, we're running into a bit of a snag with generating PDFs that include Czech characters (you know, those letters with the little hats and hooks – ě, š, č, ř, ž, ý, á, í, é, ů, ú, ď, ť, ň) while using Deno on the Base44 platform. It's proving to be quite the headache, and we're hoping to find a solution that works smoothly.

Context

Essentially, we're trying to automatically generate contracts with Czech text using a Deno backend function hosted on Base44. The problem? The Czech diacritics aren't rendering correctly in the generated PDFs. It's like they're allergic to the PDF format or something!

Attempts and Results

We've tried a few different PDF generation libraries, each with its own set of quirks and disappointments. Here's a rundown of what we've tried and what went wrong:

1. jsPDF (First attempt)

We started with jsPDF, thinking it would be a straightforward solution. Boy, were we wrong!

  • Issue #1: Instead of those lovely Czech diacritics, we got a bunch of random, garbled characters. It looked like the PDF was speaking in tongues!
  • Issue #2: The document format structure was a complete mess. It didn't respect the layout at all.
  • Issue #3: The layout calibration was totally off. Text was overlapping, and the spacing was all wonky. It was a design disaster!

Root cause: It seems like jsPDF in a Deno environment struggles with proper UTF-8 and Czech font support. It's like trying to fit a square peg into a round hole.

2. pdf-lib (Second attempt)

Next up, we tried pdf-lib, hoping for a better outcome. Sadly, it was more of the same.

  • Issue: We encountered similar problems to jsPDF. The diacritics either didn't show up at all or were replaced with those dreaded replacement characters (you know, the question marks in boxes).

3. pdfmake (Multiple attempts with different configurations)

We then turned to pdfmake, thinking that with the right configuration, we could get it to work. We tried a few different approaches, but no luck.

Attempt A: Standard Roboto fonts with vfs_fonts

import PdfPrinter from 'npm:pdfmake@0.2.7';
import { default as pdfFonts } from 'npm:pdfmake/build/vfs_fonts.js';

PdfPrinter.vfs = pdfFonts.pdfMake.vfs;
const fonts = {
  Roboto: {
    normal: 'Roboto',
    bold: 'Roboto-Medium',
    italics: 'Roboto-Italic',
    bolditalics: 'Roboto-MediumItalic'
  }
};

Result: The PDF generated, but it was completely blank! Not a single character appeared. It was as if the text had vanished into thin air.

Note: We even added a buffer polyfill for Deno since pdfmake expects Node.js Buffer. But even with that, the issue persisted. It felt like we were chasing ghosts.

Attempt B: Helvetica fallback (current code)

const fonts = {
  Roboto: {
    normal: 'Helvetica',
    bold: 'Helvetica-Bold',
    italics: 'Helvetica-Oblique',
    bolditalics: 'Helvetica-BoldOblique'
  }
};

Result: Same issue. The PDF generates, but all the text is missing or blank. It's like the PDF is playing hide-and-seek with us, and we're losing.

Required Document Content

To give you an idea of what we're trying to generate, here's a snippet of the Czech text that needs to be included in the contract:

  • Headers: "Dohoda o provedení práce podle §75 zák.262/2006 Sb ZP"
  • Fields: "Datum narození", "Rodné číslo", "Bytem"
  • Sections: "Čl. I. Pracovní úkol", "Čl. II. Doba provedení, rozsah a odměna"
  • Multiple paragraphs with Czech characters: "zaměstnavatel", "zaměstnanec", "měsíčně", "změny", "účely", "práce", etc.

Technical Environment

Here's a rundown of our setup:

  • Platform: Base44 / Deno Deploy
  • Handler: Deno.serve
  • Base44 SDK: npm:@base44/sdk@0.8.6
  • Tested libraries:
    • npm:jspdf@2.5.2 ❌
    • npm:pdf-lib ❌
    • npm:pdfmake@0.2.7 ❌ (blank output)

Questions for Base44 Team

So, we're really scratching our heads here. We're hoping the Base44 team can shed some light on this issue.

  1. Recommended Solution: What's the recommended way to generate PDFs with full Czech/UTF-8 character support in Deno on the Base44 platform? Are there specific libraries or configurations that you've found to work well?
  2. pdfmake Issue: Is there a known issue with pdfmake in the Deno Deploy environment? It seems like it's not playing nicely with Base44.
  3. Workarounds/Libraries: Are there any Base44-specific workarounds or recommended libraries for internationalized PDF generation? We're open to trying different approaches.
  4. Alternative Approach: Should we consider an alternative approach, like HTML-to-PDF conversion using a headless browser? It feels like a bit of a workaround, but if it gets the job done, we're willing to explore it.

Current Function Code

Here's the code we're currently using to generate the PDFs. Maybe you can spot something we're missing:

import { createClientFromRequest } from 'npm:@base44/sdk@0.8.6';
import PdfPrinter from 'npm:pdfmake@0.2.7';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    const { formData } = await req.json();

    if (!formData) {
      return Response.json({ error: 'Form data is required' }, { status: 400 });
    }

    const dateFrom = formatDate(formData.pracovniOd);
    const dateTo = formatDate(formData.pracovniDo);
    const today = new Date();
    const dateStr = `${today.getDate()}. ${today.getMonth() + 1}. ${today.getFullYear()}`;

    // Define fonts for pdfmake (using standard fonts with Unicode support)
    const fonts = {
      Roboto: {
        normal: 'Helvetica',
        bold: 'Helvetica-Bold',
        italics: 'Helvetica-Oblique',
        bolditalics: 'Helvetica-BoldOblique'
      }
    };

    const printer = new PdfPrinter(fonts);

    // Define document structure with Czech text
    const docDefinition = {
      content: [
        {
          text: 'Dohoda o provedení práce',
          style: 'header',
          alignment: 'center',
          margin: [0, 0, 0, 5]
        },
        {
          text: 'podle §75 zák.262/2006 Sb ZP',
          alignment: 'center',
          fontSize: 10,
          margin: [0, 0, 0, 15]
        },
        {
          text: 'Pracovník:',
          style: 'subheader',
          margin: [0, 5, 0, 5]
        },
        {
          text: formData.potvrzeneJmeno || '',
          margin: [0, 0, 0, 3]
        },
        {
          text: `Datum narození: ${formatDate(formData.datumNarození)}`,
          margin: [0, 0, 0, 3]
        },
        {
          text: `Rodné číslo: ${formData.rodnecislo || ''}`,
          margin: [0, 0, 0, 3]
        },
        {
          text: `Bytem: ${formData.bytem || ''}`,
          margin: [0, 0, 0, 10]
        },
        {
          text: 'a',
          alignment: 'center',
          margin: [0, 5, 0, 5]
        },
        {
          text: 'Zaměstnavatel: Foodway Catering s.r.o.',
          style: 'subheader',
          margin: [0, 5, 0, 5]
        },
        {
          text: 'Říční 539/2',
          margin: [0, 0, 0, 3]
        },
        {
          text: 'Praha 1, 118 00',
          margin: [0, 0, 0, 3]
        },
        {
          text: 'IČO: 074244488',
          margin: [0, 0, 0, 3]
        },
        {
          text: 'DIČ: CZ07424488',
          margin: [0, 0, 0, 10]
        },
        {
          text: 'Čl. I. Pracovní úkol',
          style: 'subheader',
          margin: [0, 5, 0, 5]
        },
        {
          text: 'Pracovník bude vykonávat práci na pozici obsluha a další úkoly týkající se provozu se zaměstnavatelem.',
          margin: [0, 0, 0, 10]
        },
        {
          text: 'Čl. II. Doba provedení, rozsah a odměna',
          style: 'subheader',
          margin: [0, 5, 0, 5]
        },
        {
          text: `Pracovní úkol bude prováděn v době od ${dateFrom} do ${dateTo}. Odborný odhad práce je cca 20 hod./měsíčně, ale v maximálním rozsahu do 300 hod za rok.`,
          margin: [0, 0, 0, 5]
        },
        {
          text: 'Odměna bude činit 170 Kč/hod a další odměny budou stanoveny dle výkonnosti zaměstnance. Odměna bude vyplacena do 15. dne následujícího měsíce zaměstnanci hotově na provozovně Říční 539/2, Praha 1 a bude zdaněna podle platných právních předpisů.',
          margin: [0, 0, 0, 10]
        },
        {
          text: 'Čl. III. Ostatní ujednání',
          style: 'subheader',
          margin: [0, 5, 0, 5]
        },
        {
          text: 'Ostatní práva a povinnosti smluvních stran se řídí ustanoveními Zákoníku práce a předpisů jej provádějících. Zaměstnanec se zavazuje zachovávat mlčenlivost o skutečnostech, které se při výkonu pracovních činností dozví. Výpovědní lhůta činí tři dny od podání výpovědi.',
          margin: [0, 0, 0, 5]
        },
        {
          text: 'Smlouva je sepsána ve dvou vyhotoveních, z nichž jedno obdrží pracovník a druhé zaměstnavatel. Veškeré změny této smlouvy je možno provést pouze písemně.',
          margin: [0, 0, 0, 10]
        },
        {
          text: 'Čl. IV. Souhlas s pořizováním záznamů',
          style: 'subheader',
          margin: [0, 5, 0, 5]
        },
        {
          text: 'Smluvní strana souhlasí s pořizováním a užitím obrazových a zvukových záznamů (fotografie, video) zachycujících její osobu při výkonu činnosti nebo v souvislosti s akcemi společnosti Foodway Catering s.r.o., a to pro interní, marketingové a propagační účely, v souladu s platnými právními předpisy o ochraně osobních údajů (GDPR). Souhlas je udělen bez nároku na odměnu a může být kdykoliv odvolán písemně přes email hr@foodwaycatering.cz',
          margin: [0, 0, 0, 20]
        },
        {
          columns: [
            {
              width: '*',
              stack: [
                {
                  canvas: [
                    {
                      type: 'line',
                      x1: 30,
                      y1: 0,
                      x2: 130,
                      y2: 0,
                      lineWidth: 0.5
                    }
                  ]
                },
                {
                  text: 'Zaměstnavatel',
                  fontSize: 10,
                  alignment: 'center',
                  margin: [0, 5, 0, 0]
                }
              ]
            },
            {
              width: '*',
              stack: [
                {
                  canvas: [
                    {
                      type: 'line',
                      x1: 30,
                      y1: 0,
                      x2: 130,
                      y2: 0,
                      lineWidth: 0.5
                    }
                  ]
                },
                {
                  text: 'Zaměstnanec',
                  fontSize: 10,
                  alignment: 'center',
                  margin: [0, 5, 0, 0]
                }
              ]
            }
          ],
          margin: [0, 10, 0, 15]
        },
        {
          text: `V Praze dne ${dateStr}`,
          alignment: 'center',
          fontSize: 9,
          margin: [0, 10, 0, 5]
        },
        {
          text: `Digitálně podepsáno: ${formData.potvrzeneJmeno}`,
          alignment: 'center',
          fontSize: 9
        }
      ],
      styles: {
        header: {
          fontSize: 16,
          bold: true
        },
        subheader: {
          fontSize: 11,
          bold: true
        }
      },
      defaultStyle: {
        fontSize: 11,
        font: 'Roboto'
      },
      pageMargins: [40, 40, 40, 40]
    };

    const pdfDoc = printer.createPdfKitDocument(docDefinition);
    
    const chunks = [];
    pdfDoc.on('data', (chunk) => chunks.push(chunk));
    
    await new Promise((resolve, reject) => {
      pdfDoc.on('end', resolve);
      pdfDoc.on('error', reject);
      pdfDoc.end();
    });

    // Concatenate chunks manually without Buffer
    const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
    const pdfBytes = new Uint8Array(totalLength);
    let offset = 0;
    for (const chunk of chunks) {
      pdfBytes.set(chunk, offset);
      offset += chunk.length;
    }

    return new Response(pdfBytes, {
      status: 200,
      headers: {
        'Content-Type': 'application/pdf',
        'Content-Disposition': 'attachment; filename=smlouva_DPP.pdf'
      }
    });
  } catch (error) {
    console.error('Error generating PDF:', error);
    return Response.json({ error: error.message }, { status: 500 });
  }
});

function formatDate(dateString) {
  if (!dateString) return '';
  try {
    const date = new Date(dateString);
    const day = date.getDate();
    const month = date.getMonth() + 1;
    const year = date.getFullYear();
    return `${day}. ${month}. ${year}`;
  } catch {
    return dateString;
  }
}

Any guidance or recommendations would be greatly appreciated! This is blocking our contract generation workflow.

We're really hoping to get this sorted out soon. Any help or suggestions you can offer would be amazing! Thanks in advance for your time and expertise!