Hey,

we are using PDFBox for one of our products and have a proposed
improvement for PDFBOX-3812.

The problem we are facing with the current implementation of
auto-sized multiline text fields is the following:

1. If the rectangle of the multiline text field has a reasonable height
   for a few lines and only a few characters are put in the text field
   the font will get too big, resulting in strange looking input.  If
   edited aftewards with Adobe Acrobat the font size is adjusted to 12pt
   (like the DEFAULT_FONT_SIZE).

2. Words are not broken if they are too long to fit in the width of the
   textbox.  The result is that the part not fitting is cut off.

The attached patch tries to fix these issues in the following way:

1. Use DEFAULT_FONT_SIZE instead of MAX_FONT_SIZE to calculate the
   maximum line height for multiline text fields

2. Test if a word is longer than the width if it is the only word
   present on a line.  If yes a similar alorithm to calculate the font
   size in 1. is used.

If there are any problems with the patch, please let me know.
-- 
Mit freundlichen Grüßen
Sebastian Fieber
Software Entwickler

Mentana-Claimsoft GmbH
EIN UNTERNEHMEN DER FP-GRUPPE
Griesbergstr. 8 · 31162 Bad Salzdetfurth
Trebuser Str. 47 · Haus 1 · 15517 Fürstenwalde
Tel: +49 (0)5063 277440 · Fax: +49 (0)5063 2774450
E-Mail: [email protected] · De-Mail: 
[email protected]
Web: www.mentana-claimsoft.de · Blog: fp-francotyp.com/blog

Service Center De-Mail: 01806/ Mentana (6368262)
(0,20 € pro Anruf aus dem deutschen Festnetz, max. 0,60 € pro Anruf aus
dem deutschen Mobilfunknetz)

Service Center Signaturprodukte: 01806/ Signatur (74462887)
(0,20 € pro Anruf aus dem deutschen Festnetz, max. 0,60 € pro Anruf aus
dem deutschen Mobilfunknetz)

Geschäftsführung Stephan Vanberg, Patricius de Gruyter
HRB: 13886 - Amtsgericht Frankfurt/Oder
diff --git parent/pom.xml parent/pom.xml
index 1d84cad91..afd54687b 100644
--- parent/pom.xml
+++ parent/pom.xml
@@ -54,7 +54,7 @@
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 
         <bouncycastle.version>1.64</bouncycastle.version>
-        
+
         <!-- PDFBOX-4479 to build on jdk6 on newer Jenkins -->
         <jdk.path>${env.JAVA_HOME}</jdk.path>
     </properties>
@@ -155,7 +155,7 @@
         </profile>
         <profile>
             <!-- from jdk11 onwards activation and bind are no longer part of the jdk -->
-            <!-- must be set to "test" or "provided" in subprojects --> 
+            <!-- must be set to "test" or "provided" in subprojects -->
             <id>jdk11</id>
            <activation>
                 <jdk>[11,)</jdk>
@@ -225,14 +225,18 @@
                     <encoding>UTF-8</encoding>
                     <!-- https://maven.apache.org/plugins/maven-compiler-plugin/examples/compile-using-different-jdk.html -->
                     <executable>${jdk.path}/bin/javac</executable>
-                    <fork>true</fork>
-                    <!-- enable these when getting CompilationFailureException without explanation: -->
-                    <compilerArgs>
-                        <arg>-verbose</arg>
-                        <arg>-J-Xmx1g</arg>
-                        <arg>-J-XX:PermSize=256m</arg>
-                        <arg>-J-XX:MaxPermSize=512m</arg>
-                    </compilerArgs>
+                    <!-- <fork>true</fork> -->
+                    <!-- <!-\- enable these when getting CompilationFailureException without explanation: -\-> -->
+                    <!-- <compilerArgs> -->
+                    <!--     <arg>-verbose</arg> -->
+                    <!--     <arg>-J-Xmx1g</arg> -->
+                    <!--     <arg>-J-XX:PermSize=256m</arg> -->
+                    <!--     <arg>-J-XX:MaxPermSize=512m</arg> -->
+                    <!-- </compilerArgs> -->
+                    <compilerArguments>
+                        <verbose/>
+                    </compilerArguments>
+                    <verbose>true</verbose>
                 </configuration>
             </plugin>
             <plugin>
@@ -257,51 +261,51 @@
                 </configuration>
             </plugin>
             <plugin>
-		        <groupId>org.apache.maven.plugins</groupId>
-		        <artifactId>maven-jar-plugin</artifactId>
-		        <configuration>
-		          <archive>
-		            <manifest>
-		              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
-		              <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
-		            </manifest>
-		          </archive>
-		        </configuration>
-		    </plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-jar-plugin</artifactId>
+                        <configuration>
+                          <archive>
+                            <manifest>
+                              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+                              <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+                            </manifest>
+                          </archive>
+                        </configuration>
+                    </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-source-plugin</artifactId>
                 <executions>
