@@ -357,60 +357,91 @@ private void createSelectionsRows(BaseTable table, PDFont font, Map<String, Map<
357357 float baseBottomMargin = 20f ;
358358
359359 PDPage page = table .getCurrentPage ();
360+ float pageWidth = page .getMediaBox ().getWidth ();
360361 float pageHeight = page .getMediaBox ().getHeight ();
362+ float availableTextWidth = pageWidth - (2 * margin );
363+
364+ // Build wrapped lines first so we can paginate correctly
365+ final class SelectionLine {
366+ final String key ;
367+ final float keyWidth ;
368+ final java .util .List <String > wrappedValueLines ;
369+
370+ SelectionLine (String key , float keyWidth , java .util .List <String > wrappedValueLines ) {
371+ this .key = key ;
372+ this .keyWidth = keyWidth ;
373+ this .wrappedValueLines = wrappedValueLines ;
374+ }
375+ }
376+
377+ java .util .List <SelectionLine > lines = new java .util .ArrayList <>();
361378
362- // Count number of selections
363- int selectionCount = 0 ;
364379 for (Map .Entry <String , Map <String , JSONArray >> datasetEntry : selections .entrySet ()) {
365380 Map <String , JSONArray > selectionContent = datasetEntry .getValue ();
366- if (selectionContent != null && !selectionContent .isEmpty ()) {
367- selectionCount += selectionContent .size ();
381+ if (selectionContent == null || selectionContent .isEmpty ()) {
382+ continue ;
383+ }
384+
385+ for (Map .Entry <String , JSONArray > columnEntry : selectionContent .entrySet ()) {
386+ String key = columnEntry .getKey ();
387+ key = key .replace ("('" , "" ).replace ("')" , "" );
388+
389+ String valueStr = getValues (columnEntry ).toString ().replace ("('" , "" ).replace ("')" , "" );
390+
391+ float keyWidth = font .getStringWidth (key ) / 1000f * fontSize ;
392+ float prefixWidth = keyWidth + (font .getStringWidth (": " ) / 1000f * fontSize );
393+ float valueStartX = margin + prefixWidth ;
394+ float valueAvailableWidth = Math .max (10f , (margin + availableTextWidth ) - valueStartX );
395+
396+ java .util .List <String > wrappedValues = wrapText (font , fontSize , valueStr , valueAvailableWidth );
397+ if (wrappedValues .isEmpty ()) {
398+ wrappedValues = java .util .Collections .singletonList ("" );
399+ }
400+
401+ lines .add (new SelectionLine (key , keyWidth , wrappedValues ));
368402 }
369403 }
370404
371- if (selectionCount == 0 ) return ;
405+ if (lines .isEmpty ()) {
406+ return ;
407+ }
408+
409+ int totalLines = 0 ;
410+ for (SelectionLine sl : lines ) {
411+ totalLines += Math .max (1 , sl .wrappedValueLines .size ());
412+ }
372413
373414 // Calculate where the table actually ends
374415 // Table starts at y=550 (from createBaseTable)
375416 float tableStartY = 550f ;
376417 float tableHeight = 0f ;
377418
378- // Sum up all row heights to get total table height
379419 try {
380420 List <Row <PDPage >> rows = table .getRows ();
381421 for (Row <PDPage > row : rows ) {
382422 tableHeight += row .getHeight ();
383423 }
384424 } catch (Exception e ) {
385- // If we can't get rows, use an estimate
386425 logger .warn ("Could not calculate exact table bottom" );
387426 tableHeight = 0 ;
388427 }
389428
390- // Table ends at: startY - totalHeight
391429 float tableEndY = tableStartY - tableHeight ;
392430
393- // Calculate required height for all selections
394- float requiredHeight = selectionCount * leading ;
395-
396- // Check if there's space below the table
397- // Available space = tableEndY - baseBottomMargin
431+ float requiredHeight = totalLines * leading ;
398432 float availableSpace = tableEndY - baseBottomMargin ;
399433 boolean needsNewPage = availableSpace < requiredHeight ;
400434
401435 PDPage targetPage ;
402- float topBaseline ;
436+ float currentY ;
403437
404438 if (needsNewPage ) {
405- // Create a new page for selections
406439 targetPage = new PDPage (page .getMediaBox ());
407440 table .document .addPage (targetPage );
408- // On the new page, start from near the top with margins
409- topBaseline = pageHeight - margin - leading ;
441+ currentY = pageHeight - margin - leading ;
410442 } else {
411- // Position selections immediately after the table
412443 targetPage = page ;
413- topBaseline = tableEndY - leading ;
444+ currentY = tableEndY - leading ;
414445 }
415446
416447 try (PDPageContentStream contentStream =
@@ -419,57 +450,100 @@ private void createSelectionsRows(BaseTable table, PDFont font, Map<String, Map<
419450 contentStream .setNonStrokingColor (Color .BLACK );
420451 contentStream .setFont (font , fontSize );
421452
422- float currentY = topBaseline ;
453+ for (SelectionLine sl : lines ) {
454+ float keyX = margin ;
455+ float keyWidth = sl .keyWidth ;
456+ float afterKeyX = keyX + keyWidth ;
457+ float colonWidth = font .getStringWidth (": " ) / 1000f * fontSize ;
458+ float valuesX = afterKeyX + colonWidth ;
459+
460+ java .util .List <String > wrapped = sl .wrappedValueLines ;
461+ int lineCount = Math .max (1 , wrapped .size ());
462+
463+ for (int i = 0 ; i < lineCount ; i ++) {
464+ // page break
465+ if (currentY <= baseBottomMargin + leading ) {
466+ targetPage = new PDPage (page .getMediaBox ());
467+ table .document .addPage (targetPage );
468+ currentY = pageHeight - margin - leading ;
469+
470+ try (PDPageContentStream csNew =
471+ new PDPageContentStream (table .document , targetPage , PDPageContentStream .AppendMode .APPEND , true )) {
472+ csNew .setNonStrokingColor (Color .BLACK );
473+ csNew .setFont (font , fontSize );
474+
475+ for (int j = i ; j < lineCount ; j ++) {
476+ if (j == 0 ) {
477+ csNew .beginText ();
478+ csNew .newLineAtOffset (keyX , currentY );
479+ csNew .showText (sl .key );
480+ csNew .endText ();
481+
482+ float underlineY = currentY - 2.0f ;
483+ csNew .setLineWidth (0.5f );
484+ csNew .moveTo (keyX , underlineY );
485+ csNew .lineTo (keyX + keyWidth , underlineY );
486+ csNew .stroke ();
487+
488+ csNew .beginText ();
489+ csNew .newLineAtOffset (afterKeyX , currentY );
490+ csNew .showText (": " );
491+ csNew .endText ();
492+
493+ csNew .beginText ();
494+ csNew .newLineAtOffset (valuesX , currentY );
495+ csNew .showText (wrapped .get (j ));
496+ csNew .endText ();
497+ } else {
498+ csNew .beginText ();
499+ csNew .newLineAtOffset (valuesX , currentY );
500+ csNew .showText (wrapped .get (j ));
501+ csNew .endText ();
502+ }
503+
504+ currentY -= leading ;
505+ if (currentY <= baseBottomMargin + leading && j + 1 < lineCount ) {
506+ targetPage = new PDPage (page .getMediaBox ());
507+ table .document .addPage (targetPage );
508+ currentY = pageHeight - margin - leading ;
509+ i = j + 1 ;
510+ break ;
511+ }
512+ }
513+ }
423514
424- for (Map .Entry <String , Map <String , JSONArray >> datasetEntry : selections .entrySet ()) {
425- Map <String , JSONArray > selectionContent = datasetEntry .getValue ();
515+ i = lineCount ;
516+ break ;
517+ }
426518
427- if (selectionContent == null || selectionContent .isEmpty ()) {
428- continue ;
429- }
519+ if (i == 0 ) {
520+ contentStream .beginText ();
521+ contentStream .newLineAtOffset (keyX , currentY );
522+ contentStream .showText (sl .key );
523+ contentStream .endText ();
524+
525+ float underlineY = currentY - 2.0f ;
526+ contentStream .setLineWidth (0.5f );
527+ contentStream .moveTo (keyX , underlineY );
528+ contentStream .lineTo (keyX + keyWidth , underlineY );
529+ contentStream .stroke ();
530+
531+ contentStream .beginText ();
532+ contentStream .newLineAtOffset (afterKeyX , currentY );
533+ contentStream .showText (": " );
534+ contentStream .endText ();
535+
536+ contentStream .beginText ();
537+ contentStream .newLineAtOffset (valuesX , currentY );
538+ contentStream .showText (wrapped .get (i ));
539+ contentStream .endText ();
540+ } else {
541+ contentStream .beginText ();
542+ contentStream .newLineAtOffset (valuesX , currentY );
543+ contentStream .showText (wrapped .get (i ));
544+ contentStream .endText ();
545+ }
430546
431- for (Map .Entry <String , JSONArray > columnEntry : selectionContent .entrySet ()) {
432- String key = columnEntry .getKey ();
433- // Remove 'WA' and spaces from the key
434- key = key .replace ("('" , "" ).replace ("')" , "" );
435-
436- StringBuilder values = getValues (columnEntry );
437- String valueStr = values .toString ().replace ("('" , "" ).replace ("')" , "" );
438-
439- float currentX = margin ;
440-
441- // Print the key in bold
442- float keyWidth = font .getStringWidth (key ) / 1000f * fontSize ;
443- contentStream .beginText ();
444- contentStream .setFont (font , fontSize );
445- contentStream .newLineAtOffset (currentX , currentY );
446- contentStream .showText (key );
447- contentStream .endText ();
448- contentStream .setFont (font , fontSize );
449-
450- // Draw underline under the key
451- float underlineY = currentY - 2.0f ;
452- contentStream .setLineWidth (0.5f );
453- contentStream .moveTo (currentX , underlineY );
454- contentStream .lineTo (currentX + keyWidth , underlineY );
455- contentStream .stroke ();
456-
457- currentX += keyWidth ;
458-
459- // Print ": "
460- contentStream .beginText ();
461- contentStream .newLineAtOffset (currentX , currentY );
462- contentStream .showText (": " );
463- contentStream .endText ();
464- currentX += font .getStringWidth (": " ) / 1000f * fontSize ;
465-
466- // Print the values
467- contentStream .beginText ();
468- contentStream .newLineAtOffset (currentX , currentY );
469- contentStream .showText (valueStr );
470- contentStream .endText ();
471-
472- // Move to next line
473547 currentY -= leading ;
474548 }
475549 }
@@ -494,6 +568,86 @@ private void createSelectionsRows(BaseTable table, PDFont font, Map<String, Map<
494568 return values ;
495569 }
496570
571+ /**
572+ * Wrap text so that each line fits within maxWidth using the provided font metrics.
573+ * Splits by whitespace; if a single token is wider than maxWidth it will be hard-split.
574+ */
575+ private static java .util .List <String > wrapText (PDFont font , float fontSize , String text , float maxWidth ) throws IOException {
576+ java .util .List <String > result = new java .util .ArrayList <>();
577+ if (text == null || text .isEmpty ()) {
578+ return result ;
579+ }
580+
581+ String [] words = text .split ("\\ s+" );
582+ StringBuilder line = new StringBuilder ();
583+
584+ for (String word : words ) {
585+ if (word .isEmpty ()) {
586+ continue ;
587+ }
588+
589+ if (line .length () == 0 ) {
590+ if (stringWidth (font , fontSize , word ) <= maxWidth ) {
591+ line .append (word );
592+ } else {
593+ for (String part : hardSplitToken (font , fontSize , word , maxWidth )) {
594+ if (!part .isEmpty ()) {
595+ result .add (part );
596+ }
597+ }
598+ }
599+ } else {
600+ String candidate = line + " " + word ;
601+ if (stringWidth (font , fontSize , candidate ) <= maxWidth ) {
602+ line .append (' ' ).append (word );
603+ } else {
604+ result .add (line .toString ());
605+ line .setLength (0 );
606+ if (stringWidth (font , fontSize , word ) <= maxWidth ) {
607+ line .append (word );
608+ } else {
609+ for (String part : hardSplitToken (font , fontSize , word , maxWidth )) {
610+ if (!part .isEmpty ()) {
611+ result .add (part );
612+ }
613+ }
614+ }
615+ }
616+ }
617+ }
618+
619+ if (line .length () > 0 ) {
620+ result .add (line .toString ());
621+ }
622+
623+ return result ;
624+ }
625+
626+ private static float stringWidth (PDFont font , float fontSize , String text ) throws IOException {
627+ return (font .getStringWidth (text ) / 1000f ) * fontSize ;
628+ }
629+
630+ private static java .util .List <String > hardSplitToken (PDFont font , float fontSize , String token , float maxWidth ) throws IOException {
631+ java .util .List <String > parts = new java .util .ArrayList <>();
632+ StringBuilder current = new StringBuilder ();
633+ for (int i = 0 ; i < token .length (); i ++) {
634+ char c = token .charAt (i );
635+ current .append (c );
636+ if (stringWidth (font , fontSize , current .toString ()) > maxWidth ) {
637+ current .setLength (current .length () - 1 );
638+ if (current .length () > 0 ) {
639+ parts .add (current .toString ());
640+ }
641+ current .setLength (0 );
642+ current .append (c );
643+ }
644+ }
645+ if (current .length () > 0 ) {
646+ parts .add (current .toString ());
647+ }
648+ return parts ;
649+ }
650+
497651 private void buildRowsAndCols (BaseTable table , JSONObject settings , JSONArray rows , JSONArray columnsOrdered , int numberOfSummaryRows , List <String > summaryRowsLabels , PDFont font , String [] columnDateFormats , Map <String , JSONArray > styles ) throws JSONException {
498652
499653 // Check if summary row is enabled
@@ -632,7 +786,7 @@ private void applyWholeRowStyle(int c, List<Boolean> styleCanBeOverriddenByWhole
632786 }
633787 }
634788 }
635-
789+
636790 private void applyStyleToCell (Style style , Cell <PDPage > cell ) {
637791 if (!style .getBackgroundColor ().isEmpty ()) {
638792 cell .setFillColor (getJavaColorFromRGBA (style .getBackgroundColor ()));
0 commit comments