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,