Dziwny błąd w bibliotece FPDF

Wątek przeniesiony 2024-05-06 15:43 z PHP przez cerrato.

0

Dzień dobry,

Jeden z praktykantów miał za zadanie stworzyć raport błędów generujący listę obiektów QGIS w tablicy.
Zrobił to i na jego komputerze wychodziło bardzo dobrze.

Teraz mam z zadanie wrzucić go do głównego projektu.

Kod wygląda tak:

class PDF(FPDF): # stopka z numerem strony
    def footer(self):
        self.set_y(-15)
        self.cell(0, 10, f'Strona {self.page_no()}', 0, 0, 'C')
    def create_pdf(filename):
        czas = time.strftime("%Y-%m-%d %H:%M")
        pdf = PDF()
        walidator_sciezka = os.path.dirname(os.path.realpath(__file__))
        pdf.add_page()
        pdf.alias_nb_pages()
        pdf.set_margins(10,10,10)
        
        pdf.add_font('Verdana', '', walidator_sciezka + "/fpdf\Verdana.ttf", uni = True)
        pdf.add_font("Verdana", "B", walidator_sciezka + "/fpdf\Verdana-bold.ttf", uni = True)
        pdf.set_font('Verdana', 'B',14)
        pdf.cell(0,5,ln = 1)
        pdf.cell(0,0, "Raport z kontroli", align='C',ln=2)
        pdf.set_font("Verdana", "", 8)
        pdf.cell(0,5,ln = 1)
        pdf.cell(100,0, "wynik kontroli:", align = "R", ln = 0)
        print ('ustalony font i komórki')
        if len(bledyKontroli["KLASA"]) == 0:
            wynikkontroli = "Pozytywny"
            pdf.set_text_color(0,255,0)

        else:
            wynikkontroli = "Negatywny"
            pdf.set_text_color(255,0,0)
        lightblue = (143, 216, 255)
        gray = (220,220,220)
        pdf.cell(100,0,wynikkontroli, align = "L")
        pdf.cell(0,5,ln = 1)
        pdf.set_text_color(0,0,0)
        pdf.cell(100,0, "wynik walidacji:", align = "R", ln = 0)
        print ('ustalony font i komórki 2')
        if len(bledyWalidacji["WIERSZ"]) == 0:
            wynikw = "Pozytywny"
            pdf.set_text_color(0,255,0)
        else:
            wynikw = "Negatywny"
            pdf.set_text_color(255,0,0)
        pdf.cell(100,0,wynikw, align = "L")
        pdf.cell(0,5, ln = 1) # wolne miejsce
        pdf.set_text_color(0,0,0)
        pdf.cell(100,0,"data kontroli:", ln=0, align = "R") # tekst
        pdf.cell(100,0, czas , ln = 1, align = "L")
        pdf.cell(0,1,ln = 1)
        print ("ustawienie komórek")
        with pdf.table(first_row_as_headings = False, col_widths = (105,95), borders_layout = "NONE", v_align = "TOP") as tabelaskp:
            rzad = tabelaskp.row()
            rzad.cell("szablon kontroli:", align = "R", padding=(1,1,1,1))
            rzad.cell("skp", align = "J", padding = (1,40,1,1))
        pdf.cell(0,1,ln=1)
        
        # tabela walidacji
        if len(bledyWalidacji["WIERSZ"]) > 0:
            pdf.set_font("Verdana", "B", 14)
            pdf.cell(0,0,"Tabela z błędami walidacji", align = "C")
            pdf.set_font("Verdana",  "", 8)
            pdf.cell(0,5,ln = 1)
            pdf.set_fill_color(lightblue)
            with pdf.table(col_widths=(40,12,40,88), text_align = "J",padding = (1)) as table:

                pdf.set_font("Verdana", "", 5)
                headings = table.row()
                
                headings.cell("WALIDOWANY PLIK")
                headings.cell("WIERSZ")
                headings.cell("OPIS BŁĘDU")
                headings.cell("KOMUNIKAT BŁĘDU")
                pdf.set_fill_color(gray)
            with pdf.table(datawalidacja, col_widths=(40,12,40,88), first_row_as_headings = False, text_align = "J", padding = (1)):
                pass
            pdf.set_fill_color(255,255,255)
            pdf.set_font("Verdana", "" , 6)
        else: # jak nie ma błędów walidacji
            pdf.set_font("Verdana", "", 14)
            pdf.cell(0,0, "Nie znaleziono błędów walidacji", align = "C")
        
        pdf.cell(w=0,h=10,txt = " ",ln=1)
        # tabela konotroli
        if len(bledyKontroli["KLASA"]) > 0:
            pdf.set_font("Verdana", "B", 14)
            pdf.cell(0,0,"Tabela z błędami kontroli", align="C")
            pdf.set_font("Verdana", "", 5)
            pdf.cell(0,5,ln = 1)
            pdf.set_fill_color(lightblue)
            with pdf.table(col_widths=(40,20,50,70), text_align = "J", padding = (1)) as table1:
                headings = table1.row()
                headings.cell("KONTROLOWANY PLIK")
                headings.cell("KLASA")
                headings.cell("LOKALNY ID")
                headings.cell("KOMUNIKAT BŁĘDU")
                pdf.set_fill_color(gray)
            with pdf.table(datakontrola, col_widths=(40,20,50,70), first_row_as_headings = False, text_align="J", padding = (1)):
                pass
        else:
            pdf.set_font("Verdana", "B", 14)
            pdf.cell(0,0,"Nie znaleziono błędów kontroli", align = "C")
            pdf.set_font("Verdana", "", 5)

        pdf.output(filename)
    create_pdf(pdfpath)

