You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ms...@apache.org on 2015/03/04 12:36:25 UTC

svn commit: r1663941 - in /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form: AppearanceGeneratorHelper.java AppearanceStyle.java PlainText.java PlainTextFormatter.java

Author: msahyoun
Date: Wed Mar  4 11:36:25 2015
New Revision: 1663941

URL: http://svn.apache.org/r1663941
Log:
PDFBOX-1402, PDFBOX-2333 initial multiline handling

Added:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java   (with props)
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java   (with props)
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java   (with props)
Modified:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java?rev=1663941&r1=1663940&r2=1663941&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java Wed Mar  4 11:36:25 2015
@@ -46,7 +46,7 @@ import org.apache.pdfbox.pdmodel.interac
 import org.apache.pdfbox.contentstream.operator.Operator;
 
 /**
- * Create the AcroForms filed appearance helper.
+ * Create the AcroForms field appearance helper.
  * <p>
  * A helper class to the {@link AppearanceGenerator} to generate update an AcroForm field appearance.
  * </p>
@@ -325,30 +325,37 @@ class AppearanceGeneratorHelper
         else
         {
             // Unknown quadding value - default to left
-            printWriter.println(paddingLeft + " " + verticalOffset + " Td");
+            leftOffset = paddingLeft;
             LOG.debug("Unknown justification value, defaulting to left: " + q);
         }
 
-        printWriter.println(leftOffset + " " + verticalOffset + " Td");
-
         // show the text
-        if (!isMultiLineValue(value) || stringWidth > borderEdge.getWidth() - paddingLeft -
-                paddingRight)
+        if (!isMultiLine())
         {
+            printWriter.println(leftOffset + " " + verticalOffset + " Td");
             printWriter.flush();
             COSWriter.writeString(font.encode(value), output); 
             printWriter.println(" Tj");
         }
         else
         {
-            String[] paragraphs = value.split("\n");
-            for (int i = 0; i < paragraphs.length; i++)
-            {
-                boolean lastLine = i == paragraphs.length - 1;
-                printWriter.flush();
-                COSWriter.writeString(font.encode(value), output);
-                printWriter.println(lastLine ? " Tj\n" : "> Tj 0 -13 Td");
-            }
+            // adjust offset
+            // TODO: offset is dependent on border value if there is one
+            verticalOffset = verticalOffset + font.getFontDescriptor().getAscent() / 1000 * fontSize -4f;
+            printWriter.println(leftOffset + " " + verticalOffset + " Td");
+            PlainText textContent = new PlainText(value);
+            AppearanceStyle appearanceStyle = new AppearanceStyle();
+            appearanceStyle.setFont(font);
+            appearanceStyle.setFontSize(fontSize);
+            PlainTextFormatter formatter = new PlainTextFormatter
+                                                .Builder(output)
+                                                    .style(appearanceStyle)
+                                                    .text(textContent)
+                                                    .width(borderEdge.getWidth() - paddingLeft - paddingRight)
+                                                    .wrapLines(true)
+                                                    .build();
+            formatter.format();
+
         }
         printWriter.println("ET");
         printWriter.flush();
@@ -453,9 +460,9 @@ class AppearanceGeneratorHelper
     }
 
     
-    private boolean isMultiLineValue(String multiLineValue)
+    private boolean isMultiLine()
     {
-        return (parent instanceof PDTextField && ((PDTextField) parent).isMultiline() && multiLineValue.contains("\n"));
+        return (parent instanceof PDTextField && ((PDTextField) parent).isMultiline());
     }
 
     /**

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java?rev=1663941&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java Wed Mar  4 11:36:25 2015
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.interactive.form;
+
+import java.io.IOException;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+
+class AppearanceStyle
+{
+    private static final float FONTSCALE = 1000f;
+
+    private PDFont font;
+    private float fontSize;
+    private float leading;
+    
+    PDFont getFont()
+    {
+        return font;
+    }
+    
+    void setFont(PDFont font)
+    {
+        this.font = font;
+    }
+    
+    float getFontSize()
+    {
+        return fontSize;
+    }
+    
+    void setFontSize(float fontSize)
+    {
+        final float scale = fontSize/FONTSCALE;
+        this.fontSize = fontSize;
+        if (leading == 0)
+        {
+            try
+            {
+                leading = font.getBoundingBox().getHeight() * scale;
+            }
+            catch (IOException e)
+            {
+                leading = fontSize * 1.2f;
+            }
+        }
+    }
+    
+    float getLeading()
+    {
+        return leading;
+    }
+    
+    void setLeading(float leading)
+    {
+        this.leading = leading;
+    }
+}
\ No newline at end of file

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java?rev=1663941&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java Wed Mar  4 11:36:25 2015
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.interactive.form;
+
+import java.io.IOException;
+import java.text.AttributedString;
+import java.text.BreakIterator;
+import java.text.AttributedCharacterIterator.Attribute;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.pdfbox.pdmodel.font.PDFont;
+
+/**
+ * A block of text.
+ * <p>
+ * 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 
+     * encountered.
+     * 
+     * @param textValue the text block string.
+     */
+    PlainText(String textValue)
+    {
+        List<String> parts = Arrays.asList(textValue.split("\\n"));
+        paragraphs = new ArrayList<Paragraph>();
+        for (String part : parts)
+        {
+            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)
+    {
+        paragraphs = new ArrayList<Paragraph>();
+        for (String part : listValue)
+        {
+            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
+    {
+        /**
+         * UID for serializing.
+         */
+        private static final long serialVersionUID = -3138885145941283005L;
+
+        /**
+         * Attribute width of the text.
+         */
+        public static final Attribute WIDTH = new TextAttribute("width");
+        
+        protected TextAttribute(String name)
+        {
+            super(name);
+        }
+        
+
+    }
+
+    /**
+     * A block of text to be formatted as a whole.
+     * <p>
+     * A block of text can contain multiple paragraphs which will
+     * be treated individually within the block placement.
+     * </p>
+     * 
+     */
+    static class Paragraph
+    {
+        private 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.
+         * @return the individual lines.
+         * @throws IOException
+         */
+        List<Line> getLines(PDFont font, float fontSize, float width) throws IOException
+        {
+            BreakIterator iterator = BreakIterator.getLineInstance();
+            iterator.setText(textContent);
+            
+            final float scale = fontSize/FONTSCALE;
+            
+            int start = iterator.first();
+            int end = iterator.next();
+            float lineWidth = 0;
+            float wordWidth = 0f;
+            
+            List<Line> textLines = new ArrayList<Line>();
+            Line textLine =  new Line();
+
+            while (end != BreakIterator.DONE)
+            {
+                String word = textContent.substring(start,end);
+                wordWidth = font.getStringWidth(word) * scale;
+                
+                lineWidth = lineWidth + wordWidth;
+
+                // check if the last word would fit without the whitespace ending it
+                if (lineWidth >= width && Character.isWhitespace(word.charAt(word.length()-1)))
+                {
+                    float whitespaceWidth = font.getStringWidth(word.substring(word.length()-1)) * scale;
+                    lineWidth = lineWidth - whitespaceWidth;
+                }
+                
+                if (lineWidth >= width)
+                {
+                    textLine.setWidth(lineWidth);
+                    textLines.add(textLine);
+                    textLine = new Line();
+                    lineWidth = font.getStringWidth(word) * scale;
+                }
+                
+                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();
+            }
+            textLines.add(textLine);
+            return textLines;
+        }
+    }
+
+    /**
+     * An individual line of text.
+     */
+    static class Line
+    {
+        private List<Word> words = new ArrayList<Word>();
+        private float lineWidth;
+
+        float getWidth()
+        {
+            return lineWidth;
+        }
+        
+        void setWidth(float width)
+        {
+            lineWidth = width;
+        }
+
+        List<Word> getWords()
+        {
+            return words;
+        }
+
+        void addWord(Word word)
+        {
+            words.add(word);
+        }
+    }
+    
+    /**
+     * An individual word.
+     * 
+     * A word is defined as a string which must be kept
+     * on the same line.
+     */
+    static class Word
+    {
+        private AttributedString attributedString;
+        private String textContent;
+        
+        Word(String text)
+        {
+            textContent = text;
+        }
+        
+        String getText()
+        {
+            return textContent;
+        }
+        
+        AttributedString getAttributes()
+        {
+            return attributedString;
+        }
+        
+        void setAttributes(AttributedString as)
+        {
+            this.attributedString = as;
+        }
+    }
+}

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainText.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java?rev=1663941&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java Wed Mar  4 11:36:25 2015
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.pdmodel.interactive.form;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.interactive.form.PlainText.Line;
+import org.apache.pdfbox.pdmodel.interactive.form.PlainText.Paragraph;
+import org.apache.pdfbox.pdmodel.interactive.form.PlainText.TextAttribute;
+import org.apache.pdfbox.pdmodel.interactive.form.PlainText.Word;
+import org.apache.pdfbox.util.Charsets;
+
+/**
+ * TextFormatter to handle plain text formatting.
+ * 
+ * The text formatter will take a single value or an array of values which
+ * are treated as paragraphs.
+ */
+
+class PlainTextFormatter
+{
+    private AppearanceStyle appearanceStyle;
+    private final boolean wrapLines;
+    private final float width;
+    private final OutputStream outputstream;
+    private final PlainText textContent;
+    
+    // number format
+    private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);
+    
+    static class Builder
+    {
+
+        // required parameters
+        private OutputStream outputstream;
+
+        // optional parameters
+        private AppearanceStyle appearanceStyle;
+        private boolean wrapLines = false;
+        private float width = 0f;
+        private PlainText textContent;
+        
+        public Builder(OutputStream outputstream)
+        {
+            this.outputstream = outputstream;
+        }
+
+        Builder style(AppearanceStyle appearanceStyle)
+        {
+            this.appearanceStyle = appearanceStyle;
+            return this;
+        }
+        
+        Builder wrapLines(boolean wrapLines)
+        {
+            this.wrapLines = wrapLines;
+            return this;
+        }
+
+        Builder width(float width)
+        {
+            this.width = width;
+            return this;
+        }
+
+        Builder text(PlainText textContent)
+        {
+            this.textContent  = textContent;
+            return this;
+        }        
+        
+        PlainTextFormatter build()
+        {
+            return new PlainTextFormatter(this);
+        }
+    }
+    
+    private PlainTextFormatter(Builder builder)
+    {
+        appearanceStyle = builder.appearanceStyle;
+        wrapLines = builder.wrapLines;
+        width = builder.width;
+        outputstream = builder.outputstream;
+        textContent = builder.textContent;
+    }
+    
+    /**
+     * Format the text block.
+     * 
+     * @throws IOException if there is an error writing to the stream.
+     */
+    public void format() throws IOException
+    {
+        if (textContent != null && !textContent.getParagraphs().isEmpty())
+        {
+            for (Paragraph paragraph : textContent.getParagraphs())
+            {
+                if (wrapLines)
+                {
+                    List<Line> lines = paragraph.getLines(
+                                appearanceStyle.getFont(), 
+                                appearanceStyle.getFontSize(), 
+                                width
+                            );
+                    processLines(lines);
+                }
+                else
+                {
+                    showText(paragraph.getText(), appearanceStyle.getFont());
+                }
+            }
+        }
+    }
+
+    /**
+     * Process lines for output. 
+     *
+     * Process lines for an individual paragraph and generate the 
+     * commands for the content stream to show the text.
+     * 
+     * @param lines the lines to process.
+     * @throws IOException if there is an error writing to the stream.
+     */
+    private void processLines(List<Line> lines) throws IOException
+    {
+        PDFont font = appearanceStyle.getFont();
+        float wordWidth = 0f;
+        
+        for (Line line : lines)
+        {
+           float offset = 0f;
+           List<Word> words = line.getWords();
+           for (Word word : words)
+           {
+               showText(word.getText(), font);
+               wordWidth = (Float) word.getAttributes().getIterator().getAttribute(TextAttribute.WIDTH);
+               
+               if (words.indexOf(word) != words.size() -1)
+               {
+                   newLineAtOffset(wordWidth, 0f);
+                   offset = offset + wordWidth;
+               }
+           }
+           if (lines.indexOf(line) != lines.size()-1)
+           {
+               newLineAtOffset(-offset, -appearanceStyle.getLeading());
+           }
+        }
+    }
+    
+    /**
+     * Shows the given text at the location specified by the current text matrix.
+     *
+     * @param text The Unicode text to show.
+     * @throws IOException if there is an error writing to the stream.
+     */
+    private void showText(String text, PDFont font) throws IOException
+    {
+        COSWriter.writeString(font.encode(text), outputstream);
+        write(" ");
+        writeOperator("Tj");
+    }
+    
+    /**
+     * Writes a string to the content stream as ASCII.
+     */
+    private void write(String text) throws IOException
+    {
+        outputstream.write(text.getBytes(Charsets.US_ASCII));
+    }
+    
+    /**
+     * Writes a string to the content stream as ASCII.
+     */
+    private void writeOperator(String text) throws IOException
+    {
+        outputstream.write(text.getBytes(Charsets.US_ASCII));
+        outputstream.write('\n');
+    }
+    
+    /**
+     * The Td operator.
+     * Move to the start of the next line, offset from the start of the current line by (tx, ty).
+     *
+     * @param tx The x translation.
+     * @param ty The y translation.
+     * @throws IOException if there is an error writing to the stream.
+     */
+    public void newLineAtOffset(float tx, float ty) throws IOException
+    {
+        writeOperand(tx);
+        writeOperand(ty);
+        writeOperator("Td");
+    }
+    
+    /**
+     * Writes a real real to the content stream.
+     */
+    private void writeOperand(float real) throws IOException
+    {
+        write(formatDecimal.format(real));
+        outputstream.write(' ');
+    }
+}

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain