@@ -24,7 +24,7 @@ Public Class XRechnungViewDocument
Private Const MARGIN_LEFT As Integer = 10
Private Const MARGIN_TOP As Integer = 15
Private Const LINE_WIDTH As Integer = 200
Private Const PAGE_HEIGHT_LIMIT As Integer = 270
Private Const PAGE_HEIGHT_LIMIT As Integer = 275
Private Const FOOTER_Y As Integer = 280
Private Const FOOTER_TEXT_Y As Integer = 285
Private Const LINE_HEIGHT As Integer = 5
@@ -39,6 +39,14 @@ Public Class XRechnungViewDocument
Private Const COL_POS_SUM As Integer = 181
Private Const COL_VALUE_X As Integer = 70
' Spalten für TAXPOS-Tabelle (Steuerbeträge):
' BT 116 <TAB> 2.116,36 € (BASEAMOUNT-Zeile)
' BT 119 <TAB> 7.00 % <TAB> 148,14 € <TAB> VAT (RATE-Zeile)
Private Const COL_TAXPOS_LABEL As Integer = MARGIN_LEFT ' "BT 116" / "BT 119"
Private Const COL_TAXPOS_VALUE1 As Integer = 50 ' Basisbetrag ODER Steuersatz (%)
Private Const COL_TAXPOS_AMOUNT As Integer = 100 ' Steuerbetrag (nur RATE-Zeile)
Private Const COL_TAXPOS_TYPE As Integer = 150 ' VAT-Kennzeichen (nur RATE-Zeile)
' Text-Größen
Private Const TEXT_SIZE_TITLE As Integer = 18
Private Const TEXT_SIZE_NORMAL As Integer = 10
@@ -174,6 +182,9 @@ Public Class XRechnungViewDocument
DrawHeader ( context . PDF )
DrawFooter ( context . PDF , context . CreatedString )
context . YPosition = MARGIN_TOP + 30 ' Nach Header
' WICHTIG: CurrentPositionPage wird hier NICHT gesetzt.
' Es gehört zum Positions-Anker und wird nur in HandleArticleTextFollowUp
' und HandlePositionAmountFollowUp gesetzt – nicht beim Seitenbruch selbst.
End Sub
Private Sub DrawHeader ( pdf As GdPicturePDF )
@@ -412,6 +423,7 @@ Public Class XRechnungViewDocument
context . YDynamic = 0
context . YPosition += LINE_HEIGHT ' Neue Zeile für erste Position
context . CurrentPositionStartY = context . YPosition
context . CurrentPositionPage = context . PDF . GetPageCount ( )
context . PDF . DrawText ( fontResName , COL_POS_NUMBER , context . YPosition , " 1 " ) ' Erste Position ist immer 1
context . PDF . DrawText ( fontResName , COL_POS_AMOUNT , context . YPosition , itemData . Value )
context . PositionCount = 1 ' Zähler auf 1 setzen statt increment
@@ -447,15 +459,42 @@ Public Class XRechnungViewDocument
End Sub
Private Sub HandleTaxPosAreaSwitch ( context As PdfRenderContext , itemData As InvoiceItemData )
If itemData . SpecName = " INVOICE_TAXPOS_RATE " Then
context . PositionCount = 1
context . TaxPosText = $ " {itemData.Value} %: " ' ← In Context speichern!
context . IsFirstTaxPosDisplay = True ' ← Flag setzen!
' Die TAXPOS-Area kann pro Steuersatz-Gruppe mit BASEAMOUNT ODER RATE beginnen:
' Gruppe: [BASEAMOUNT] RATE AMOUNT TYPE (BASEAMOUNT optional, ggf. nicht vorhanden)
' Jede Gruppe ergibt GENAU EINE Zeile, sobald TYPE (VAT) eintrifft - außer der
' optionalen BASEAMOUNT-Zeile, die SOFORT und SEPARAT gerendert wird.
' WICHTIG: Dies ist die allererste Zeile der Area - kein zusätzlicher Zeilenvorschub.
context . IsFirstTaxPosDisplay = True
If itemData . SpecName = " INVOICE_TAXPOS_BASEAMOUNT " Then
RenderTaxposBaseAmountRow ( context , itemData )
itemData . Display = False
_logger . Debug ( $ " TAXPOS RATE in AreaSwitch accumulated: [{context.TaxPosText}] " )
ElseIf itemData . SpecName = " INVOICE_ TAXPOS_ RATE" Then
context . TaxPosRate = itemData . Value
context . TaxPosRateCaption = itemData . Caption
itemData . Display = False
_logger . Debug ( $ " TAXPOS RATE in AreaSwitch (group start, no BASEAMOUNT) accumulated: [{context.TaxPosRate}] " )
End If
End Sub
''' <summary>
''' Rendert die Basisbetrag-Zeile sofort in eigener Zeile: "BT 116 <TAB> 2.116,36 €"
''' Diese Zeile ist unabhängig von der nachfolgenden RATE/AMOUNT/TYPE-Zeile.
''' </summary>
Private Sub RenderTaxposBaseAmountRow ( context As PdfRenderContext , itemData As InvoiceItemData )
' Erste Zeile der TAXPOS-Area braucht keinen zusätzlichen Zeilenvorschub
' (Area-Header hat YPosition bereits korrekt positioniert); alle weiteren Zeilen schon.
If Not context . IsFirstTaxPosDisplay Then
context . YPosition += LINE_HEIGHT
End If
context . IsFirstTaxPosDisplay = False
Dim baseAmountFormatted As String = FormatCurrency ( itemData . Value , context . CurrencySymbol )
context . PDF . DrawText ( fontResName , COL_TAXPOS_LABEL , context . YPosition , itemData . Caption )
context . PDF . DrawText ( fontResName , COL_TAXPOS_VALUE1 , context . YPosition , baseAmountFormatted )
_logger . Debug ( $ " TAXPOS BASEAMOUNT row rendered: [{itemData.Caption}] [{baseAmountFormatted}] at Y={context.YPosition} " )
End Sub
#End Region
@@ -506,7 +545,6 @@ Public Class XRechnungViewDocument
ElseIf itemData . SpecName = " INVOICE_POSITION_UNIT_TYPE " Then
HandleUnitTypeFollowUp ( context , itemData )
ElseIf { " POSITION_ALLOWANCE_REASON " , " RECEIPT_ALLOWANCE_REASON " } . Contains ( itemData . SpecName ) Then
' ALLOWANCE_REASON direkt in Spalte schreiben
context . PDF . DrawText ( fontResName , COL_POS_REASON , context . YPosition , itemData . Value )
itemData . Display = False
ElseIf { " INVOICE_POSITION_ARTICLE " , " INVOICE_POSITION_ARTICLE_DESCRIPTION " } . Contains ( itemData . SpecName ) Then
@@ -514,37 +552,63 @@ Public Class XRechnungViewDocument
ElseIf itemData . SpecName = " INVOICE_POSITION_NOTE " Then
HandlePositionNoteFollowUp ( context , itemData )
ElseIf { " INVOICE_TAXPOS_TAX_RATE " , " INVOICE_TAXPOS_RATE " } . Contains ( itemData . SpecName ) Then
' ← NUR für POSITION: Tax Rate
HandleTaxRateFollowUp ( context , itemData )
ElseIf { " RECEIPT_ALLOWANCE_VAT_RATE " , " POSITION_ALLOWANCE_VAT_RATE " } . Contains ( itemData . SpecName ) Then
' ← NEU: Für ALLOWANCE: VAT Rate (nicht CALCULATION_PERCENT!)
HandleTaxRateFollowUp ( context , itemData )
ElseIf itemData . SpecName = " INVOICE_POSITION_TAX_AMOUNT " Then
HandlePositionTaxAmountFollowUp ( context , itemData )
ElseIf { " RECEIPT_ALLOWANCE_VAT_CODE " , " POSITION_ALLOWANCE_VAT_CODE " } . Contains ( itemData . SpecName ) Then
' VAT_CODE wird nicht angezeigt (nur Metadata)
itemData . Display = False
ElseIf { " RECEIPT_ALLOWANCE_CALCULATION_PERCENT " , " POSITION_ALLOWANCE_CALCULATION_PERCENT " } . Contains ( itemData . SpecName ) Then
' ← NEU: CALCULATION_PERCENT wird nicht angezeigt (nur Metadata)
itemData . Display = False
ElseIf itemData . SpecName = " RECEIPT_ALLOWANCE_CHARGE_INDICATOR " Then
' CHARGE_INDICATOR im Follow-Up (zweite Allowance) wird nicht angezeigt
itemData . Display = False
End If
End Sub
Private Sub HandlePositionAmountFollowUp ( context As PdfRenderContext , itemData As InvoiceItemData , descriptionFollowup As Boolean )
context . PositionCount += 1
' *** BUGFIX: Unnötige Leerzeile zwischen Positionen ***
'
' HandleArticleTextFollowUp synct am Ende jeder Position:
' context.YPosition = Math.Max(context.YPosition, context.YDynamic)
' Nach einer mehrzeiligen DESCRIPTION (z.B. 2 Teile) zeigt YDynamic bereits auf die
' NÄCHSTE freie Zeile (RenderMultiLineText erhöht YDynamic nach jedem Teil). D.h.
' YPosition steht zu Beginn dieser Methode oft schon korrekt auf der nächsten freien
' Zeile - ein zusätzliches "+= LINE_HEIGHT" weiter unten würde dann eine echte
' Leerzeile zwischen letzter DESCRIPTION-Zeile und der neuen Position erzeugen.
'
' Erkennungsmerkmal: YDynamic > 0 UND YDynamic = YPosition bedeutet, die vorherige
' Position hat bereits über RenderMultiLineText/HandleArticleTextFollowUp die
' YPosition auf die korrekte nächste freie Zeile gesetzt - dann KEIN weiterer Vorschub.
Dim yPositionAlreadyAdvanced As Boolean = ( context . YDynamic > 0 ) AndAlso ( context . YDynamic = context . YPosition )
If Not descriptionFollowup Then
context . YPlus = 0
context . YDynamic = 0
End If
' WICHTIG: Neue Zeile für jede neue Position!
context . YPosition += LINE_HEIGHT
' ← NEU: Start-Y der aktuellen Position speichern
If Not yPositionAlreadyAdvanced Then
context . YPosition += LINE_HEIGHT
End If
context . CurrentPositionStartY = context . YPosition
context . CurrentPositionPage = context . PDF . GetPageCount ( )
' ✓ OPTIMIERT: Von 11mm auf 6mm
' WARUM 6mm?
' - Position#-Zeile: 5mm (wird SOFORT gerendert)
' - Tax/Amount: 0mm Extra (werden auf DERSELBEN Y-Position gerendert via CurrentPositionStartY)
' - Minimaler Puffer: 1mm (für Textbox-Rendering-Toleranzen)
Dim requiredSpace As Integer = 6
If ( context . YPosition + requiredSpace ) >= PAGE_HEIGHT_LIMIT Then
CreateNewPage ( context )
context . YDynamic = context . YPosition
context . CurrentPositionStartY = context . YPosition
context . CurrentPositionPage = context . PDF . GetPageCount ( ) ' neue Seite ist der Anker
_logger . Debug ( $ " HandlePositionAmountFollowUp: Page break! New YPosition={context.YPosition}, CurrentPositionStartY={context.CurrentPositionStartY}, Page={context.CurrentPositionPage} " )
End If
context . PDF . DrawText ( fontResName , COL_POS_NUMBER , context . YPosition , context . PositionCount . ToString ( ) )
@@ -587,9 +651,41 @@ Public Class XRechnungViewDocument
xPos = COL_POS_REASON
End If
RenderMultiLineText ( context , itemData . Value , xPos , MAX_TEXT_LENGTH_POSITION )
' *** BUGFIX: CurrentPositionStartY auf die ERSTE Zeile von INVOICE_POSITION_ARTICLE verankern ***
'
' Tax (COL_POS_TAX) und Amount (COL_POS_SUM) müssen in der ERSTEN Zeile des Artikeltexts
' erscheinen - dort wo der Artikelname und damit auch der Steuersatz/Betrag laut Layout
' hingehören. Bei kurzen (einzeiligen) Artikeln ist "erste Zeile" = "letzte Zeile", daher
' lieferte die alte Berechnung (YDynamic - LINE_HEIGHT, NACH dem Rendern) bislang dasselbe
' Ergebnis. Bei LANGEN, mehrzeiligen Artikeltexten (z.B. 15 Zeilen Prüfbeschreibung, die
' selbst über einen Seitenbruch laufen) zeigte "YDynamic - LINE_HEIGHT NACH dem Rendern"
' auf die LETZTE Zeile des Artikeltexts (ggf. auf einer späteren Seite) - das war falsch.
'
' Die neuen ByRef-Parameter firstAnchorY/firstAnchorPage liefern stattdessen exakt den
' Y-Wert und die Seite des ALLERERSTEN tatsächlich gezeichneten Textteils, erfasst
' INNERHALB von RenderMultiLineText direkt vor dem ersten DrawText-Aufruf - das ist in
' allen Fällen korrekt, auch wenn der Seitenbruch noch VOR dem ersten Zeichnen eintritt
' (Szenario B: Artikel beginnt komplett auf neuer Seite).
'
' Szenario A – kein Seitenbruch, kurzer Artikel: erste Zeile=240/Seite1 → Anker=240/1 (wie bisher) ✓
' Szenario B – FIRST-part-Break (Artikel beginnt auf neuer Seite): erste Zeile=45/Seite2 → Anker=45/2 (wie bisher) ✓
' Szenario C – CONTINUATION-Break in DESCRIPTION (Artikel einzeilig, bleibt auf alter Seite):
' erste Zeile=265/Seite1 → Anker=265/1 (wie bisher, unverändert) ✓
' Szenario D – NEU: langer mehrzeiliger ARTIKEL, der selbst über mehrere Seiten läuft:
' erste Zeile=220/Seite1 → Anker=220/1 (KORRIGIERT, vorher fälschlich letzte Zeile/spätere Seite)
If itemData . SpecName = " INVOICE_POSITION_ARTICLE " Then
Dim firstAnchorY As Integer = - 1
Dim firstAnchorPage As Integer = - 1
RenderMultiLineText ( context , itemData . Value , xPos , MAX_TEXT_LENGTH_POSITION , firstAnchorY , firstAnchorPage )
' ← NEU: YPosition auf den höchsten erreichten Wert setzen
context . CurrentPositionStartY = firstAnchorY
context . CurrentPositionPage = firstAnchorPage
_logger . Debug ( $ " HandleArticleTextFollowUp: CurrentPositionStartY anchored to FIRST ARTICLE row Y={context.CurrentPositionStartY} on page {context.CurrentPositionPage} " )
Else
RenderMultiLineText ( context , itemData . Value , xPos , MAX_TEXT_LENGTH_POSITION )
End If
' YPosition immer synchronisieren
context . YPosition = Math . Max ( context . YPosition , context . YDynamic )
itemData . Display = False
@@ -602,28 +698,74 @@ Public Class XRechnungViewDocument
RenderMultiLineText ( context , itemData . Value , COL_POS_TEXT , MAX_TEXT_LENGTH_NOTE )
' ← NEU : YPosition synchronisieren
' ✓ KORRIGIERT : YPosition synchronisieren
context . YPosition = Math . Max ( context . YPosition , context . YDynamic )
itemData . Display = False
End Sub
Private Sub HandleTaxRateFollowUp ( context As PdfRenderContext , itemData As InvoiceItemData )
' ← VERWENDE die Start-Y-Position der aktuellen Position!
Dim yPos As Integer = context . CurrentPositionStartY
_logger . Debug ( $ " Handling Tax Rate Follow-Up: Value=[{itemData.Value}] at YPos={yPos} " )
Dim targetPage As Integer = context . CurrentPositionPage
Dim activePage As Integer = context . PDF . GetPageCount ( )
_logger . Debug ( $ " Handling Tax Rate Follow-Up: Value=[{itemData.Value}] at YPos={yPos}, targetPage={targetPage}, activePage={activePage} " )
If yPos < MARGIN_TOP + 30 Then
_logger . Warn ( $ " TaxRate: yPos={yPos} ungültig. Verwende YPosition={context.YPosition}. " )
yPos = context . YPosition
targetPage = activePage
End If
If targetPage <> activePage Then
context . PDF . SelectPage ( targetPage )
_logger . Debug ( $ " TaxRate: SelectPage({targetPage}) to draw at Y={yPos}, then back to page {activePage} " )
End If
' SetTextSize explizit setzen: GdPicturePDF hält Font-Zustand global;
' nach SelectPage ist er undefiniert und muss vor jedem DrawText normalisiert werden.
context . PDF . SetTextSize ( TEXT_SIZE_NORMAL )
context . PDF . DrawText ( fontResName , COL_POS_TAX , yPos , $ " {itemData.Value} % " )
If targetPage <> activePage Then
context . PDF . SelectPage ( activePage )
context . PDF . SetTextSize ( TEXT_SIZE_NORMAL ) ' Font-Zustand der aktiven Seite wiederherstellen
End If
itemData . Display = False
End Sub
Private Sub HandlePositionTaxAmountFollowUp ( context As PdfRenderContext , itemData As InvoiceItemData )
Dim yPos As Integer = context . CurrentPositionStartY
Dim targetPage As Integer = context . CurrentPositionPage
Dim activePage As Integer = context . PDF . GetPageCount ( )
If yPos < MARGIN_TOP + 30 Then
_logger . Warn ( $ " TaxAmount: yPos={yPos} ungültig. Verwende YPosition={context.YPosition}. " )
yPos = context . YPosition
targetPage = activePage
End If
Dim yPosAdjusted As Double = yPos - 3 . 5
Dim taxTerm As String = FormatCurrency ( itemData . Value , context . CurrencySymbol )
_logger . Debug ( $ " Handling Position Tax Amount Follow-Up: Value=[{itemData.Value}] Formatted=[{taxTerm}] at YPos={yPos}, Adjusted YPos ={yPosAdjusted} " )
_logger . Debug ( $ " Handling Position Tax Amount Follow-Up: Value=[{itemData.Value}] Formatted=[{taxTerm}] at YPos={yPos}, Adjusted={yPosAdjusted}, targetPage={targetPage}, activePage={activePage} " )
If targetPage <> activePage Then
context . PDF . SelectPage ( targetPage )
_logger . Debug ( $ " TaxAmount: SelectPage({targetPage}) to draw at Y={yPosAdjusted}, then back to page {activePage} " )
End If
' SetTextSize explizit setzen – gleicher Grund wie in HandleTaxRateFollowUp.
context . PDF . SetTextSize ( TEXT_SIZE_NORMAL )
context . PDF . DrawTextBox ( fontResName , 177 , yPosAdjusted , 198 , YCoo_TextBoxPlus5 ( yPosAdjusted ) ,
TextAlignment . TextAlignmentFar , TextAlignment . TextAlignmentNear ,
taxTerm )
If targetPage <> activePage Then
context . PDF . SelectPage ( activePage )
context . PDF . SetTextSize ( TEXT_SIZE_NORMAL ) ' Font-Zustand der aktiven Seite wiederherstellen
End If
itemData . Display = False
End Sub
@@ -634,23 +776,47 @@ Public Class XRechnungViewDocument
End Sub
Private Sub HandleTaxPosFollowUp ( context As PdfRenderContext , itemData As InvoiceItemData )
' TAXPOS Items werden zu einem String kombiniert
If itemData . SpecName = " INVOICE_TAXPOS_RATE " Then
If itemData . SpecName = " INVOICE_TAXPOS_BASEAMOUNT " Then
' BASEAMOUNT kann auch als Follow-Up (zweite und weitere Steuersatz-Gruppen)
' auftreten, nicht nur im allerersten Area-Switch. Eigene Zeile, sofort gerendert.
RenderTaxposBaseAmountRow ( context , itemData )
itemData . Display = False
ElseIf itemData . SpecName = " INVOICE_TAXPOS_RATE " Then
context . PositionCount += 1
context . TaxPosText = $ " {itemData.Value} %: " ' Speichern statt direkt setzen
context . TaxPosRate = itemData . Value
context . TaxPosRateCaption = itemData . Caption
itemData . Display = False
_logger . Debug ( $ " TAXPOS RATE accumulated: [{context.TaxPosText }] " )
_logger . Debug ( $ " TAXPOS RATE accumulated (new group) : [{context.TaxPosRate }] " )
ElseIf itemData . SpecName = " INVOICE_TAXPOS_AMOUNT " Then
Dim amount As String = FormatCurrency ( itemData . Value , context . CurrencySymbol )
context . TaxPosText &= amount ' Anhängen
context . TaxPosAmount = FormatCurrency ( itemData . Value , context . CurrencySymbol )
itemData . Display = False
_logger . Debug ( $ " TAXPOS AMOUNT accumulated: [{context.TaxPosTex t}] " )
_logger . Debug ( $ " TAXPOS AMOUNT accumulated: [{context.TaxPosAmoun t}] " )
ElseIf itemData . SpecName = " INVOICE_TAXPOS_TYPE " Then
context . TaxPosText &= $ " {itemData.Value} " ' Anhängen
itemData . Value = context . TaxPosText ' JETZT den kombinierten String setzen
itemData . Display = True ' Und anzeigen!
context . TaxPosText = " " ' Reset für nächste TAXPOS
_logger . Debug ( $ " TAXPOS TYPE final: [{itemData.Value}], Display=True " )
' Vollständige Gruppe (RATE + AMOUNT + TYPE) ist da → eine Zeile rendern:
' "BT 119 <TAB> 7.00 % <TAB> 148,14 € <TAB> VAT"
If Not String . IsNullOrEmpty ( context . TaxPosRate ) Then
' Nur Zeilenvorschub wenn dies NICHT die allererste Zeile der Area ist
' (z.B. wenn die Gruppe direkt mit RATE begann, ohne BASEAMOUNT davor).
If Not context . IsFirstTaxPosDisplay Then
context . YPosition += LINE_HEIGHT
End If
context . IsFirstTaxPosDisplay = False
context . PDF . DrawText ( fontResName , COL_TAXPOS_LABEL , context . YPosition , context . TaxPosRateCaption )
context . PDF . DrawText ( fontResName , COL_TAXPOS_VALUE1 , context . YPosition , $ " {context.TaxPosRate} % " )
context . PDF . DrawText ( fontResName , COL_TAXPOS_AMOUNT , context . YPosition , context . TaxPosAmount )
context . PDF . DrawText ( fontResName , COL_TAXPOS_TYPE , context . YPosition , itemData . Value )
_logger . Debug ( $ " TAXPOS row rendered: [{context.TaxPosRateCaption}] [{context.TaxPosRate} %] [{context.TaxPosAmount}] [{itemData.Value}] at Y={context.YPosition} " )
' Reset für nächste TAXPOS-Gruppe
context . TaxPosRate = " "
context . TaxPosRateCaption = " "
context . TaxPosAmount = " "
Else
_logger . Debug ( $ " TAXPOS TYPE DUPLICATE/incomplete ignored: [{itemData.Value}] " )
End If
itemData . Display = False
ElseIf itemData . Value . Contains ( " EXEMPTION " ) Then
_logger . Debug ( $ " We got an Exemption: {itemData.Value} " )
End If
@@ -731,28 +897,58 @@ Public Class XRechnungViewDocument
End If
End Sub
Private Sub RenderMultiLineText ( context As PdfRenderContext , text As String , xPos As Integer , maxLength As Integer )
' ERST nach echten Zeilenumbrüchen aufteilen
Private Sub RenderMultiLineText ( context As PdfRenderContext , text As String , xPos As Integer , maxLength As Integer ,
Optional ByRef firstRenderedY As Integer = - 1 ,
Optional ByRef firstRenderedPage As Integer = - 1 )
Dim partsNL As List ( Of String ) = StringFunctions . SplitTextByNewLine ( text )
Dim isFirstPart As Boolean = True
For Each linePart As String In partsNL
' DANN jede Zeile bei maxLength umbrechen
Dim parts As List ( Of String ) = StringFunctions . SplitText_Length ( linePart , maxLength )
For Each part As String In parts
' ← NEU: VOR jedem Rendering prüfen, ob neue Seite nötig !
If context . YDynamic >= PAGE_HEIGHT_LIMIT Then
' ✓ PROBLEM GEFUNDEN: requiredSpace=10 ist zu viel !
' Bei YDynamic=230 prüft es: 230 + 10 = 240 >= 270 → TRUE → Seitenumbruch
' ABER: Die Zeile selbst benötigt nur 5mm! 230 + 5 = 235 wäre OK!
Dim requiredSpace As Integer = 6 ' ← REDUZIERT: Nur 1mm Puffer (Zeile=5mm + 1mm)
If ( context . YDynamic + requiredSpace ) >= PAGE_HEIGHT_LIMIT Then
' Neue Seite erstellen
CreateNewPage ( context )
' YDynamic auf neue Startposition setzen (nach Header)
context . YDynamic = context . YPosition
_logger . Debug ( $ " RenderMultiLineText: Page break! New YDynamic: {context.YDynamic} " )
' WICHTIG: CurrentPositionStartY wird hier NICHT geändert.
' Der korrekte Anker ist die Zeile von INVOICE_POSITION_ARTICLE,
' gesetzt in HandleArticleTextFollowUp VOR dem RenderMultiLineText-Aufruf.
' - CONTINUATION-Break: Artikel auf alter Seite → CurrentPositionStartY
' bleibt auf der alten Seite (Tax/Amount landen korrekt neben Artikel).
' - FIRST-part-Break im ARTICLE-Feld: HandleArticleTextFollowUp hat
' CurrentPositionStartY = context.YDynamic gesetzt, BEVOR CreateNewPage
' aufgerufen wurde → nach dem Seitenbruch zeigt YDynamic auf die neue
' Seite und der Anker ist bereits korrekt (45).
If isFirstPart Then
_logger . Debug ( $ " RenderMultiLineText: Page break at FIRST part! YDynamic={context.YDynamic}, CurrentPositionStartY unchanged={context.CurrentPositionStartY} " )
Else
_logger . Debug ( $ " RenderMultiLineText: Page break at continuation! YDynamic={context.YDynamic}, CurrentPositionStartY unchanged={context.CurrentPositionStartY} " )
End If
End If
' *** BUGFIX (lange mehrzeilige ARTICLE-Texte über Seitenbruch hinweg) ***
' Erfasst Y/Seite des ALLERERSTEN tatsächlich gezeichneten Textteils - das ist
' immer die korrekte Ankerzeile, unabhängig davon, ob danach noch weitere Zeilen
' folgen oder ein Seitenbruch mitten im Text auftritt. Wird nur befüllt, wenn der
' Aufrufer (HandleArticleTextFollowUp) die Parameter explizit übergibt.
If firstRenderedY = - 1 Then
firstRenderedY = context . YDynamic
firstRenderedPage = context . PDF . GetPageCount ( )
End If
_logger . Debug ( $ " RenderMultiLineText: Rendering part: [{part}] at Y position: {context.YDynamic} " )
context . PDF . DrawText ( fontResName , xPos , context . YDynamic , part )
context . YDynamic += LINE_HEIGHT
context . YPlus += LINE_HEIGHT
isFirstPart = False
Next
Next
End Sub
@@ -821,9 +1017,15 @@ Public Class XRechnungViewDocument
Public Property YPlus As Integer
Public Property CreateTextBox As Boolean
Public Property CreatedString As String
Public Property TaxPosText As String
Public Property TaxPosText As String ' Legacy, nicht mehr aktiv genutzt - bleibt für Kompatibilität
Public Property TaxPosRate As String
Public Property TaxPosRateCaption As String
Public Property TaxPosAmount As String
Public Property IsFirstTaxPosDisplay As Boolean
Public Property CurrentPositionStartY As Integer
' Seitennummer auf der CurrentPositionStartY gültig ist.
' Wird beim Setzen von CurrentPositionStartY immer mitgesetzt.
Public Property CurrentPositionPage As Integer
Public Sub New ( pdf As GdPicturePDF , createdString As String )
Me . PDF = pdf
@@ -837,8 +1039,12 @@ Public Class XRechnungViewDocument
YPlus = 0
CreateTextBox = False
TaxPosText = " "
TaxPosRate = " "
TaxPosRateCaption = " "
TaxPosAmount = " "
IsFirstTaxPosDisplay = False
CurrentPositionStartY = 0
CurrentPositionPage = 1
End Sub
End Class