jednakże w ostatniej linijce wyskakuje mi błąd odnoszący się do... jednego z wewnętrznych plików biblioteki fpdf.

Pełny błąd jest taki:

File "[pelna sciezka]", line 473, in walidacjaIKontrolaAtrybutow -> nazwa głównego pliku
    create_pdf(pdfpath)
    File "[pelna sciezka]\walidatorPlikowGML.py", line 472, in create_pdf
    pdf.output(filename)
    File "[pelna sciezka]\Walidator_plikow_gml\fpdf\fpdf.py", line 5014, in output
    self.buffer = output_producer.bufferize()
    File "[pelna sciezka]\Walidator_plikow_gml\fpdf\output.py", line 380, in bufferize
    font_objs_per_index = self._add_fonts()
    File "[pelna sciezka]\Walidator_plikow_gml\fpdf\output.py", line 549, in _add_fonts
    print(f"Font name for TTF: {font.name}") 
   AttributeError: name

TTF pochodzi z linii: pdf.add_font('Verdana', '', walidator_sciezka + "/fpdf\Verdana.ttf", uni = True) jest to oddzielny plik przechowujący czcionkę Verdana ( walidator_sciezka to zmienna przechowująca ścieżkę do folderu). Jak sprawdziłem, printami że atrybut name jest możliwy dla zmiennej font w pliku output.py (kod podam na końcu), ale się nie uzupełnia.

... i w ogóle czy warto zmieniać/wchodzić w głąb biblioteki fpdf, zamiast obejść dookoła?

Składania output.py jest taka:

 def _add_fonts(self):
        font_objs_per_index = {}
        for font in sorted(self.fpdf.fonts.values(), key=lambda font: font.i):
            # Standard font
            print("Dostępne atrybuty dla font:", dir(font), 'wartosci',self.fpdf.fonts.values(),'fonty' ,self.fpdf.fonts) # dir wykazał że metoda .name jest możliwa,m zaś kolejne dwie wartości wyglądają tak: wartosci dict_values([TTFFont(i=1, fontkey=verdana), TTFFont(i=2, fontkey=verdanaB)]) fonty {'verdana': TTFFont(i=1, fontkey=verdana), 'verdanaB': TTFFont(i=2, fontkey=verdanaB)}
            if font.type == "core":
                encoding = (
                    "WinAnsiEncoding"
                    if font.name not in ("Symbol", "ZapfDingbats")
                    else None
                )
                core_font_obj = PDFFont(
                    subtype="Type1", base_font=font.name, encoding=encoding
                )

                self._add_pdf_obj(core_font_obj, "fonts")
                font_objs_per_index[font.i] = core_font_obj
            elif font.type == "TTF":
                    fontname = f"MPDFAA+{font.name}" 

Oczywiście trzeba znać fpdf, aby znaleźć źródło problemów.

Z góry dziękuje za każdą pomoc!

2

Ja bym sprawdził dokładnie jakie pakiety i wersje są na maszynie na której działa

0

To jest najnowsza: 2.7.8. z lutego br. ChaGPT z fpdf nie pomoże, bo ma wgraną wersję 1.7.2. i starsze Co do maszyny... nie wiem czy jest jeszcze dostępna, bo praktykant po miesiącu skończył swoją robotę, a jego stanowisko albo zostało zdemontowane, albo przekazane na inne obliczenia.

Tak, wiem, powinien nam krok po kroku przekazać ostatniego dnia co i jak po kolei zrobił, byśmy mogli zrobić przekazać nam, jak mu to wyszło...

1

dlaczego "/fpdf\Verdana.ttf" są / i \ ?

0
Karol Śpila napisał(a):

To jest najnowsza: 2.7.8. z lutego br. ChaGPT z fpdf nie pomoże, bo ma wgraną wersję 1.7.2. i starsze Co do maszyny... nie wiem czy jest jeszcze dostępna, bo praktykant po miesiącu skończył swoją robotę, a jego stanowisko albo zostało zdemontowane, albo przekazane na inne obliczenia.

