In PDF Javascript gespeicherte Feldoptionen verwenden
Die Kosten-pro-Tag-Berechnung und die Drop-Down-Optionen im Anwesenheitsfeld werden per Javsacript gemacht. JS funktioniert aber in pyumpdf nicht, darum werden die korrekten Optionen aus dem JS ausgelesen und dieses 1:1 in den Feldern gesetzt. Mittels Schalter in der config können auch wieder die Rohwerte aus dem Excel gesetzt werden. (PDF muss dann von Hand geöffnet und gespeichert werden damit die entgültigen passenden Werte enthalten sind).
This commit is contained in:
1 parent
c51eddcb73
commit
2433478680
4 files changed
+77
-5
No files matched your search
+2
-1
@@ -29,5 +29,6 @@
|
||||
"pdf_field_ort": "unterschrift.ort",
|
||||
"pdf_field_erstellungsdatum": "unterschrift.datum",
|
||||
"pdf_pauschale_prefix": "pauschale_5",
|
||||
"pauschale_present_value": "1"
|
||||
"pauschale_present_value": "1",
|
||||
"pdf_use_display_values": true
|
||||
}
|
||||
@@ -80,6 +80,11 @@
|
||||
"pauschale_value": {
|
||||
"type": "string",
|
||||
"description": "Wert für das pauschale.N Feld wenn anwesend"
|
||||
},
|
||||
"pdf_use_display_values": {
|
||||
"type": "boolean",
|
||||
"description": "true = Dropdown-Anzeigewerte ins PDF schreiben (z.B. 'X = anwesend'); false = rohe Export-Werte (z.B. 'X')",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,51 @@ def build_field_dict(
|
||||
return fields
|
||||
|
||||
|
||||
def _load_opt_mappings(reader: PdfReader) -> Dict[str, Dict[str, str]]:
|
||||
"""Return export→display maps for every choice field in the template.
|
||||
|
||||
For a field named ``anwesend.0`` with /Opt ``[["X", "X = anwesend"], ...]``
|
||||
the result contains ``{"anwesend": {"X": "X = anwesend", ...}}``.
|
||||
Multiple indexed fields (e.g. ``anwesend.0``…``anwesend.22``) share the
|
||||
same base name and therefore the same mapping.
|
||||
"""
|
||||
opt_maps: Dict[str, Dict[str, str]] = {}
|
||||
fields = reader.get_fields() or {}
|
||||
for name, field in fields.items():
|
||||
if str(field.get("/FT", "")) != "/Ch":
|
||||
continue
|
||||
base = name.split(".")[0]
|
||||
if base in opt_maps:
|
||||
continue # already recorded from an earlier indexed copy
|
||||
raw_opts = field.get("/Opt", [])
|
||||
mapping: Dict[str, str] = {}
|
||||
for opt in raw_opts:
|
||||
if isinstance(opt, list) and len(opt) == 2:
|
||||
mapping[str(opt[0])] = str(opt[1])
|
||||
else:
|
||||
s = str(opt)
|
||||
mapping[s] = s
|
||||
if mapping:
|
||||
opt_maps[base] = mapping
|
||||
return opt_maps
|
||||
|
||||
|
||||
def _apply_opt_mappings(
|
||||
fields: Dict[str, str], opt_maps: Dict[str, Dict[str, str]]
|
||||
) -> None:
|
||||
"""Replace export values with display values in *fields* in-place.
|
||||
|
||||
Iterates over every field whose base name exists in *opt_maps* and
|
||||
substitutes the stored export value for its display counterpart.
|
||||
Fields whose value is not found in the map are left unchanged.
|
||||
"""
|
||||
for field_name, value in list(fields.items()):
|
||||
base = field_name.split(".")[0]
|
||||
mapping = opt_maps.get(base)
|
||||
if mapping and value in mapping:
|
||||
fields[field_name] = mapping[value]
|
||||
|
||||
|
||||
def _update_widget_visibility(writer: PdfWriter, fields: Dict[str, str]) -> None:
|
||||
"""Un-hide widget annotations for ist.N and plan.N fields that have a value.
|
||||
|
||||
@@ -175,6 +220,27 @@ def fill_pdf(
|
||||
f"PDF-Template konnte nicht gelesen werden: {exc}"
|
||||
) from exc
|
||||
|
||||
# Optionally replace export values with display values for choice fields.
|
||||
# Controlled by the config key ``pdf_use_display_values`` (default: True).
|
||||
# When True, pymupdf/viewers render e.g. "X = anwesend" instead of "X".
|
||||
if config.get("pdf_use_display_values", True):
|
||||
opt_maps = _load_opt_mappings(reader)
|
||||
|
||||
# Prefill every choice field that our data logic didn't touch so that
|
||||
# its template-default export value also gets mapped to a display value.
|
||||
# This covers e.g. pauschale_25.N (never set by us, always "0" in template)
|
||||
# and anwesend.N / pauschale_5.N for rows beyond the data entries.
|
||||
for field_name, field in (reader.get_fields() or {}).items():
|
||||
if field_name in fields:
|
||||
continue
|
||||
if str(field.get("/FT", "")) != "/Ch":
|
||||
continue
|
||||
export_val = str(field.get("/V", ""))
|
||||
if export_val:
|
||||
fields[field_name] = export_val
|
||||
|
||||
_apply_opt_mappings(fields, opt_maps)
|
||||
|
||||
writer = PdfWriter()
|
||||
writer.clone_document_from_reader(reader)
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ A fixture is discovered automatically when:
|
||||
|
||||
Rendering
|
||||
---------
|
||||
Both reference and generated PDFs are rendered to images using pymupdf
|
||||
(no browser required). The images are then compared pixel-by-pixel.
|
||||
Both reference and generated PDFs are rendered to images using pymupdf.
|
||||
The images are then compared pixel-by-pixel.
|
||||
|
||||
Similarity threshold
|
||||
--------------------
|
||||
SIMILARITY_THRESHOLD (default 95 %) — fraction of pixels whose R, G, B channels
|
||||
SIMILARITY_THRESHOLD (100%) — fraction of pixels whose R, G, B channels
|
||||
each differ by at most PIXEL_TOLERANCE (default 5) between reference and generated PDF.
|
||||
|
||||
Diff images
|
||||
@@ -47,7 +47,7 @@ from PIL import Image
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
||||
SIMILARITY_THRESHOLD = 0.95 # minimum fraction of matching pixels per page
|
||||
SIMILARITY_THRESHOLD = 1.00 # minimum fraction of matching pixels per page
|
||||
PIXEL_TOLERANCE = 5 # max per-channel delta still counted as "same"
|
||||
RENDER_DPI = 150 # rendering resolution for PDF → image
|
||||
|
||||
|
||||
Reference in new issue
Block a user