-				   <execution>
-				      <id>attach-sources</id>
-				      <goals>
-				        <goal>jar</goal>
-				      </goals>
-				   </execution>
-				</executions>
+                                   <execution>
+                                      <id>attach-sources</id>
+                                      <goals>
+                                        <goal>jar</goal>
+                                      </goals>
+                                   </execution>
+                                </executions>
             </plugin>
-	        <plugin>
-	            <groupId>org.codehaus.mojo</groupId>
-	            <artifactId>animal-sniffer-maven-plugin</artifactId>
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>animal-sniffer-maven-plugin</artifactId>
                     <!-- don't update, or it won't work with jdk1.7 -->
-	            <version>1.17</version>
-	            <executions>
-	               <execution>
-	                  <id>check-java-version</id>
-	                  <phase>test</phase>
-	                  <goals>
-	                    <goal>check</goal>
-	                  </goals>
-	                  <configuration>
+                    <version>1.17</version>
+                    <executions>
+                       <execution>
+                          <id>check-java-version</id>
+                          <phase>test</phase>
+                          <goals>
+                            <goal>check</goal>
+                          </goals>
+                          <configuration>
                             <skip>${skipTests}</skip>
-	                    <signature>
-	                      <groupId>org.codehaus.mojo.signature</groupId>
-	                      <artifactId>java16</artifactId>
-	                      <version>1.0</version>
-	                    </signature>
-	                  </configuration>
-	               </execution>
-	            </executions>
+                            <signature>
+                              <groupId>org.codehaus.mojo.signature</groupId>
+                              <artifactId>java16</artifactId>
+                              <version>1.0</version>
+                            </signature>
+                          </configuration>
+                       </execution>
+                    </executions>
                 </plugin>
                 <plugin>
                     <groupId>com.googlecode.maven-download-plugin</groupId>
diff --git pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
index 29160d83f..1d6957055 100644
--- pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
+++ pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
@@ -798,7 +798,7 @@ class AppearanceGeneratorHelper
                 {
                     float width = contentRect.getWidth() - contentRect.getLowerLeftX();
                     float fs = MINIMUM_FONT_SIZE;
-                    while (fs <= MAXIMUM_FONT_SIZE)
+                    while (fs <= DEFAULT_FONT_SIZE)
                     {
                         // determine the number of lines needed for this font and contentRect
                         int numLines = 0;
@@ -818,7 +818,7 @@ class AppearanceGeneratorHelper
                         }
                         fs++;
                     }
-                    return Math.min(fs, MAXIMUM_FONT_SIZE);
+                    return Math.min(fs, DEFAULT_FONT_SIZE);
                 }
 
                 // Acrobat defaults to 12 for multiline text with size 0
diff --git pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java
index af66d6282..2bf706ada 100644
--- pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java
+++ pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java
@@ -32,21 +32,21 @@ import org.apache.pdfbox.pdmodel.font.PDFont;
  * A block of text can contain multiple paragraphs which will
  * be treated individually within the block placement.
  * </p>
- * 
+ *
  */
 class PlainText
 {
     private static final float FONTSCALE = 1000f;
-    
+
     private final List<Paragraph> paragraphs;
-    
+
     /**
      * Construct the text block from a single value.
-     * 
+     *
      * Constructs the text block from a single value splitting
-     * into individual {@link Paragraph} when a new line character is 
+     * into individual {@link Paragraph} when a new line character is
      * encountered.
-     * 
+     *
      * @param textValue the text block string.
      */
     PlainText(String textValue)
@@ -55,21 +55,21 @@ class PlainText
         paragraphs = new ArrayList<Paragraph>();
         for (String part : parts)
         {
-        	// Acrobat prints a space for an empty paragraph
-        	if (part.length() == 0)
-        	{
-        		part = " ";
-        	}
+                // Acrobat prints a space for an empty paragraph
+                if (part.length() == 0)
+                {
+                        part = " ";
+                }
             paragraphs.add(new Paragraph(part));
         }
     }
-    
+
     /**
      * Construct the text block from a list of values.
-     * 
+     *
      * Constructs the text block from a list of values treating each
      * entry as an individual {@link Paragraph}.
-     * 
+     *
      * @param listValue the text block string.
      */
     PlainText(List<String> listValue)
@@ -80,23 +80,23 @@ class PlainText
             paragraphs.add(new Paragraph(part));
         }
     }
-    
+
     /**
      * Get the list of paragraphs.
-     * 
+     *
      * @return the paragraphs.
      */
     List<Paragraph> getParagraphs()
     {
         return paragraphs;
     }
-    
+
     /**
      * Attribute keys and attribute values used for text handling.
-     * 
+     *
      * This is similar to {@link java.awt.font.TextAttribute} but
      * handled individually as to avoid a dependency on awt.
-     * 
+     *
      */
     static class TextAttribute extends Attribute
     {
@@ -109,12 +109,12 @@ class PlainText
          * Attribute width of the text.
          */
         public static final Attribute WIDTH = new TextAttribute("width");
-        
+
         protected TextAttribute(String name)
         {
             super(name);
         }
-        
+
 
     }
 