Tak, wiem, powinien nam krok po kroku przekazać ostatniego dnia co i jak po kolei zrobił, byśmy mogli zrobić przekazać nam, jak mu to wyszło...

A jakiś github pozostał?

0

Raczej nie..., ale cała biblioteka fdpf jes tutaj: https://github.com/py-pdf/fpdf2/tree/master

2
Miang napisał(a):

dlaczego "/fpdf\Verdana.ttf" są / i \ ?

jak wyżej, najpierw spróbuj zamienić \ na /, do tego sprawdź wielkość liter w nazwach plików. Mogło działać na windowsie a nie działa na linuksie

0

Ja też pracuje na Windowsie, więc SO nie ma znaczenia

0

próbowałem odtworzyć ten błąd (no ale w żadnym przypadku mi nie wyskoczył podobny), wyskakuje mi tylko, że nie może znaleźć czcionki. Napisze tylko co ja sprawdzałem.

jak nie znalazło czcionki to wyskakuje inny błąd

PS C:\Users\Darek\PycharmProjects\font project> python skrypt.py
C:\Users\Darek\PycharmProjects\font project
.
Traceback (most recent call last):
  File "skrypt.py", line 98, in <module>
    create_pdf("core_fonts.pdf")
  File "skrypt.py", line 71, in create_pdf
    pdf.add_font("Verdana", "", str(font_dir / "verdanab.ttf"))
  File "C:\Users\Darek\AppData\Local\Programs\Python\Python37\lib\site-packages\fpdf\fpdf.py", line 1840, in add_font
    raise FileNotFoundError(f"TTF Font file not found: {fname}")
FileNotFoundError: TTF Font file not found: fonts\verdanab.ttf

No to poszedłem w tym kierunku co sugeruje @lion137

w kodzie tym co jest def _add_fonts(self): uzyta jest biblioteka fonttools ponieważ w dużym uproszczeniu:

font.name - prowadzi do kodu

for font in sorted(self.fpdf.fonts.values(), key=lambda font: font.i): - co prowadzi do kodu

.fpdf.fonts.values() - co prowadzi do kodu

self.name = re.sub("[ ()]", "", self.ttfont["name"].getBestFullName()) - co co prowadzi do kodu

self.ttfont = ttLib.TTFont( self.ttffile, recalcTimestamp=False, fontNumber=0, lazy=True ) - co prowadzi do kodu

from fontTools import ttLib - czyli użyta jest biblioteka fontTools

jak zainstalowałem najnowsza wersje fpdf2 2.7.8 to na pythonie 3.7 nie działa- sprawdzlem wersje fpdf2==2.2.0 to ta wersja działa

trzeba było obniżyć wersja dla biblioteki fonttols żeby obsługiwała python 3.7

przy okazji jak zwykle antywirus mi przeniósł tworzony plik do izolacji :-D

ponizsze fpdf2 2.7.8 działa na pythonie 3.10 z ponizszymi wersjami bibliotek
defusedxml==0.7.1
fonttools==4.51.0
fpdf2==2.7.8
pillow==10.2.0

wypis bibliotek dla fpdf2 2.7.8

supported version:
fonttools v4.51.0
Python :: 3.8
Python :: 3.9
Python :: 3.10
Python :: 3.11
Python :: 3.12

fpdf2 v2.7.8
Python :: 3.7
Python :: 3.8
Python :: 3.9
Python :: 3.10
Python :: 3.11
Python :: 3.12

defusedxml 0.7.1
Python :: 2
Python :: 2.7
Python :: 3
Python :: 3.5
Python :: 3.6
Python :: 3.7
Python :: 3.8
Python :: 3.9

pillow 10.3.0
Python :: 3.8
Python :: 3.9
Python :: 3.10
Python :: 3.11
Python :: 3.12

a jaka wersja pythona jest na docelowej maszynie? Moze wystarczy obniżyć wersje fpdf2 do takiej co obsłuzy pythona na do celowej maszynie, z tego co czytałem ostatnia wersja qgis operuje na python 3.9, może na maszynie była starsza wersja qgis co miała inna wersje pythona

0

Jak się okazało później... plik działa, tylko QGIS blokuje jego otwieranie. Adobe wyświetla mi wiadomość: "Ten plik jest już otwarty lub używany przez inną aplikację". Dopiero zamknięcie QGIS-a pozwala mi na otwarcie utworzonego tym kodem pliku PDF AI zaproponowało mi taki sposób sprawdzenia:

if os.path.isfile(path):
  # Sprawdź przed otwarciem
  """ Sprawdź, czy plik jest zablokowany """
  locked = False
  if os.path.exists(path):
      try:
          os.rename(path, path)
          print("Access on file " + path + " is available!")
      except OSError as e:
          print("Access on file " + path + " is denied!")
          locked = True
  if locked == False:
      os.startfile(path, 'open')
  else:
      print("Plik jest aktualnie zablokowany.")

