5
5
import java .io .*;
6
6
import java .net .URL ;
7
7
import java .util .ArrayList ;
8
+ import java .util .HashSet ;
8
9
import java .util .List ;
10
+ import java .util .Set ;
9
11
import java .util .concurrent .ConcurrentHashMap ;
10
12
11
13
import com .genexus .CommonUtil ;
19
21
import com .google .zxing .BarcodeFormat ;
20
22
import com .google .zxing .common .BitMatrix ;
21
23
import com .google .zxing .oned .Code128Writer ;
24
+
22
25
import org .apache .pdfbox .cos .*;
23
26
import org .apache .pdfbox .io .IOUtils ;
24
27
import org .apache .pdfbox .pdmodel .*;
25
28
import org .apache .pdfbox .pdmodel .common .PDRectangle ;
26
29
import org .apache .pdfbox .pdmodel .font .*;
30
+ import org .apache .pdfbox .pdmodel .graphics .color .PDColor ;
31
+ import org .apache .pdfbox .pdmodel .graphics .color .PDDeviceRGB ;
27
32
import org .apache .pdfbox .pdmodel .graphics .form .PDFormXObject ;
28
33
import org .apache .pdfbox .pdmodel .graphics .image .LosslessFactory ;
29
34
import org .apache .pdfbox .pdmodel .graphics .image .PDImageXObject ;
32
37
import org .apache .pdfbox .pdmodel .interactive .viewerpreferences .PDViewerPreferences ;
33
38
import org .apache .pdfbox .util .Matrix ;
34
39
40
+ import org .jsoup .Jsoup ;
41
+ import org .jsoup .nodes .Document ;
42
+ import org .jsoup .nodes .Element ;
43
+ import org .jsoup .nodes .Node ;
44
+ import org .jsoup .select .Elements ;
45
+
35
46
public class PDFReportPDFBox extends GXReportPDFCommons {
36
47
private PDRectangle pageSize ;
37
48
private PDFont baseFont ;
@@ -49,6 +60,10 @@ public class PDFReportPDFBox extends GXReportPDFCommons{
49
60
public int runDirection = 0 ;
50
61
private int page ;
51
62
63
+ private final float DEFAULT_PDFBOX_LEADING = 1.2f ;
64
+
65
+ private Set <String > supportedHTMLTags = new HashSet <>();
66
+
52
67
static {
53
68
log = org .apache .logging .log4j .LogManager .getLogger (PDFReportPDFBox .class );
54
69
}
@@ -594,7 +609,38 @@ else if (valign == PDFReportPDFBox.VerticalAlign.BOTTOM.value())
594
609
boolean autoResize = (align & 256 ) == 256 ;
595
610
596
611
if (htmlformat == 1 ) {
597
- //As for now, HTML printing is not supported
612
+ log .debug ("WARNING: HTML rendering is not natively supported by PDFBOX 2.0.27. Handcrafted support is provided but it is not intended to cover all possible use cases" );
613
+ try {
614
+ bottomAux = (float )convertScale (bottom );
615
+ topAux = (float )convertScale (top );
616
+ float drawingPageHeight = this .pageSize .getUpperRightY () - topMargin - bottomMargin ;
617
+
618
+ float llx = leftAux + leftMargin ;
619
+ float lly = drawingPageHeight - bottomAux ;
620
+ float urx = rightAux + leftMargin ;
621
+ float ury = drawingPageHeight - topAux ;
622
+
623
+ // Define the rectangle where the content will be displayed
624
+ PDRectangle htmlRectangle = new PDRectangle ();
625
+ htmlRectangle .setLowerLeftX (llx );
626
+ htmlRectangle .setLowerLeftY (lly );
627
+ htmlRectangle .setUpperRightX (urx );
628
+ htmlRectangle .setUpperRightY (ury );
629
+ cb .addRect (llx , lly , urx - llx , ury - lly );
630
+ cb .stroke ();
631
+ SpaceHandler spaceHandler = new SpaceHandler (htmlRectangle .getUpperRightY (), htmlRectangle .getHeight ());
632
+
633
+ loadSupportedHTMLTags ();
634
+
635
+ Document document = Jsoup .parse (sTxt );
636
+ Elements allElements = document .getAllElements ();
637
+ for (Element element : allElements )
638
+ if (this .supportedHTMLTags .contains (element .normalName ()))
639
+ processHTMLElement (cb , htmlRectangle , spaceHandler , element );
640
+
641
+ } catch (Exception e ) {
642
+ log .error ("GxDrawText failed to print HTML text : " , e );
643
+ }
598
644
}
599
645
else
600
646
if (barcodeType != null ){
@@ -801,11 +847,191 @@ else if (valign == PDFReportPDFBox.VerticalAlign.BOTTOM.value())
801
847
}
802
848
}
803
849
}
804
- } catch (IOException ioe ){
850
+ } catch (Exception ioe ){
805
851
log .error ("GxDrawText failed: " , ioe );
806
852
}
807
853
}
808
854
855
+ private void loadSupportedHTMLTags (){
856
+ this .supportedHTMLTags .add ("p" );
857
+ this .supportedHTMLTags .add ("ol" );
858
+ this .supportedHTMLTags .add ("ul" );
859
+ this .supportedHTMLTags .add ("div" );
860
+ this .supportedHTMLTags .add ("h1" );
861
+ this .supportedHTMLTags .add ("h2" );
862
+ this .supportedHTMLTags .add ("h3" );
863
+ this .supportedHTMLTags .add ("img" );
864
+ this .supportedHTMLTags .add ("a" );
865
+ }
866
+
867
+ private void processHTMLElement (PDPageContentStream cb , PDRectangle htmlRectangle , SpaceHandler spaceHandler , Element blockElement ) throws Exception {
868
+ String tagName = blockElement .normalName ();
869
+ PDFont htmlFont = PDType1Font .TIMES_ROMAN ;
870
+
871
+ if (tagName .equals ("div" )) {
872
+ for (Node child : blockElement .childNodes ())
873
+ if (child instanceof Element )
874
+ processHTMLElement (cb , htmlRectangle , spaceHandler , (Element ) child );
875
+ }
876
+
877
+ if (spaceHandler .getAvailableSpace () <= 0 ){
878
+ log .error ("You ran out of available space while rendering HTML" );
879
+ return ;
880
+ }
881
+
882
+ float lineHeight = (PDType1Font .TIMES_ROMAN .getFontDescriptor ().getFontBoundingBox ().getHeight () / 1000 * fontSize ) * DEFAULT_PDFBOX_LEADING ;
883
+ float leading = (float )(Double .valueOf (props .getGeneralProperty (Const .LEADING )).doubleValue ());
884
+
885
+ float llx = htmlRectangle .getLowerLeftX ();
886
+ float lly = htmlRectangle .getLowerLeftY ();
887
+ float urx = htmlRectangle .getUpperRightX ();
888
+
889
+ float fontSize = 16f ; // Default font size for the HTML <p> tag
890
+ cb .setFont (htmlFont , 16f );
891
+ this .fontBold = false ;
892
+ if (tagName .equals ("h1" )){
893
+ cb .setFont (htmlFont , 32f );
894
+ fontSize = 32f ;
895
+ tagName = "h" ;
896
+ } else if (tagName .equals ("h2" )){
897
+ cb .setFont (htmlFont , 24f );
898
+ fontSize = 24f ;
899
+ tagName = "h" ;
900
+ } else if (tagName .equals ("h3" )){
901
+ cb .setFont (htmlFont , 18.72f );
902
+ fontSize = 18.72f ;
903
+ tagName = "h" ;
904
+ } else if (tagName .equals ("h4" )){
905
+ cb .setFont (htmlFont , 16f );
906
+ fontSize = 16.5f ;
907
+ tagName = "h" ;
908
+ }
909
+ if (tagName .equals ("h" )){
910
+ this .fontBold = true ;
911
+ float lines = renderHTMLContent (cb , blockElement .text (), fontSize , llx , lly , urx , spaceHandler .getCurrentYPosition ());
912
+ float totalTextHeight = lineHeight * lines * DEFAULT_PDFBOX_LEADING * leading ;
913
+ spaceHandler .setCurrentYPosition (spaceHandler .getCurrentYPosition () - totalTextHeight );
914
+ } else if (tagName .equals ("p" )) {
915
+ float lines = renderHTMLContent (cb , blockElement .text (), fontSize , llx , lly , urx , spaceHandler .getCurrentYPosition ());
916
+ float totalTextHeight = lineHeight * lines * DEFAULT_PDFBOX_LEADING * leading ;
917
+ spaceHandler .setCurrentYPosition (spaceHandler .getCurrentYPosition () - totalTextHeight );
918
+ } else if (tagName .equals ("ul" ) || tagName .equals ("ol" )){
919
+ int i = 0 ;
920
+ for (Element listItem : blockElement .select ("li" )){
921
+ String text = (tagName .equals ("ul" )) ? "• " + listItem .text () : i + ". " + listItem .text ();
922
+ i ++;
923
+ float lines = renderHTMLContent (cb , text , fontSize , llx , lly , urx , spaceHandler .getCurrentYPosition ());
924
+ float totalTextHeight = lineHeight * lines * DEFAULT_PDFBOX_LEADING ;
925
+ spaceHandler .setCurrentYPosition (spaceHandler .getCurrentYPosition () - totalTextHeight );
926
+ }
927
+ } else if (tagName .equals ("a" )){
928
+ cb .setNonStrokingColor (new Color (0 , 0 , 255 ));
929
+ float lines = renderHTMLContent (cb , blockElement .attr ("href" ), fontSize , llx , lly , urx , spaceHandler .getCurrentYPosition ());
930
+ float totalTextHeight = lineHeight * lines * DEFAULT_PDFBOX_LEADING * leading ;
931
+ spaceHandler .setCurrentYPosition (spaceHandler .getCurrentYPosition () - totalTextHeight );
932
+ cb .setStrokingColor (new Color (0 , 0 , 0 ));
933
+ } else if (tagName .equals ("img" )){
934
+ String bitmap = blockElement .attr ("src" );
935
+ PDImageXObject image ;
936
+
937
+ try {
938
+ if (!NativeFunctions .isWindows () && new File (bitmap ).isAbsolute () && bitmap .startsWith (httpContext .getStaticContentBase ()))
939
+ bitmap = bitmap .replace (httpContext .getStaticContentBase (), "" );
940
+ if (!new File (bitmap ).isAbsolute () && !bitmap .toLowerCase ().startsWith ("http:" ) && !bitmap .toLowerCase ().startsWith ("https:" )) {
941
+ if (bitmap .startsWith (httpContext .getStaticContentBase ()))
942
+ bitmap = bitmap .replace (httpContext .getStaticContentBase (), "" );
943
+ image = PDImageXObject .createFromFile (defaultRelativePrepend + bitmap ,document );
944
+ if (image == null ) {
945
+ bitmap = webAppDir + bitmap ;
946
+ image = PDImageXObject .createFromFile (bitmap ,document );
947
+ }
948
+ else
949
+ bitmap = defaultRelativePrepend + bitmap ;
950
+ }
951
+ else
952
+ image = PDImageXObject .createFromFile (bitmap ,document );
953
+ } catch (java .lang .IllegalArgumentException | FileNotFoundException e ) {
954
+ URL url = new java .net .URL (bitmap );
955
+ image = PDImageXObject .createFromByteArray (document , IOUtils .toByteArray (url .openStream ()),bitmap );
956
+ }
957
+ cb .drawImage (image , llx , spaceHandler .getCurrentYPosition () - image .getHeight (), image .getWidth (), image .getHeight ());
958
+ spaceHandler .setCurrentYPosition (spaceHandler .getCurrentYPosition () - image .getHeight () - 10f );
959
+ }
960
+
961
+ float availableSpace = spaceHandler .getCurrentYPosition () - lly ;
962
+ spaceHandler .setAvailableSpace (availableSpace );
963
+ }
964
+
965
+ private class SpaceHandler {
966
+ float currentYPosition ;
967
+ float availableSpace ;
968
+
969
+ public SpaceHandler (float currentYPosition , float availableSpace ) {
970
+ this .currentYPosition = currentYPosition ;
971
+ this .availableSpace = availableSpace ;
972
+ }
973
+
974
+ public float getCurrentYPosition () {
975
+ return currentYPosition ;
976
+ }
977
+
978
+ public void setCurrentYPosition (float currentYPosition ) {
979
+ this .currentYPosition = currentYPosition ;
980
+ }
981
+
982
+ public float getAvailableSpace () {
983
+ return availableSpace ;
984
+ }
985
+
986
+ public void setAvailableSpace (float availableSpace ) {
987
+ this .availableSpace = availableSpace ;
988
+ }
989
+ }
990
+
991
+ private float renderHTMLContent (PDPageContentStream contentStream , String text , float fontSize , float llx , float lly , float urx , float ury ) {
992
+ try {
993
+ PDFont defaultHTMLFont = PDType1Font .TIMES_ROMAN ;
994
+ List <String > lines = new ArrayList <>();
995
+ String [] words = text .split (" " );
996
+ StringBuilder currentLine = new StringBuilder ();
997
+ for (String word : words ) {
998
+ float currentLineWidth = defaultHTMLFont .getStringWidth (currentLine + " " + word ) / 1000 * fontSize ;
999
+ if (currentLineWidth < urx - llx ) {
1000
+ if (currentLine .length () > 0 ) {
1001
+ currentLine .append (" " );
1002
+ }
1003
+ currentLine .append (word );
1004
+ } else {
1005
+ lines .add (currentLine .toString ());
1006
+ currentLine .setLength (0 );
1007
+ currentLine .append (word );
1008
+ }
1009
+ }
1010
+ lines .add (currentLine .toString ());
1011
+
1012
+ float leading = lines .size () == 1 ? fontSize : DEFAULT_PDFBOX_LEADING * fontSize ;
1013
+ float startY = ury ;
1014
+
1015
+ if (fontSize > 16f ){
1016
+ contentStream .setLineWidth (fontSize * 0.05f );
1017
+ contentStream .setRenderingMode (RenderingMode .FILL_STROKE );
1018
+ }
1019
+ contentStream .beginText ();
1020
+ float lineHeight = (defaultHTMLFont .getFontDescriptor ().getFontBoundingBox ().getUpperRightY () - defaultHTMLFont .getFontDescriptor ().getFontBoundingBox ().getLowerLeftY ())/ 1000 * fontSize ;
1021
+ contentStream .newLineAtOffset (llx , startY );
1022
+ for (String line : lines ) {
1023
+ contentStream .showText (line );
1024
+ startY = startY - leading - lineHeight ;
1025
+ contentStream .newLineAtOffset (0 , startY );
1026
+ }
1027
+ contentStream .endText ();
1028
+ return lines .size ();
1029
+ } catch (IOException ioe ) {
1030
+ log .error ("failed to draw wrapped text: " , ioe );
1031
+ return -1 ;
1032
+ }
1033
+ }
1034
+
809
1035
private void resolveTextStyling (PDPageContentStream contentStream , String text , float x , float y , boolean isWrapped ){
810
1036
try {
811
1037
if (this .fontBold && this .fontItalic ){
@@ -836,6 +1062,7 @@ private void resolveTextStyling(PDPageContentStream contentStream, String text,
836
1062
contentStream .endText ();
837
1063
contentStream .setLineWidth (1f ); // Default line width for PDFBox 2.0.27
838
1064
contentStream .setRenderingMode (RenderingMode .FILL ); // Default text rendering mode for PDFBox 2.0.27
1065
+ contentStream .moveTo (x ,y );
839
1066
} catch (IOException ioe ) {
840
1067
log .error ("failed to apply text styling: " , ioe );
841
1068
}
@@ -861,7 +1088,7 @@ private void showWrappedTextAligned(PDPageContentStream contentStream, PDFont fo
861
1088
}
862
1089
lines .add (currentLine .toString ());
863
1090
864
- float leading = lines .size () == 1 ? fontSize : 1.2f * fontSize ;
1091
+ float leading = lines .size () == 1 ? fontSize : DEFAULT_PDFBOX_LEADING * fontSize ;
865
1092
float totalTextHeight = fontSize * lines .size () + leading * (lines .size () - 1 );
866
1093
float startY = lines .size () == 1 ? lly + (ury - lly - totalTextHeight ) / 2 : lly + (ury - lly - totalTextHeight ) / 2 + (lines .size () - 1 ) * (fontSize + leading ) + font .getFontDescriptor ().getDescent () / 1000 * fontSize ;
867
1094
0 commit comments