DavBfr / dart_pdf

Pdf creation module for dart/flutter
https://pub.dev/packages/pdf
Apache License 2.0
1.42k stars 632 forks source link

Not support Unicode Khmer with "Coeng" sign (្) #1698

Open DyDev-1 opened 4 months ago

DyDev-1 commented 4 months ago

Describe the bug

When generating a PDF invoice using the pdf package, the Khmer script's "Khmer Sign Coeng" (្) is not rendering correctly with Khmer letters. This diacritical mark, essential for forming certain consonant clusters, fails to combine properly with the preceding and following Khmer characters, resulting in incorrect and unreadable text. Example Correct Rendering: បន្ទាត់ (a valid Khmer word) Incorrect Rendering: image (shows the issue with "Khmer Sign Coeng" not combining correctly)

To Reproduce Code snippet to reproduce the behavior:

  Future<String> generatePdfInvoice(PdfPageFormat format, List<Invoice> invoiceItems) async {
    final pdf = pw.Document();
    final img = await rootBundle.load('assets/images/header_invoice.png');
    final imageBytes = img.buffer.asUint8List();
    // final khmerFont = pw.Font.ttf(await rootBundle.load('assets/fonts/Khmer-OS-Siemreap-Regular.ttf'));
    final Uint8List fontData = base64Decode(khmerFontBase64);
    final khmerFont = pw.Font.ttf(fontData.buffer.asByteData());

    // Define the maximum number of items per page
    const int itemsPerPage = 33;

    // Split the invoice items into chunks of itemsPerPage
    List<List<Invoice>> chunks = [];
    for (var i = 0; i < invoiceItems.length; i += itemsPerPage) {
      chunks.add(invoiceItems.sublist(
        i,
        i + itemsPerPage > invoiceItems.length ? invoiceItems.length : i + itemsPerPage,
      ));
    }

    // Add a page for each chunk of invoice items
    for (var i = 0; i < chunks.length; i++) {
      final chunk = chunks[i];
      pdf.addPage(

        pw.MultiPage(
          theme: pw.ThemeData.withFont(
            base: khmerFont,
          ),
          pageFormat: PdfPageFormat.a4,
          build: (pw.Context context) {
            return <pw.Widget>[
              if (i == 0)
                pw.Container(
                  alignment: pw.Alignment.center,
                  child: pw.Image(pw.MemoryImage(imageBytes)),
                ),
              if (i == 0)
                pw.Container(
                  child: pw.Divider(
                    height: 1,
                    thickness: 1,
                    indent: 1,
                    endIndent: 0,
                    color: PdfColor.fromHex("#000000"),
                  ),
                ),
              pw.SizedBox(height: 20),
              if (i == 0)
                pw.Row(
                  children: [
                    pw.Container(
                      child: pw.Row(
                        children: [
                          pw.Column(
                              crossAxisAlignment: pw.CrossAxisAlignment.start,
                              children: [
                                pw.Text("Billed To", style: pw.TextStyle(color: PdfColor.fromHex("#5453C6"))),
                                pw.Text("Client Name : ${nameController.text.toString()}"),
                                pw.Text("Address : ${addressController.text.toString()}"),
                                pw.Text("Tel: ${telController.text.toString()}"),
                              ]
                          ),
                          pw.SizedBox(width: 80), // Add horizontal space between text elements
                          pw.Column(
                              mainAxisAlignment: pw.MainAxisAlignment.center,
                              children: [
                                pw.Text("Date Issued", style: pw.TextStyle(color: PdfColor.fromHex("#5453C6"))),
                                pw.Text("${dateController.text.toString()}"),
                              ]
                          ),
                          pw.SizedBox(width: 15),
                          pw.Column(
                              mainAxisAlignment: pw.MainAxisAlignment.center,
                              children: [
                                pw.Text("Invoice Number", style: pw.TextStyle(color: PdfColor.fromHex("#5453C6"))),
                                pw.Text("${numController.text.toString()}"),
                              ]
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              pw.SizedBox(height: 20),

              // Table construction using pw.Table.fromTextArray
              pw.Table.fromTextArray(
                border: null,
                cellAlignment: pw.Alignment.center,
                headerDecoration: pw.BoxDecoration(color: PdfColors.grey300),
                headerHeight: 30,
                cellHeight: 30,
                headerStyle: pw.TextStyle(fontWeight: pw.FontWeight.bold, font: khmerFont),

                headers: ['No', 'Description', 'Qty', 'Unit Price', 'Amount'],
                data: List<List<dynamic>>.generate(
                  chunk.length,
                      (index) => [
                    '${index + 1}',
                    chunk[index].description,
                    chunk[index].qty,
                    pw.Text(
                      '${chunk[index].price} ${chunk[index].cur == "R" ? "\R" : "\$"}',
                      style: pw.TextStyle(fontSize: 12),
                    ),
                    '${chunk[index].qty * chunk[index].price} ${chunk[index].cur == "R" ? "\R" : "\$"}',
                  ],
                ),
              ),

              pw.SizedBox(height: 20),
              if (i == chunks.length - 1)
                pw.Row(
                    mainAxisAlignment: pw.MainAxisAlignment.end,
                    children: [
                      pw.Column(
                        mainAxisAlignment: pw.MainAxisAlignment.end,
                        children: [
                          pw.Text('Total Amount Riel: ${totalRController.text} R',
                              style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                          pw.Text('Total Amount USD : ${totalUController.text} \$',
                              style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                          pw.Text('Total Due Amount Riel : ${totalDueRController.text} R',
                              style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                          pw.Text('Total Due Amount USD : ${totalDueUController.text} \$',
                              style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                        ],
                      )
                    ]
                )

            ];
          },
        ),
      );
    }

    final output = await getTemporaryDirectory();
    final invoice_num =  numController.text;
    final file = File("${output.path}/${invoice_num.toString()}.pdf");
    await file.writeAsBytes(await pdf.save());
    return file.path;
  }

Expected behavior

The "Khmer Sign Coeng" (្) should combine seamlessly with preceding and following Khmer letters to form valid consonant clusters, ensuring that the text is rendered correctly and is readable.

Screenshots

image

Flutter Doctor

Desktop (please complete the following information):

Smartphone (please complete the following information):

Additional context

Samnangsokhorn commented 2 weeks ago

I'm facing the same problem b, not yet solved. Have you had solution for this issue?

DyDev-1 commented 2 weeks ago

I'm facing the same problem b, not yet solved. Have you had solution for this issue?

I'm switch to using a HTMLPDF package

Samnangsokhorn commented 2 weeks ago

Screenshot 2567-11-07 at 3 33 06 in the afternoon This package?

DyDev-1 commented 2 weeks ago

Screenshot 2567-11-07 at 3 33 06 in the afternoon This package?

yes right.