Dla xls i txt wpada w "is available", zaś pdf w "is denied" path to pełna ścieżka do utworzonego pliku przez generator dokumentów, którego pdf ma być częścią.
QGIS (3.28) ma następującą wersję pythona: 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021)
... brakuje tylko żeby QGIS zamykał raport pdf, tak aby system Windows (i Adobe Acrobat) wiedział że jest on już zamknięty

0

Poniżej przedstawię tylko luźne pomysły co może być przyczyna, bo nie udaje nadal mi się zreprodukować tego błędu..

Ogólnie używając pdf.output(filename) powinna nam samo zamknąć plik.

Excele i pliki tekstowe są zamykane prawdopodobnie przez kontekst manager cos jak tu poniżej np.

with open("countries.txt", encoding="utf8") as csv_file: #as csv_file: This assigns the created file object to the variable csv_file.
	#jakies operacje

może cos jest takiego w kodzie ze napisane kontekst managery with pdf.table() as table:nie zamykaja pliku i jest jakis bypass jak się używa pdf.table():

np bo w przykładzie poniżej jest stworzona lista i w środku jest kolejna lista 😀

TABLE_DATA = [
    ["A",           "B",            "C",            "D"],
    ["A1",          TableSpan.COL,  "C1",           TableSpan.COL],
    [TableSpan.ROW, TableSpan.ROW,  "C2",           TableSpan.COL],
    [TableSpan.ROW, TableSpan.ROW,  TableSpan.ROW,  TableSpan.ROW],
    ["A4",          TableSpan.COL,  TableSpan.COL,  TableSpan.COL],
    ["A5",          TableSpan.COL,  "C5",           "D5"],
    ["A6",          "B6",           TableSpan.COL,  "D6"],
    ["A7",          TableSpan.ROW,  TableSpan.ROW,  TableSpan.ROW],
]

with pdf.table(TABLE_DATA, text_align="CENTER"):
    pass

no i w powyższym przykładzie tabela jest stworzona na sztywno (jest lista zawierająca inne listy). a jak np otwieramy plik csv z uzyciem withto nie jest juz lista tylko zmienna do której przypisujemy obiekt pliku.

W przykładzie na początku jest datakontrola i ciekawe jak są pobierane dane do tej zmiennej tak jak tu (zgaduje ze są to jakieś dynamiczne dane z Qgisa albo dane tekstowe/Excelowe) może tutaj coś jest nie tak (może tu jest jakiś zagnieżdżony kontekst manager a przez to kontekst manager with pdf.table() as table: nie obsuguje jakiegos typu danych i nie nie wywołuje metody __EXIT__ trzeba byłoby sprawdzić debugerem:
Ponizej datakontrola to opcjonany argument rows dla tworzenia komórek, a text_align="J" jest zbędny bo JUSTIFY jest u stawiony jako default moze chodzi o CENTER.

 with pdf.table(datakontrola, col_widths=(40,20,50,70), first_row_as_headings = False, text_align="J", padding = (1)):
                pass

W przykładzie poniżej to kod z manuala dane z pliku nie zaciągamy ich do pdf.table() tak jak w przykładzie poniżej

import csv
from fpdf import FPDF
from fpdf.fonts import FontFace
from fpdf.enums import TableCellFillMode


with open("countries.txt", encoding="utf8") as csv_file:
    data = list(csv.reader(csv_file, delimiter=",")) # tworzyme liste z danymi

pdf = FPDF()
pdf.set_font("helvetica", size=14)

# Styled table:
pdf.add_page()
pdf.set_draw_color(255, 0, 0)
pdf.set_line_width(0.3)
headings_style = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0))
with pdf.table( # nie zaciagane sa dane z  "countries.txt" tzn zmienna data
    borders_layout="NO_HORIZONTAL_LINES",
    cell_fill_color=(224, 235, 255),
    cell_fill_mode=TableCellFillMode.ROWS,
    col_widths=(42, 39, 35, 42),
    headings_style=headings_style,
    line_height=6,
    text_align=("LEFT", "CENTER", "RIGHT", "RIGHT"),
    width=160,
) as table:
    for data_row in data: #tutaj sa zaciagane dane z pliku (lista) dopiero i troche to dziwne bo nie ma pewności, czy dane będą dostępne
        row = table.row()
        for datum in data_row:
            row.cell(datum)

pdf.output("tuto5.pdf")

czyli podsumowując zamiast (i zmiennej datakontrola)

 with pdf.table(datakontrola, col_widths=(40,20,50,70), first_row_as_headings = False, text_align="J", padding = (1)):
                pass

