PDF-export loopt oneindig rond bij een tabelrij die hoger is dan een blad #1798

Closed
opened 2026-08-26 20:43:30 +00:00 by brenno · 2 comments
Owner

Wat is het probleem?

Een documentexport naar PDF loopt oneindig rond wanneer één tabelcel zoveel tekst bevat dat de rij hoger wordt dan een bladzijde. De export voltooit nooit; het proces blijft op ~140% CPU draaien. In een release-build is er geen enkele rem.

Hoe te reproduceren

final reus = List.generate(400, (i) => 'woord$i').join(' ');
await buildDocumentPdf(
  markdownToPdfBlocks(
    '| Kenmerk | Waarde |\n| --- | --- |\n'
    '| Reus | $reus |\n| Klein | kort |\n',
  ),
  style: DocumentPdfStyle.fromTheme(const ThemeProfile()),
  fonts: ...,
  verbatimLabel: (kind) => 'bron: ${kind.name}',
);

Gemeten op 26-08-2026: flutter_tester draaide tien minuten op 126–140% CPU zonder te voltooien, bij herhaling.

Het is niet onze tabel

Vastgesteld door dezelfde invoer met een kale pw.Table te draaien in plaats van de OrphanSafeTable uit #1790: het hangt identiek. De oorzaak ligt dus in package:pdf zelf, niet in de weesbescherming.

Waar het vandaan komt

multi_page.dart: MultiPage biedt een spannende widget de resterende ruimte aan; Table plaatst rijen tot er één niet meer past. Past de éérste rij al niet — omdat ze hoger is dan een heel blad — dan wordt er niets geplaatst, MultiPage begint een nieuw blad, en daar gebeurt precies hetzelfde.

De bewaking die dit zou moeten vangen staat in een assert (multi_page.dart regel 294):

assert(() {
  if (sameCount++ > maxPages) {
    throw PdfTooBigPageException(...);
  }
  return true;
}());

Een assert verdwijnt in een release-build. In debug krijg je een uitzondering na twintig bladen; in de uitgeleverde app krijgt de gebruiker een bevroren venster.

Waarom dit ertoe doet

Dit is geen exotische invoer. Eén lange alinea in een tabelcel is in een rapport heel gewoon — een toelichting, een citaat, een opsomming van bevindingen in één cel. De gebruiker ziet geen fout en geen voortgang: het programma reageert niet meer, en er is niets dat vertelt welke tabel het is.

Richtingen

  1. Vooraf begrenzen. Bij het bouwen van een tabel vaststellen dat geen enkele cel meer tekst draagt dan er op een blad past, en anders de cel afbreken op een bladgrens of de export met een duidelijke melding weigeren. Dit is de enige route die volledig binnen OciDeck ligt.
  2. maxPages verlagen helpt niet — de bewaking zit in een assert en doet in release niets. Een eigen teller om de export heen zou wel werken.
  3. Upstream: de bewaking uit de assert halen en er een echte uitzondering van maken. DavBfr/dart_pdf, lib/src/widgets/multi_page.dart.

Route 1 is de enige die de gebruiker nu beschermt. Een bevroren venster is de slechtste uitkomst van alle drie.

