From dce41a01acaeba4e345cc2481184b539639fff8c Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Tue, 9 Jun 2026 23:40:43 +0200
Subject: [PATCH] fix: unit tests and pdf bullet rendering
Co-authored-by: Cursor
---
.../renderers/rendererPdf.py | 38 ++++++++++++++-----
.../workflow/test_extract_content_handover.py | 4 +-
tests/unit/workflow/test_node_combinations.py | 2 +-
3 files changed, 32 insertions(+), 12 deletions(-)
diff --git a/modules/serviceCenter/services/serviceGeneration/renderers/rendererPdf.py b/modules/serviceCenter/services/serviceGeneration/renderers/rendererPdf.py
index 0543a7f3..d1fe3b20 100644
--- a/modules/serviceCenter/services/serviceGeneration/renderers/rendererPdf.py
+++ b/modules/serviceCenter/services/serviceGeneration/renderers/rendererPdf.py
@@ -828,7 +828,15 @@ class RendererPdf(BaseRenderer):
return []
def _renderJsonBulletList(self, list_data: Dict[str, Any], styles: Dict[str, Any]) -> List[Any]:
- """Render a JSON bullet list to PDF elements."""
+ """Render a JSON bullet list to PDF elements.
+
+ Uses ReportLab's built-in ``bulletText`` parameter for proper hanging
+ indent: the bullet/number is drawn at ``bulletIndent`` while all text
+ lines (including continuation) start at ``leftIndent``. This avoids
+ the previous approach of prepending the bullet character to the text
+ which caused misaligned wrap lines when the character width did not
+ match the indent value.
+ """
try:
content = list_data.get("content", {})
if not isinstance(content, dict):
@@ -836,27 +844,39 @@ class RendererPdf(BaseRenderer):
items = content.get("items", [])
bulletStyleDef = styles.get("bullet_list", {})
indent = bulletStyleDef.get("indent", 18)
+ fs = bulletStyleDef.get("font_size", 11)
+
+ us = getattr(self, '_unifiedStyle', None)
+ primaryFont = us["fonts"]["primary"] if us else "Calibri"
+ fontName = _resolveFontFamily(primaryFont, False)
+
+ isNumbered = content.get("list_type") == "numbered"
+ bulletChar = bulletStyleDef.get("bullet_char", "\u2022")
+
bulletStyle = ParagraphStyle(
"BulletItem",
- fontSize=bulletStyleDef.get("font_size", 11),
+ fontName=fontName,
+ fontSize=fs,
textColor=self._hexToColor(bulletStyleDef.get("color", styles.get("colors", {}).get("primary", "#24292e"))),
leftIndent=indent,
- firstLineIndent=-indent,
+ bulletIndent=0,
+ bulletFontName=fontName,
+ bulletFontSize=fs,
spaceAfter=2,
- leading=bulletStyleDef.get("font_size", 11) * 1.25,
+ leading=fs * 1.25,
)
- bulletChar = bulletStyleDef.get("bullet_char", "\u2022")
elements = []
- for item in items:
+ for idx, item in enumerate(items):
+ marker = f"{idx + 1}." if isNumbered else bulletChar
runs = self._inlineRunsForListItem(item)
if isinstance(item, list):
xml = self._renderInlineRunsToPdfXml(runs)
- elements.append(Paragraph(f"{bulletChar} {_wrapEmojiSpansInXml(xml)}", bulletStyle))
+ elements.append(Paragraph(_wrapEmojiSpansInXml(xml), bulletStyle, bulletText=marker))
elif isinstance(item, str):
- elements.append(Paragraph(f"{bulletChar} {self._markdownInlineToReportlabXml(item)}", bulletStyle))
+ elements.append(Paragraph(self._markdownInlineToReportlabXml(item), bulletStyle, bulletText=marker))
elif isinstance(item, dict) and "text" in item:
- elements.append(Paragraph(f"{bulletChar} {self._markdownInlineToReportlabXml(item['text'])}", bulletStyle))
+ elements.append(Paragraph(self._markdownInlineToReportlabXml(item['text']), bulletStyle, bulletText=marker))
if elements:
elements.append(Spacer(1, bulletStyleDef.get("space_after", 3)))
diff --git a/tests/unit/workflow/test_extract_content_handover.py b/tests/unit/workflow/test_extract_content_handover.py
index 401b7a16..f18a3fc6 100644
--- a/tests/unit/workflow/test_extract_content_handover.py
+++ b/tests/unit/workflow/test_extract_content_handover.py
@@ -554,7 +554,7 @@ def test_presentation_envelopes_preserves_data_slot_order_text_image_text():
)
class _Svc:
- interfaceDbComponent = _Mgmt()
+ chat = _Mgmt()
pres = {
"kind": PRESENTATION_KIND,
@@ -666,7 +666,7 @@ def test_presentation_envelopes_to_document_json_image_slot():
return b"\x89PNG\r\n\x1a\n" + b"\x00" * 16
class _Svc:
- interfaceDbComponent = _Mgmt()
+ chat = _Mgmt()
out = presentation_envelopes_to_document_json(
pres,
diff --git a/tests/unit/workflow/test_node_combinations.py b/tests/unit/workflow/test_node_combinations.py
index 3c807f19..c26d4f07 100644
--- a/tests/unit/workflow/test_node_combinations.py
+++ b/tests/unit/workflow/test_node_combinations.py
@@ -596,7 +596,7 @@ def test_extract_image_slot_carries_file_id_and_mime():
class _Services:
def __init__(self):
- self.interfaceDbComponent = _MgmtStub()
+ self.chat = _MgmtStub()
envelope = {
"schemaVersion": PRESENTATION_SCHEMA_VERSION,