może zrobić np:

 with pdf.table(col_widths=(40,20,50,70), first_row_as_headings = False, text_align="CENTER", padding = (1)) as table1:
                  for data_row in datakontrola:
						table1.row(data_row)

druga rzeczą jaka bym po sprawdzał jeszcze sprawdzić kod podobny do tego

           with pdf.table(col_widths=(40,20,50,70), text_align = "J", padding = (1)) as table1:
                headings = table1.row()
                headings.cell("KONTROLOWANY PLIK")
                headings.cell("KLASA")
                headings.cell("LOKALNY ID")
                headings.cell("KOMUNIKAT BŁĘDU")
                pdf.set_fill_color(gray) # odnosimy sie do pdf i raczej tego nie powinno być bo pdf.table() ma wlasne elementy do stylów

bo środku odnosimy sie do pdf.set_fill_color(gray) , a pdf.table() ma swoje elementy do styli czyli
cell_fill_color=(224, 235, 255),
cell_fill_mode=TableCellFillMode.ROWS,
i to powinno wygladac mniej wiecej tak:

		headings_style = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0))	# i tak nagłowek stylujemy
           with pdf.table(
				borders_layout="NO_HORIZONTAL_LINES",
				cell_fill_color=(224, 235, 255), # stylowanie pozostałych komórek
				cell_fill_mode=TableCellFillMode.ROWS,
				headings_style=headings_style, #stylowanie nagłowka
				line_height=6,
				col_widths=(40,20,50,70), text_align = "CENTER", padding = (1)
		   ) as table1:
                headings = table1.row()
                headings.cell("KONTROLOWANY PLIK")
                headings.cell("KLASA")
                headings.cell("LOKALNY ID")
                headings.cell("KOMUNIKAT BŁĘDU")
                

nie wiem czy dobrze myślę i czy właśnie w tym jest problem można jeszcze sprawdzić to debugerem cos jak to: https://docs.qgis.org/3.34/en/docs/pyqgis_developer_cookbook/plugins/ide_debugging.html

Aby stworzyć plik roboczy korzystałem z manuala z tego linku **https://py-pdf.github.io/fpdf2/Tables.html **, kod uruchamiałem z konsoli pythona ddla Qgis 3.36.2-Maidenhead, który używa pythona python 3.12.3 (main, Apr 14 2024, 17:21:43) [MSC v.1938 64 bit (AMD64)]i stworzył poprawny plik, a kod daje poniżej:

import os
from fpdf import FPDF
from fpdf.fonts import CORE_FONTS
from pathlib import Path
from fpdf.fonts import FontFace
from fpdf.enums import TableCellFillMode


class PDF(FPDF):  # stopka z numerem strony
    def footer(self):
        self.set_y(-15)
        self.cell(0, 10, f'Strona {self.page_no()}', 0, 0, 'C')


def create_pdf(filename):
    pdf = PDF()
    pdf.set_font("helvetica", size=14)
    
    pdf.add_page()
    
    headings_style = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0))	# i tak nagłowek stylujemy
    override_style = FontFace(emphasis="BOLD")
    TABLE_DATA = [
        ["A",           "B",            "C",            "D"],
        ["A1",          "B",            "C1",           "B",],    
        ["A6",          "B6",           "B",            "D6"],   
    ]

    with pdf.table(
        borders_layout="NO_HORIZONTAL_LINES",
        cell_fill_color=(54, 54, 124), # stylowanie pozostałych komórek
        cell_fill_mode=TableCellFillMode.ROW, # to koloruje nieparzyste komorki lub  TableCellFillMode.ALL jesli wszystkie komorki maja byc kolorowe 
        headings_style=headings_style, #stylowanie nagłowka
        line_height=6,
        col_widths=(40,20,50,70), 
        text_align = "CENTER",
        padding = (1)
    ) as table1:
        headings = table1.row()
        headings.cell("KONTROLOWANY PLIK", style=override_style)
        headings.cell("KLASA", style=override_style)
        headings.cell("LOKALNY ID")
        headings.cell("KOMUNIKAT BleDU")
        for data_row in TABLE_DATA:
            table1.row(data_row)

    pdf.set_font("Helvetica", size=24)
    pdf.cell(40, 20, "Hello Arial_TTF! v5")
    pdf.output(filename)
    os.startfile(filename) #otworz plik


create_pdf("d:\core_fonts.pdf")
print("koniec")

Ciekawe czy można też otwoczyć plik dodając os.startfile(filename) tak jak wyżej

albo obejść to i skopiować albo przenieść pdf z użyciem shutil wtedy może będzie się go dało otworzyć bez zamykania programu ale to tymczasowe rozwiązanie po po dłuższym czasie może być bałagan i za dużo duplikatów(jak nie będzie się dało usunąć pliku).