## Wat is het probleem? Een documentexport naar PDF loopt oneindig rond wanneer één tabelcel zoveel tekst bevat dat de rij hoger wordt dan een bladzijde. De export voltooit nooit; het proces blijft op ~140% CPU draaien. In een release-build is er geen enkele rem. ## Hoe te reproduceren ```dart final reus = List.generate(400, (i) => 'woord$i').join(' '); await buildDocumentPdf( markdownToPdfBlocks( '| Kenmerk | Waarde |\n| --- | --- |\n' '| Reus | $reus |\n| Klein | kort |\n', ), style: DocumentPdfStyle.fromTheme(const ThemeProfile()), fonts: ..., verbatimLabel: (kind) => 'bron: ${kind.name}', ); ``` Gemeten op 26-08-2026: `flutter_tester` draaide tien minuten op 126–140% CPU zonder te voltooien, bij herhaling. ## Het is niet onze tabel Vastgesteld door dezelfde invoer met een kale `pw.Table` te draaien in plaats van de `OrphanSafeTable` uit #1790: het hangt identiek. De oorzaak ligt dus in `package:pdf` zelf, niet in de weesbescherming. ## Waar het vandaan komt `multi_page.dart`: `MultiPage` biedt een spannende widget de resterende ruimte aan; `Table` plaatst rijen tot er één niet meer past. Past de éérste rij al niet — omdat ze hoger is dan een heel blad — dan wordt er niets geplaatst, `MultiPage` begint een nieuw blad, en daar gebeurt precies hetzelfde. De bewaking die dit zou moeten vangen staat in een `assert` (`multi_page.dart` regel 294): ```dart assert(() { if (sameCount++ > maxPages) { throw PdfTooBigPageException(...); } return true; }()); ``` Een `assert` verdwijnt in een release-build. In debug krijg je een uitzondering na twintig bladen; in de uitgeleverde app krijgt de gebruiker een bevroren venster. ## Waarom dit ertoe doet Dit is geen exotische invoer. Eén lange alinea in een tabelcel is in een rapport heel gewoon — een toelichting, een citaat, een opsomming van bevindingen in één cel. De gebruiker ziet geen fout en geen voortgang: het programma reageert niet meer, en er is niets dat vertelt welke tabel het is. ## Richtingen 1. **Vooraf begrenzen.** Bij het bouwen van een tabel vaststellen dat geen enkele cel meer tekst draagt dan er op een blad past, en anders de cel afbreken op een bladgrens of de export met een duidelijke melding weigeren. Dit is de enige route die volledig binnen OciDeck ligt. 2. **`maxPages` verlagen helpt niet** — de bewaking zit in een `assert` en doet in release niets. Een eigen teller om de export heen zou wel werken. 3. **Upstream**: de bewaking uit de `assert` halen en er een echte uitzondering van maken. `DavBfr/dart_pdf`, `lib/src/widgets/multi_page.dart`. Route 1 is de enige die de gebruiker nu beschermt. Een bevroren venster is de slechtste uitkomst van alle drie.
Author
Owner

Opgepakt. Tak fix/export-hang-1798.

Eerst de grens bepalen: is het de hóógte van de rij, of de combinatie van veel tekst in een smalle kolom? Ik toets met per-toets time-outs zodat een hang faalt in plaats van te blijven staan.

Opgepakt. Tak `fix/export-hang-1798`. Eerst de grens bepalen: is het de hóógte van de rij, of de combinatie van veel tekst in een smalle kolom? Ik toets met per-toets time-outs zodat een hang faalt in plaats van te blijven staan.
Author
Owner

Opgelost in PR #1800.

Een tabel met een rij die hoger is dan een blad gaat nu als losse blokken de stroom in: per rij elke cel op een eigen regel, met de kolomkop ervoor. De inhoud blijft volledig; alleen de uitlijning naast elkaar gaat verloren, en die was bij een cel van een halve bladzijde toch geen leeshulp meer.

Twee dingen die een ronde kostten, voor wie hier later komt:

  • De lus is synchroon. Een Timeout in een toets vuurt niet, want de isolate geeft nooit terug. De grens is daarom met een pure functie bepaald in plaats van met een renderproef.
  • Alleen een widget die rechtstreeks in de lijst van MultiPage staat mag breken. De eerste terugvalvorm zat in een pw.Column; die plaatst zijn kinderen heel, waardoor de hang terugkwam in een andere vorm.

Let op bij onderhoud: de regressietoets hangt als de bescherming stukgaat, in plaats van te falen — om dezelfde reden. Dat staat als waarschuwing in de toets zelf.

De upstream-oorzaak blijft bestaan: de bewaking in multi_page.dart staat in een assert en doet in een release-build niets. Wij lopen er nu omheen; een upstream-melding blijft zinvol.

Opgelost in PR #1800. Een tabel met een rij die hoger is dan een blad gaat nu als losse blokken de stroom in: per rij elke cel op een eigen regel, met de kolomkop ervoor. De inhoud blijft volledig; alleen de uitlijning naast elkaar gaat verloren, en die was bij een cel van een halve bladzijde toch geen leeshulp meer. Twee dingen die een ronde kostten, voor wie hier later komt: - **De lus is synchroon.** Een `Timeout` in een toets vuurt niet, want de isolate geeft nooit terug. De grens is daarom met een pure functie bepaald in plaats van met een renderproef. - **Alleen een widget die rechtstreeks in de lijst van `MultiPage` staat mag breken.** De eerste terugvalvorm zat in een `pw.Column`; die plaatst zijn kinderen heel, waardoor de hang terugkwam in een andere vorm. Let op bij onderhoud: de regressietoets *hangt* als de bescherming stukgaat, in plaats van te falen — om dezelfde reden. Dat staat als waarschuwing in de toets zelf. De upstream-oorzaak blijft bestaan: de bewaking in `multi_page.dart` staat in een `assert` en doet in een release-build niets. Wij lopen er nu omheen; een upstream-melding blijft zinvol.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#1798
No description provided.