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/10 12:11:53 UTC
svn commit: r1665472 - in
/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form:
AppearanceGeneratorHelper.java AppearancePrimitivesComposer.java
AppearanceStyle.java PlainTextFormatter.java
Author: msahyoun
Date: Tue Mar 10 11:11:52 2015
New Revision: 1665472
URL: http://svn.apache.org/r1665472
Log:
PDFBOX-1402, PDFBOX-2333 start factoring out raw commands; enhance box detection
Added:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java (with props)
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.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=1665472&r1=1665471&r2=1665472&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 Tue Mar 10 11:11:52 2015
@@ -19,7 +19,6 @@ package org.apache.pdfbox.pdmodel.intera
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -31,7 +30,6 @@ import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNumber;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdfparser.PDFStreamParser;
-import org.apache.pdfbox.pdfwriter.COSWriter;
import org.apache.pdfbox.pdfwriter.ContentStreamWriter;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.COSObjectable;
@@ -263,14 +261,27 @@ class AppearanceGeneratorHelper
PDFont font, List<Object> tokens, PDAppearanceStream appearanceStream)
throws IOException
{
- PrintWriter printWriter = new PrintWriter(output, true);
+ AppearancePrimitivesComposer composer = new AppearancePrimitivesComposer(output);
float fontSize = 0.0f;
PDRectangle boundingBox = appearanceStream.getBBox();
if (boundingBox == null)
{
boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle();
}
- printWriter.println("BT");
+
+ // Acrobat calculates the left and right padding dependent on the offset of the border edge
+ // This calculation works for forms having been generated by Acrobat.
+ // The minimum distance is always 1f even if there is no rectangle being drawn around.
+ float lineWidth = getLineWidth(tokens);
+ PDRectangle paddingEdge = applyPadding(boundingBox,Math.max(1f, lineWidth));
+ PDRectangle contentEdge = applyPadding(paddingEdge,Math.max(1f, lineWidth));
+
+ // add a clipping path to avoid overlapping with the border
+ composer.addRect(paddingEdge);
+ composer.clip();
+
+ // start the text output
+ composer.beginText();
if (!defaultAppearanceHandler.getTokens().isEmpty())
{
fontSize = calculateFontSize(font, boundingBox, tokens);
@@ -279,16 +290,11 @@ class AppearanceGeneratorHelper
daWriter.writeTokens(defaultAppearanceHandler.getTokens());
}
- PDRectangle borderEdge = getSmallestDrawnRectangle(boundingBox, tokens);
-
- // Acrobat calculates the left and right padding dependent on the offset of the border edge
- // This calculation works for forms having been generated by Acrobat.
- // Need to revisit this for forms being generated with other software.
- float paddingLeft = Math.max(2, Math.round(4 * borderEdge.getLowerLeftX()));
- float paddingRight = Math.max(2,
- Math.round(4 * (boundingBox.getUpperRightX() - borderEdge.getUpperRightX())));
+ float paddingLeft = contentEdge.getLowerLeftX();
+ float paddingRight = boundingBox.getUpperRightX() - contentEdge.getUpperRightX();
+
float verticalOffset = getVerticalOffset(boundingBox, font, fontSize, tokens);
-
+
// Acrobat shifts the value so it aligns to the bottom if
// the font's caps are larger than the height of the borderEdge
//
@@ -297,10 +303,10 @@ class AppearanceGeneratorHelper
// We potentially need to revisit that calculation
float fontHeight = boundingBox.getHeight() - verticalOffset * 2;
- if (fontHeight + 2 * borderEdge.getLowerLeftX() > borderEdge.getHeight())
+ if (fontHeight + 2 * paddingEdge.getLowerLeftX() > paddingEdge.getHeight())
{
verticalOffset = font.getBoundingBox().getHeight() / 1000 * fontSize
- - borderEdge.getHeight();
+ - paddingEdge.getHeight();
}
float leftOffset = 0f;
@@ -310,7 +316,7 @@ class AppearanceGeneratorHelper
int q = getQ();
if (q == PDTextField.QUADDING_LEFT
- || stringWidth > borderEdge.getWidth() - paddingLeft - paddingRight)
+ || stringWidth > contentEdge.getWidth())
{
leftOffset = paddingLeft;
}
@@ -332,34 +338,34 @@ class AppearanceGeneratorHelper
// show the text
if (!isMultiLine())
{
- printWriter.println(leftOffset + " " + verticalOffset + " Td");
- printWriter.flush();
- COSWriter.writeString(font.encode(value), output);
- printWriter.println(" Tj");
+ composer.newLineAtOffset(leftOffset, verticalOffset);
+ composer.showText(value, font);
}
else
{
// adjust offset
// TODO: offset is dependent on border value if there is one
verticalOffset = verticalOffset + font.getFontDescriptor().getAscent() / 1000 * fontSize;
- printWriter.println(leftOffset + " " + verticalOffset + " Td");
+
+ composer.newLineAtOffset(leftOffset, verticalOffset);
+
PlainText textContent = new PlainText(value);
AppearanceStyle appearanceStyle = new AppearanceStyle();
appearanceStyle.setFont(font);
appearanceStyle.setFontSize(fontSize);
+
PlainTextFormatter formatter = new PlainTextFormatter
- .Builder(output)
+ .Builder(composer)
.style(appearanceStyle)
.text(textContent)
- .width(borderEdge.getWidth() - paddingLeft - paddingRight)
+ .width(contentEdge.getWidth())
.wrapLines(true)
.textAlign(q)
.build();
formatter.format();
}
- printWriter.println("ET");
- printWriter.flush();
+ composer.endText();
}
/*
@@ -485,13 +491,13 @@ class AppearanceGeneratorHelper
*/
private float getLineWidth(List<Object> tokens)
{
- float retval = 1;
+ float retval = 0f;
if (tokens != null)
{
int btIndex = tokens.indexOf(Operator.getOperator("BT"));
int wIndex = tokens.indexOf(Operator.getOperator("w"));
// the w should only be used if it is before the first BT.
- if ((wIndex > 0) && (wIndex < btIndex))
+ if ((wIndex > 0) && (wIndex < btIndex || btIndex == -1))
{
retval = ((COSNumber) tokens.get(wIndex - 1)).floatValue();
}
@@ -499,34 +505,6 @@ class AppearanceGeneratorHelper
return retval;
}
- private PDRectangle getSmallestDrawnRectangle(PDRectangle boundingBox, List<Object> tokens)
- {
- PDRectangle smallest = boundingBox;
- for (int i = 0; i < tokens.size(); i++)
- {
- Object next = tokens.get(i);
- if (next == Operator.getOperator("re"))
- {
- COSNumber x = (COSNumber) tokens.get(i - 4);
- COSNumber y = (COSNumber) tokens.get(i - 3);
- COSNumber width = (COSNumber) tokens.get(i - 2);
- COSNumber height = (COSNumber) tokens.get(i - 1);
- PDRectangle potentialSmallest = new PDRectangle();
- potentialSmallest.setLowerLeftX(x.floatValue());
- potentialSmallest.setLowerLeftY(y.floatValue());
- potentialSmallest.setUpperRightX(x.floatValue() + width.floatValue());
- potentialSmallest.setUpperRightY(y.floatValue() + height.floatValue());
- if (smallest == null
- || smallest.getLowerLeftX() < potentialSmallest.getLowerLeftX()
- || smallest.getUpperRightY() > potentialSmallest.getUpperRightY())
- {
- smallest = potentialSmallest;
- }
- }
- }
- return smallest;
- }
-
/**
* My "not so great" method for calculating the fontsize. It does not work superb, but it
* handles ok.
@@ -618,4 +596,19 @@ class AppearanceGeneratorHelper
{
return boundingBox.getHeight() - 2 * lineWidth;
}
+
+ /**
+ * Apply padding to a box.
+ *
+ * @param original box
+ * @return the padded box.
+ */
+ private PDRectangle applyPadding(PDRectangle box, float padding)
+ {
+ return new PDRectangle(
+ box.getLowerLeftX() +padding,
+ box.getLowerLeftY() +padding,
+ box.getWidth()-2*padding, box.getHeight()-2*padding
+ );
+ }
}
Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java?rev=1665472&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java Tue Mar 10 11:11:52 2015
@@ -0,0 +1,184 @@
+/*
+ * 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.Locale;
+
+import org.apache.pdfbox.pdfwriter.COSWriter;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.util.Charsets;
+
+/**
+ * Write the primitives making up a content stream.
+ *
+ * <strong>This class shall be treated internal use only!</strong>
+ */
+class AppearancePrimitivesComposer
+{
+ // the ouput stream to write to
+ private final OutputStream outputstream;
+
+ // number format for real number output
+ private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);
+
+ // will be set for operators doing text output
+ private boolean inTextMode = false;
+
+ AppearancePrimitivesComposer(OutputStream outputstream)
+ {
+ this.outputstream = outputstream;
+ }
+
+ /**
+ * Add a rectangle to the current path.
+ *
+ * @param rect the rectangle.
+ * @throws IOException If the content stream could not be written.
+ */
+ public void addRect(PDRectangle rect) throws IOException
+ {
+ addRect(rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getWidth(), rect.getHeight());
+ }
+
+ /**
+ * Add a rectangle to the current path.
+ *
+ * @param x The lower left x coordinate.
+ * @param y The lower left y coordinate.
+ * @param width The width of the rectangle.
+ * @param height The height of the rectangle.
+ * @throws IOException If the content stream could not be written.
+ */
+ public void addRect(float x, float y, float width, float height) throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IOException("Error: addRect is not allowed within a text block.");
+ }
+ writeOperand(x);
+ writeOperand(y);
+ writeOperand(width);
+ writeOperand(height);
+ writeOperator("re");
+ }
+
+ /**
+ * Begin some text operations.
+ *
+ * @throws IOException If there is an error writing to the stream or if you attempt to
+ * nest beginText calls.
+ */
+ public void beginText() throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IOException("Error: Nested beginText() calls are not allowed.");
+ }
+ writeOperator("BT");
+ inTextMode = true;
+ }
+
+ /**
+ * Intersects the current clipping path with the current path, using the nonzero rule.
+ *
+ * @throws IOException If the content stream could not be written
+ */
+ public void clip() throws IOException
+ {
+ if (inTextMode)
+ {
+ throw new IOException("Error: clip is not allowed within a text block.");
+ }
+ writeOperator("W");
+ writeOperator("n"); // end path without filling or stroking
+ }
+
+ /**
+ * End some text operations.
+ *
+ * @throws IOException If there is an error writing to the stream or if you attempt to
+ * nest endText calls.
+ */
+ public void endText() throws IOException
+ {
+ if (!inTextMode)
+ {
+ throw new IOException("Error: You must call beginText() before calling endText.");
+ }
+ writeOperator("ET");
+ inTextMode = false;
+ }
+
+
+ /**
+ * 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.
+ */
+ void newLineAtOffset(float tx, float ty) throws IOException
+ {
+ writeOperand(tx);
+ writeOperand(ty);
+ writeOperator("Td");
+ }
+
+ /**
+ * 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.
+ */
+ 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');
+ }
+
+ /**
+ * 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/AppearancePrimitivesComposer.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearancePrimitivesComposer.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: 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=1665472&r1=1665471&r2=1665472&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceStyle.java Tue Mar 10 11:11:52 2015
@@ -28,7 +28,7 @@ class AppearanceStyle
/**
* The font size to be used for text formatting.
*
- * Defaulting to 12 to math Acrobats default.
+ * Defaulting to 12 to match Acrobats default.
*/
private float fontSize = 12.0f;
Modified: 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=1665472&r1=1665471&r2=1665472&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/PlainTextFormatter.java Tue Mar 10 11:11:52 2015
@@ -17,18 +17,13 @@
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.
@@ -69,23 +64,19 @@ class PlainTextFormatter
}
}
-
private AppearanceStyle appearanceStyle;
private final boolean wrapLines;
private final float width;
- private final OutputStream outputstream;
+
+ private final AppearancePrimitivesComposer composer;
private final PlainText textContent;
private final TextAlign textAlignment;
-
- // number format
- private final NumberFormat formatDecimal = NumberFormat.getNumberInstance(Locale.US);
-
static class Builder
{
// required parameters
- private OutputStream outputstream;
+ private AppearancePrimitivesComposer composer;
// optional parameters
private AppearanceStyle appearanceStyle;
@@ -94,9 +85,9 @@ class PlainTextFormatter
private PlainText textContent;
private TextAlign textAlignment = TextAlign.LEFT;
- public Builder(OutputStream outputstream)
+ public Builder(AppearancePrimitivesComposer composer)
{
- this.outputstream = outputstream;
+ this.composer = composer;
}
Builder style(AppearanceStyle appearanceStyle)
@@ -147,7 +138,7 @@ class PlainTextFormatter
appearanceStyle = builder.appearanceStyle;
wrapLines = builder.wrapLines;
width = builder.width;
- outputstream = builder.outputstream;
+ composer = builder.composer;
textContent = builder.textContent;
textAlignment = builder.textAlignment;
}
@@ -174,7 +165,7 @@ class PlainTextFormatter
}
else
{
- showText(paragraph.getText(), appearanceStyle.getFont());
+ composer.showText(paragraph.getText(), appearanceStyle.getFont());
}
}
}
@@ -219,74 +210,20 @@ class PlainTextFormatter
}
float offset = -lastPos + startOffset;
- newLineAtOffset(offset, -appearanceStyle.getLeading());
+ composer.newLineAtOffset(offset, -appearanceStyle.getLeading());
lastPos = startOffset;
List<Word> words = line.getWords();
for (Word word : words)
{
- showText(word.getText(), font);
+ composer.showText(word.getText(), font);
wordWidth = (Float) word.getAttributes().getIterator().getAttribute(TextAttribute.WIDTH);
if (words.indexOf(word) != words.size() -1)
{
- newLineAtOffset(wordWidth + interWordSpacing, 0f);
+ composer.newLineAtOffset(wordWidth + interWordSpacing, 0f);
lastPos = lastPos + wordWidth + interWordSpacing;
}
}
}
}
-
- /**
- * 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(' ');
- }
}