pdf.output(filename)
    # skopiować plik
    # Source and destination file paths
    source_file = filename
    destination_file = "plik_docelowy.pdf"

    # Copy the file with error handling
    try:
        shutil.copy(source_file, destination_file)
        print("File copied successfully!")

        # lub przeniesc plik bez kopiowania
        # shutil.move(source_file, destination_file)

    except shutil.Error as e:
        print(f"Error copying file: {e}")
    # usunąć stworzony plik
    os.remove(filename)

Może coś z tego kodu pomoże w rozwiązaniu problemu.

0

Błąd był gdzie indziej. Praktykant przedobrzył w innej funkcji dodając warunek do:

plikRaportu = open(path,"a", encoding='utf-8')

Czyli program po zamknięciu otrzymywał instrukcję, by ją ponownie otworzyć.
Dziękuje za chęć pomocy, ale teraz inny, trochę inny problem:

Czy jest możliwe aby:

pdf.cell(0,0,"Tabela z wykonanymi kontrolami dodatkowymi", align = "C")

widoczne było na początku każdej nowej strony przy użyciu biblioteki Pythona fpdf, ale nie jako nagłówek. Powyższa funkcja jest częścią pierwszej strony dokumentu, a może się zdarzyć, że dane zgromadzone w zmiennej 'datawalidacja' zajmą dużą liczbę stron, więc, dobrze by nagłówek się również powielał.

Dane w tabelach pochodzą z:

with pdf.table(datawalidacja, col_widths=(40,12,40,88), first_row_as_headings = False, text_align = "L", padding = (1)):
                        pass

0

hmm może wystarczy dodać to jako pierwsza linijkę do datawalidacja (cos jak podobnego jak tu w linku https://www.geeksforgeeks.org/python-perform-append-at-beginning-of-list/ dodac element na poczetek list of lists )i wtedy wystarczy ustawić ze nagłówki tabeli mają 2 wiersze a nie jedna - opcją num_heading_rows=2

może coś takiego o to chodzi?

import os
from fpdf import FPDF
from fpdf.fonts import CORE_FONTS
from pathlib import Path
from fpdf.fonts import FontFace
from fpdf.enums import TableCellFillMode, TableSpan


class PDF(FPDF):  # stopka z numerem strony, TableSpan
    def footer(self):
        self.set_y(-15)
        self.cell(0, 10, f'Strona {self.page_no()}', 0, 0, 'C')


def create_pdf(filename):
    pdf = PDF()
    pdf.set_font("helvetica", size=14)

    pdf.add_page()


    TABLE_DATA = [
        ["Tabela z wykonanymi kontrolami dodatkowymi", TableSpan.COL,  TableSpan.COL,  TableSpan.COL],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
        ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
    ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
                  ["A1", "B", "C1", "B"],
        ["A6", "B6", "B", "D6"],

    ]

    headings_style = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0))  # i tak nagłowek stylujemy
    #override_style = FontFace(emphasis="BOLD", color=56)
    with pdf.table(TABLE_DATA,
                   num_heading_rows=2,
                   borders_layout="NO_HORIZONTAL_LINES",
                   cell_fill_color=(54, 54, 124),  # stylowanie pozostałych komórek
                   cell_fill_mode=TableCellFillMode.ROWS,
                   # to koloruje nieparzyste komorki lub  TableCellFillMode.ALL jesli wszystkie komorki maja byc kolorowe
                   headings_style=headings_style,  # stylowanie nagłowka
                   line_height=6,
                   col_widths=(40, 20, 50, 70),
                   text_align="CENTER",
                   padding=(1)
                   ) as table1:
        pass



    pdf.output(filename)
    os.startfile(filename)  # otworz plik


create_pdf("d:\core_fonts.pdf")
print("koniec")

core_fonts.pdf

screenshot-20240508173105.png

0

Blisko... nagłówek, a raczej tytuł jest oddzielnym tekstem, a nie kolumną wygląd to u mnie tak

if len(bledyWalidacji["WIERSZ"]) > 0:
                    pdf.set_font("Verdana", "B", 10)
                    pdf.cell(0,0,"Tabela z błędami walidacji", align = "C") # tworzy tytuł tabeli w oddzielnej komórce
                    pdf.set_font("Verdana",  "", 8)
                    pdf.cell(0,5,ln = 1)
                    pdf.set_fill_color(lightblue)
                    with pdf.table(col_widths=(40,12,40,88), text_align = "C",padding = (1)) as table:
                        pdf.set_font("Verdana", "", 5)
                        headings = table.row()
                        headings.cell("WALIDOWANY")
                        headings.cell("W")
                        headings.cell("O")
                        headings.cell("K")
                        pdf.set_fill_color(gray)
                    with pdf.table(datawalidacja,  col_widths=(40,12,40,88), first_row_as_headings = False, text_align = "L", padding = (1)):
                        pass

