Messaging: OAuth Sending

Jobs: Sichtbeleg Anpassungen, Seitenwechsel, Freiräume und Taxpos
This commit is contained in:
Developer01
2026-06-30 10:29:00 +02:00
parent 7377a9176e
commit b6d3e61488
7 changed files with 2716 additions and 543 deletions

View File

@@ -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.TaxPosText}]")
_logger.Debug($"TAXPOS AMOUNT accumulated: [{context.TaxPosAmount}]")
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