@@ -124,30 +124,30 @@ class PlainText
      * A block of text can contain multiple paragraphs which will
      * be treated individually within the block placement.
      * </p>
-     * 
+     *
      */
     static class Paragraph
     {
         private final String textContent;
-        
+
         Paragraph(String text)
         {
             textContent = text;
         }
-        
+
         /**
          * Get the paragraph text.
-         * 
+         *
          * @return the text.
          */
         String getText()
         {
             return textContent;
         }
-        
+
         /**
          * Break the paragraph into individual lines.
-         * 
+         *
          * @param font the font used for rendering the text.
          * @param fontSize the fontSize used for rendering the text.
          * @param width the width of the box holding the content.
@@ -158,13 +158,13 @@ class PlainText
         {
             BreakIterator iterator = BreakIterator.getLineInstance();
             iterator.setText(textContent);
-            
+
             final float scale = fontSize/FONTSCALE;
-            
+
             int start = iterator.first();
             int end = iterator.next();
             float lineWidth = 0;
-            
+
             List<Line> textLines = new ArrayList<Line>();
             Line textLine = new Line();
 
@@ -172,7 +172,9 @@ class PlainText
             {
                 String word = textContent.substring(start,end);
                 float wordWidth = font.getStringWidth(word) * scale;
-                
+                boolean wordNeedsSplit = false;
+                int splitOffset = end - start;
+
                 lineWidth = lineWidth + wordWidth;
 
                 // check if the last word would fit without the whitespace ending it
@@ -181,22 +183,45 @@ class PlainText
                     float whitespaceWidth = font.getStringWidth(word.substring(word.length()-1)) * scale;
                     lineWidth = lineWidth - whitespaceWidth;
                 }
-                
-                if (lineWidth >= width)
+
+                if (lineWidth >= width && textLine.getWords().size() > 0)
                 {
                     textLine.setWidth(textLine.calculateWidth(font, fontSize));
                     textLines.add(textLine);
                     textLine = new Line();
                     lineWidth = font.getStringWidth(word) * scale;
                 }
-                
+
+                if (wordWidth > width && textLine.getWords().size() == 0) {
+                    // single word does not fit into width
+                    wordNeedsSplit = true;
+                    while (true)
+                    {
+                        splitOffset--;
+                        String substring = word.trim().substring(0, splitOffset);
+                        float substringWidth = font.getStringWidth(substring) * scale;
+                        if (substringWidth < width) {
+                            word = substring;
+                            wordWidth = font.getStringWidth(word) * scale;
+                            lineWidth = wordWidth;
+                            break;
+                        }
+                    }
+                }
+
+
                 AttributedString as = new AttributedString(word);
                 as.addAttribute(TextAttribute.WIDTH, wordWidth);
                 Word wordInstance = new Word(word);
                 wordInstance.setAttributes(as);
                 textLine.addWord(wordInstance);
-                start = end;
-                end = iterator.next();
+
+                if (wordNeedsSplit) {
+                    start = start + splitOffset;
+                } else {
+                    start = end;
+                    end = iterator.next();
+                }
             }
             textLine.setWidth(textLine.calculateWidth(font, fontSize));
             textLines.add(textLine);
@@ -216,19 +241,19 @@ class PlainText
         {
             return lineWidth;
         }
-        
+
         void setWidth(float width)
         {
             lineWidth = width;
         }
-        
+
         float calculateWidth(PDFont font, float fontSize) throws IOException
         {
             final float scale = fontSize/FONTSCALE;
             float calculatedWidth = 0f;
             for (Word word : words)
             {
-                calculatedWidth = calculatedWidth + 
+                calculatedWidth = calculatedWidth +
                         (Float) word.getAttributes().getIterator().getAttribute(TextAttribute.WIDTH);
                 String text = word.getText();
                 if (words.indexOf(word) == words.size() -1 && Character.isWhitespace(text.charAt(text.length()-1)))
@@ -244,7 +269,7 @@ class PlainText
         {
             return words;
         }
-        
+
         float getInterWordSpacing(float width)
         {
             return (width - lineWidth)/(words.size()-1);
@@ -255,10 +280,10 @@ class PlainText
             words.add(word);
         }
     }
-    
+
     /**
      * An individual word.
-     * 
+     *
      * A word is defined as a string which must be kept
      * on the same line.
      */
@@ -266,22 +291,22 @@ class PlainText
     {
         private AttributedString attributedString;
         private final String textContent;
-        
+
         Word(String text)
         {
             textContent = text;
         }
-        
+
         String getText()
         {
             return textContent;
         }
-        
+
         AttributedString getAttributes()
         {
             return attributedString;
         }
-        
+
         void setAttributes(AttributedString as)
         {
             this.attributedString = as;

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to