Twój przykład zaś zakłada, że tytuł jest częścią tabeli...
...ale upewnij się się czy da się, bo jeśli nie, to zmienię na twój sposób.

0

ok , jeszcze sprawdzę jedna rzecz czy można wstawić dowolny element jak zostaje wykryty page brake używając pdf.will_page_break(height) ale musze to jeszcze przetestować. Jak by się udało to wtedy jak wykryje page break to można wstawić dowolny element na początku każdej strony np tekst lub zdjęcie a potem dalej wyświetlać kolejne elementy z tabeli

0

Spróbowałem dodać do datakontrola insertem:

naglowek=["Tabela z błędami kontroli atrybutowych", " ",  " ",  " "],
naglowek2=["KONTROLOWANY PLIK", "KLASA",  "LOKALNY ID", "KOMUNIKAT BŁĘDU"],
datakontrola=[['0201_OT_ADJA_A.xml', 'OT_ADJA_A', 
'272D6AAF-2FB2', 
'topo_e3_k3'], 
['0201__OT_ADJA_A.xml', 
'OT_ADJA_A', '272D6AAF-2FB3', 
'topo_e3_k3'], 
['0201__OT_ADJA_A.xml', 'OT_ADJA_A', 
'272D6AAF-2FB9', 
'topo_e3_k3']]
datakontrola = datakontrola.insert(0,naglowek2)
datakontrola = datakontrola.insert(0,naglowek)

ale analiza kodu programu krzyczy że "datakontrola" referenced before assignment... i niestety musi ta zmienna pozostać, bo jest jeszcze ona (tablica z danymi) wykorzystywana przez inne funkcje w całym programie.

1

usuń przecinki na końcu są naglowek i naglowek2, dodatkowo musisz dodac [ dane ] czyli [ [ ] ] do nagłówka i nagłowka2 żeby wstawić to jako wiersz, no i wstawiamy od końca jak chcesz zeby "tabela zbledami kontroli.." była na szczycie wstawiamy to na końcu


    naglowek=[["Tabela z błędami kontroli atrybutowych", " ",  " ",  " "]]
    naglowek2=[["KONTROLOWANY PLIK", "KLASA",  "LOKALNY ID", "KOMUNIKAT BŁĘDU"]]
    datakontrola=[['0201_OT_ADJA_A.xml', 'OT_ADJA_A',
    '272D6AAF-2FB2',
    'topo_e3_k3'],
    ['0201__OT_ADJA_A.xml',
    'OT_ADJA_A', '272D6AAF-2FB3',
    'topo_e3_k3'],
    ['0201__OT_ADJA_A.xml', 'OT_ADJA_A',
    '272D6AAF-2FB9',
    'topo_e3_k3']]
    datakontrola1 = naglowek2 + datakontrola
    datakontrola2 = naglowek + datakontrola1

0

dodawanie do zmiennej działa, ale w kodzie nie zauważyłem funkcji lub instrukcji, by nagłówek sam przeskakiwał na nową stronę. poza tym mam wrażenie że instrukcja jest z załączonym zrzutem niezgodna, gdyż:

 borders_layout="NO_HORIZONTAL_LINES"

powinno oznaczać brak bocznych linii między kolumnami, a na zrzucie one są.
Kolory również się nie ustawiły/

Czy to sprawdzałeś dokładnie?

0

... a sorry, miałem w starej wersji instrukcję:

first_row_as_headings = False

więc to, co podałeś zaprzeczało się tą instrukcją :-|

0

Z tego co widzę trzeba ustawiać każda komórkę nagłówka manualnie i dodać align="L", czyli cos takiego

with pdf.table(TABLE_DATA,
                   borders_layout="NO_HORIZONTAL_LINES",
                   cell_fill_color=(54, 54, 124),  # stylowanie pozostałych komórek
                   cell_fill_mode=TableCellFillMode.ROWS,
                   # to koloruje nieparzyste komorki lub  TableCellFillMode.ALL jesli wszystkie komorki maja byc kolorowe
                   headings_style=headings_style,  # stylowanie nagłowka
                   line_height=6,
                   col_widths=(40, 20, 50, 70),
                   text_align="CENTER",
                   padding=(1)
                   ) as table1:
        headings = table1.row()
        headings.cell("KONTROLOWANY PLIK", style=override_style) #default jest ustawiony na center
        headings.cell("KLASA", style=override_style)
        headings.cell("LOKALNY ID", align="L") # manualnie ustawienie align dla wskazanej komorki "LOKALNY ID"
        headings.cell("KOMUNIKAT BleDU", style=override_style, align="L") # manualnie ustawienie align dla wskazanej komorki "KOMUNIKAT BleDU"
0

a jak używamy Fonface ( przez override_style)to cos takiego:

headings.cell("KOMUNIKAT BleDU", style=override_style, align="L")

0

Otrzymuje

NameError: name 'override_style' is not defined
0

