jorisschellekens / borb

borb is a library for reading, creating and manipulating PDF files in python.
https://borbpdf.com/
Other
3.4k stars 147 forks source link

Checkboxes are "checked" when pressing only one of them #121

Closed rogeralmengor closed 1 year ago

rogeralmengor commented 2 years ago

I add a series of checkboxes within the cells of table. The problem is: When I checked one, all of them got checked. Is there a way to make them independent to each other?

from re import A, I
from borb.pdf.document.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout
import geopandas as gpd
from borb.pdf.canvas.layout.table.fixed_column_width_table import FixedColumnWidthTable
from borb.pdf.canvas.layout.text.paragraph import Paragraph
from borb.pdf.canvas.layout.forms.text_field import TextField
from borb.pdf.canvas.color.color import HexColor
from decimal import Decimal
from borb.pdf.canvas.layout.layout_element import Alignment
from borb.pdf.canvas.layout.forms.drop_down_list import DropDownList
# New import(s)
from borb.pdf.pdf import PDF
import pandas as pd
import shapely
import folium
from shapely import wkt
from shapely.geometry import box 
import matplotlib.pyplot as plt
#from PIL import Image
from borb.pdf.canvas.layout.image.barcode import Barcode, BarcodeType  
from borb.pdf.canvas.layout.layout_element import LayoutElement
from borb.pdf.canvas.layout.table.flexible_column_width_table import FlexibleColumnWidthTable
from borb.pdf.canvas.layout.image.image import Image
import logging
import random 

layout.add(Paragraph("Stammdaten:", font="Helvetica-Bold", font_size=10))
layout.add(Paragraph(
        "Prüfbemerkungen Amt: ",
        font = "Helvetica-Bold", font_size=10, border_bottom=False,border_right=False,
        padding_bottom=Decimal(0.5),
        margin_bottom= Decimal(0.5,)))

    from borb.pdf.canvas.layout.forms.check_box import CheckBox
    from borb.pdf.canvas.layout.table.table import  TableCell

    table2: FixedColumnWidthTable = FixedColumnWidthTable(number_of_rows=2, number_of_columns=4)
    table2.add(Paragraph("bestätig  ", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.add(TableCell(CheckBox(font_color=HexColor("464E51"))))
    #table2.add(Paragraph("  ", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.add(Paragraph("Erfassung in profil c/s", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.add(CheckBox(font_color=HexColor("464E51")))
    #table2.add(Paragraph("  ", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.add(Paragraph("nicht bestätig  ", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.add(CheckBox(font_color=HexColor("464E51")))
    #table2.add(Paragraph("  ", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.add(Paragraph("Nachweiß:", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.add(CheckBox(font_color=HexColor("464E51")))
    #table2.add(Paragraph("  ", horizontal_alignment=Alignment.LEFT, font_color=HexColor("464E51"), font_size=8))
    table2.set_padding_on_all_cells(Decimal(5), Decimal(5), Decimal(5), Decimal(5))
    table2.no_borders()
    layout.add(table2) 

I expected one checkbox activated at the time I clicked on it.

Bug_checkbox
jorisschellekens commented 2 years ago

Hi,

I think your problem is related to a similar issue (recently solved). borb performs some optimisations when storing a PDF, it hashes every object, and whenever an object is stored (that has a known hash value) it just references the previously stored object.

That means the checkboxes get a reference to the same appearance dictionary, which is probably why you get this rendering issue.

The related issue was similar behaviour with a TextField.

Can you create a small, minimal example (two checkboxes on a page)? Then I can run my fix against your code.

rogeralmengor commented 2 years ago

Hi @jorisschellekens!

Thank you very much for your reply!, Below it's a small reproducible example with check boxes. I hope it suffice to check it out against your fix.

Kind Regards,

Roger

from borb.pdf.document.document import Document
from borb.pdf.page.page import Page
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout
from borb.pdf.canvas.layout.table.fixed_column_width_table import FixedColumnWidthTable
from borb.pdf.canvas.layout.text.paragraph import Paragraph
from borb.pdf.pdf import PDF
from borb.pdf.canvas.layout.forms.check_box import CheckBox 
from decimal import Decimal

# Create empty Document
pdf = Document()

page = Page()

pdf.add_page(page)

layout: PageLayout = SingleColumnLayout(page)

layout.add(Paragraph("Question: Do you own a pet?", font="Helvetica-Bold", font_size=8))

table: FixedColumnWidthTable = FixedColumnWidthTable(number_of_rows=2, number_of_columns=2)

table.add(Paragraph("Yes?"))
table.add(CheckBox())
table.add(Paragraph("No?"))
table.add(CheckBox())
table.no_borders()
table.set_padding_on_all_cells(Decimal(5), Decimal(5), Decimal(5), Decimal(5))

layout.add(table)

out_pdf_file_name = "checkbox_example.pdf"
with open(out_pdf_file_name, "wb") as out_file_handle:
    PDF.dumps(out_file_handle, pdf)
jorisschellekens commented 2 years ago

Can you verify whether this PDF displays the expected behaviour?

checkbox_example.pdf

Kind regards, Joris Schellekens

rogeralmengor commented 2 years ago

Hi,

I opened the pdf, and still both checkboxes are checked at the same time when I just click on one of them.

kbrown01 commented 2 years ago

Personally to me that code looks wrong. You are just adding doing:

table.add(CheckBox())

I would think you need to add at least a qualifier to what the field name is for the checkbox. Otherwise the code treats the name as 'None' and since they all have the same name, they all behave the same way. The code to me is:

    def __init__(
        self,
        font_size: Decimal = Decimal(12),
        font_color: Color = HexColor("000000"),
        field_name: typing.Optional[str] = None,
    ):

In other PDF tools you always associate a name with a field so it can be treated separately. And that code is not associating different names to fields (like with some GUID) if you do not enter a name.

Something like (untested):

table.add(Paragraph("Yes?")) table.add(CheckBox(field_name="checkbox_1_yes")) table.add(Paragraph("No?")) table.add(CheckBox(field_name="checkbox_1_no"))

jorisschellekens commented 2 years ago

If you do not enter a name, borb will auto-generate one (based on the number of fields on the Page).

I think the problem is something a bit deeper. You see, borb tries to optimize the PDF when it is being written. So whenever it detects some data that is duplicate, it writes it only once and the second time it uses a reference.

Although the PDF spec does not forbid it, this seems to be a problem for some PDF reader software. I remember a similar bug with a TextField.

I then added the option to tell the IO module that an object (regardless of its similarity) should be treated as being unique.

I guess (hope) this bug will be something similar.

That being said, I am going to rework some of the FormField logic, so that it fits better in the general LayoutElement framework. I will of course try to tackle this bug while I'm in that part of the code.

Kind regards, Joris Schellekens

kbrown01 commented 2 years ago

Well I could be wrong,I looked at that code too. It seems to generate a name that would be the same for everything on a page. The names must be unique.

jorisschellekens commented 1 year ago

I added a test to verify that 2 checkboxes do in fact get unique names. I output a PDF with 2 checkboxes and opened it in Adobe Reader on my phone (Adobe is not supported on Linux), and both checkboxes work independently from eachother. So that seems to work.