przed with pdf.table() tzreba miec własnie fontface czyli override_style = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0))

czyli ogólnie to tak wyglada przyklad

override_style  = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0))
with pdf.table(TABLE_DATA,
                   borders_layout="NO_HORIZONTAL_LINES",
                   cell_fill_color=(54, 54, 124),  # stylowanie pozostałych komórek
                   cell_fill_mode=TableCellFillMode.ROWS,
                   # to koloruje nieparzyste komorki lub  TableCellFillMode.ALL jesli wszystkie komorki maja byc kolorowe
                   headings_style=headings_style,  # stylowanie nagłowka
                   line_height=6,
                   col_widths=(40, 20, 50, 70),
                   text_align="CENTER",
                   padding=(1)
                   ) as table1:
        headings = table1.row()
        headings.cell("KONTROLOWANY PLIK", style=override_style) #default jest ustawiony na center
        headings.cell("KLASA", style=override_style)
        headings.cell("LOKALNY ID", align="L") # manualnie ustawienie align dla wskazanej komorki "LOKALNY ID"
        headings.cell("KOMUNIKAT BleDU", style=override_style, align="L") # manualnie ustawienie align dla wskazanej komorki "KOMUNIKAT BleDU"
1

Udało mi się zrobić tak, aby customowe elementy były wyświetlane na stronie oraz kolejne wiersze tabeli, choć lepiej byłoby dodać nowy feature do tej biblioteki bo trzeba namęczyć się ze stylami, i nie działają opcje takie jak tworzenie span.(nie działają style te tabelowe )i wszystko trzeba byłoby stylować ręcznie. Nie ma sensu moim zdaniem w ten sposób iść, ale napisze sposób może przypadkiem core_fonts.pdfkomuś do czegoś się to przyda.

import os
from fpdf import FPDF
from fpdf.fonts import FontFace
from fpdf.enums import TableCellFillMode, TableSpan



class PDF(FPDF):
    """
        This class inherits from FPDF and adds custom functionalities for creating reports in PDF format.
    """
    def footer(self):

        self.set_y(-15)
        self.cell(0, 20, f'Strona {self.page_no()}', 0, 0, 'C')

    def table_header(self, line_height):
        """
           This function creates the table header section of a report.

           Args:
               line_height (int): The line height used for spacing in the report.

           Returns:
               None
        """
        # Define table headers and styling
        headers = ["Nak", "Bcos", "Cos1", "Bos"]

        self.cell(0, line_height, "This text appears before each potential table page break.", align="C")
        self.ln(line_height)
        self.image("d:\img.png", h=self.eph / 2, w=self.epw, )
        self.ln(line_height)
        self.set_font(family="helvetica", size=14)
        self.set_text_color(0)
        for col_name in headers:
            self.cell(w=40, txt=col_name, align='C', border=1,)  # Set header style
        self.ln()

def create_pdf(filename):

    pdf = PDF(orientation='P', unit='mm', format='A4')
    pdf.set_font("helvetica", size=14)
    pdf.set_margins(left=25, top=20, right=25)
    pdf.set_auto_page_break(True, margin=10)
    # supported starting only with version 2.5.7
    pdf.add_page()

    #generuj tabele co ma 4 kolumny i 50 wierszy
    table_data =  [[str(i * 4 + col + 1) for col in range(4)] for i in range(50)]

    line_height = pdf.font_size * 2
    col_width = pdf.epw / 4

    pdf.table_header(line_height)
    #pierwsza strona tabeli
    for row in table_data:
        if pdf.will_page_break(line_height):
            # kontynacja tabeli na nastepny stronie
            pdf.table_header(line_height)
        for datum in row:
            pdf.cell(col_width, line_height, datum, border=1)
        pdf.ln(line_height)

    pdf.add_page()

    # dla porównania inne rozwiazanie
    headings_style = FontFace(emphasis="BOLD", color=255, fill_color=(255, 100, 0))  # i tak nagłowek stylujemy
    with pdf.table(table_data,
                   num_heading_rows=2,
                   borders_layout="NO_HORIZONTAL_LINES",
                   cell_fill_color=(54, 54, 124),  # stylowanie pozostałych komórek
                   cell_fill_mode=TableCellFillMode.ROWS,
                   # to koloruje nieparzyste komorki lub  TableCellFillMode.ALL jesli wszystkie komorki maja byc kolorowe
                   headings_style=headings_style,  # stylowanie nagłowka
                   line_height=6,
                   col_widths=(40, 20, 50, 70),
                   text_align="CENTER",
                   padding=(1)
                   ) as table1:
        pass

    pdf.output(filename)
    os.startfile(filename)  # otworz plik


create_pdf("d:\core_fonts.pdf")
print("koniec")

screenshot-20240509134852.png

0

OK, dziękuje i kończę ten wątek

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.