我需要使用iText以编程方式将页眉和页脚添加到现有的基于表单的PDF中。现有的PDF来自用户,它不包含页眉和页脚的空间。因此,解决方案是通过将现有PDF的内容与页眉和页脚连接来创建新的PDF。但是,此方法仅适用于不包含任何形式的PDF。对于包含AcroForm或XFA表单的交互式PDF,它失败如下:(1)AcroForm在新PDF中变得扁平化。 (2)XFA表单根本不导入 - 新PDF显示“请稍候...如果此消息最终未被文档的正确内容替换,则您的PDF查看器可能无法显示此类文档。 ..“。
这是我的代码:
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.PdfGState;
import com.lowagie.text.pdf.PdfStamper;
import com.lowagie.text.FontFactory;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.ColumnText;
import com.lowagie.text.pdf.PdfImportedPage;
public class PdfFormCopyTest {
private static final String ACRO_FORM_PDF = "AcroForm.pdf";
private static final String XFA_FORM_PDF = "XfaForm.pdf";
private static final String NO_FORM_PDF = "NoForm.pdf";
private static final String ACRO_FORM_PDF_NEW = "AcroForm-new.pdf";
private static final String XFA_FORM_PDF_NEW = "XfaForm-new.pdf";
private static final String NO_FORM_PDF_NEW = "NoForm-new.pdf";
private static final float MARGIN_LEFT = 36.0f;
private static final float MARGIN_RIGHT = 36.0f;
private static final float MARGIN_BOTTOM = 56.0f;
private static final float MARGIN_TOP = 36.0f;
private static final float FONT_SIZE = 10.0f;
private static final float MIN_LINE_HEIGHT = FONT_SIZE * 1.5f;
/**
* @param args
*/
public static void main(String[] args) {
try {
createPdfFromAcroFormBasedPdf();
createPdfFromXfaFormBasedPdf();
createPdfFromFormlessPdf();
}
catch (Exception error) {
System.out.println(error.getMessage());
}
}
private static void createPdfFromAcroFormBasedPdf() throws IOException, DocumentException {
System.out.println("Creating new PDF from an existing PDF containing AcroForm.....");
PdfReader reader = new PdfReader(ACRO_FORM_PDF);
createNewPdfWithHeaderFooter(reader, ACRO_FORM_PDF_NEW);
System.out.println("Success");
}
private static void createPdfFromXfaFormBasedPdf() throws IOException, DocumentException {
System.out.println("Creating new PDF from an existing PDF containing XfaForm......");
PdfReader reader = new PdfReader(XFA_FORM_PDF);
createNewPdfWithHeaderFooter(reader, XFA_FORM_PDF_NEW);
System.out.println("Success");
}
private static void createPdfFromFormlessPdf() throws IOException, DocumentException {
System.out.println("Creating new PDF from an existing PDF containing no form......");
PdfReader reader = new PdfReader(NO_FORM_PDF);
createNewPdfWithHeaderFooter(reader, NO_FORM_PDF_NEW);
System.out.println("Success");
}
/**
* Creates a new PDF which contains header and footer from the specified input PdfReader object
* and saves the result as the specified output file.
* @param reader A PdfReader for the existing PDF.
* @param outputFileName Name of the PDF file which contains header and footer.
* @throws IOException
* @throws DocumentException
*/
private static void createNewPdfWithHeaderFooter(PdfReader reader, String outputFileName)
throws IOException, DocumentException {
String footer = getFooter();
String header = getHeader();
List<Float> footerHeights = computeHeights(footer, reader, Font.NORMAL);
List<Float> headerHeights = computeHeights(header, reader, Font.BOLD);
InputStream resizedPdfStream = createPdfWithHeaderFooterSpace(reader, footerHeights, headerHeights);
PdfStamper stamper = null;
try {
FileOutputStream fos = new FileOutputStream(outputFileName);
PdfReader newReader = new PdfReader(resizedPdfStream);
stamper = new PdfStamper(newReader, fos);
int numberOfPages = stamper.getReader().getNumberOfPages();
for (int pageNumber = 1; pageNumber <= numberOfPages; pageNumber++) {
Rectangle rect = stamper.getReader().getPageSize(pageNumber);
PdfContentByte pageContent = stamper.getOverContent(pageNumber);
pageContent.saveState();
pageContent.setGState(new PdfGState());
renderHeaderFooter(rect, pageContent, header, footer);
pageContent.restoreState();
}
}
finally {
if (stamper != null) {
stamper.close();
}
}
}
/**
* Computes the height of the specified content for each page
* in the specified PdfReader with the specified font weight.
* @param content The string content for which the height of each page is computed.
* @param reader A PdfReader containing the existing PDF.
* @param fontWeight The font weight.
* @return A list of float representing the height of each page.
* @throws IOException
* @throws DocumentException
*/
private static List<Float> computeHeights(String content, PdfReader reader, int fontWeight)
throws IOException, DocumentException {
List<Float> contentHeights = new ArrayList<Float>();
int numberOfPages = reader.getNumberOfPages();
for (int pageNumber = 1; pageNumber <= numberOfPages; pageNumber++) {
Rectangle pageSize = reader.getPageSize(pageNumber);
float height = computeWrappedTextHeight(content, pageSize.getWidth(), fontWeight);
contentHeights.add(pageNumber - 1, height);
}
return contentHeights;
}
/**
* Creates a new PDF with place holder for header and footer from the specified parameters.
* @param reader The PdfReader storing the contents of the PDF to be created.
* @param footerHeights The footer height for each page.
* @param headerHeights The header height for each page.
* @return An InputStream representing the new PDF.
* @throws IOException
* @throws DocumentException
*/
private static InputStream createPdfWithHeaderFooterSpace(PdfReader reader,
List<Float> footerHeights, List<Float> headerHeights) throws IOException, DocumentException {
ByteArrayOutputStream baos = null;
Document newDocument = null;
try {
baos = new ByteArrayOutputStream();
newDocument = new Document();
PdfWriter newPdfWriter = PdfWriter.getInstance(newDocument, baos);
PdfContentByte newPdfCanvas = null;
int numberOfPages = reader.getNumberOfPages();
for (int pageNumber = 1; pageNumber <= numberOfPages; pageNumber++) {
Rectangle oldPageSize = reader.getPageSize(pageNumber);
float oldPageWidth = oldPageSize.getWidth();
float oldPageHeight = oldPageSize.getHeight();
float footerHeight = footerHeights.get(pageNumber - 1);
float headerHeight = headerHeights.get(pageNumber - 1);
float newPageHeight = calculateNewPageHeight(oldPageHeight, headerHeight, footerHeight);
float newPageWidth = calculateNewPageWidth(oldPageWidth);
Rectangle newPageSize = new Rectangle(0, 0, newPageWidth, newPageHeight);
newDocument.setPageSize(newPageSize);
if (!newDocument.isOpen()) {
newDocument.open();
newPdfCanvas = newPdfWriter.getDirectContent();
}
float xFactor = 1.0f;
float yFactor = 1.0f;
float xOffset = MARGIN_LEFT;
float yOffset = MARGIN_BOTTOM + footerHeight;
PdfImportedPage importedPage = newPdfWriter.getImportedPage(reader, pageNumber);
newPdfCanvas.addTemplate(importedPage, xFactor, 0, 0, yFactor, xOffset, yOffset);
newDocument.newPage();
}
}
finally {
if (newDocument != null && newDocument.isOpen()) {
newDocument.close();
}
}
return new ByteArrayInputStream(baos.toByteArray());
}
/**
* Computes the height of the specified string content which must
* wrap at the specified maximum line width with the specified font weight.
* @param content The string content for which the height is computed.
* @param maxLineWidth The maximum line width at which the content must wrap.
* @param fontWeight The font weight.
* @return The height of the specified content which wraps at
* the specified maximum line width with the specified font weight.
*/
private static float computeWrappedTextHeight(String content, float maxLineWidth, int fontWeight) {
float totalHeight = 0.0f;
Font font = FontFactory.getFont(BaseFont.HELVETICA, FONT_SIZE);
font.setStyle(fontWeight);
BaseFont baseFont = font.getCalculatedBaseFont(true);
String lineText = "";
int currentWordStart = -1;
float lineHeight;
for (int charIndex = 0; charIndex < content.length(); charIndex++) {
String currentChar = content.substring(charIndex, charIndex + 1);
lineText = lineText + currentChar;
boolean isCurrentCharWordSeparator = isWordSeperator(currentChar);
float lineWidth = computeLineWidth(lineText, baseFont);
if (charIndex == 0 || (!isCurrentCharWordSeparator && currentWordStart < 0)) {
currentWordStart = charIndex;
}
if (lineWidth > maxLineWidth || currentChar.equals("\n")) {
// Start a new line.
if (isCurrentCharWordSeparator) {
// The current character is a word separator - break the line at the current character.
lineHeight = computeLineHeight(lineText, baseFont);
// Reset line text.
if (currentChar.equals("\n")) {
lineText = "";
}
else {
lineText = currentChar;
}
}
else {
// The current character is in the middle of a word - break the line at the previous word separator.
int lineEnd = lineText.length() - (charIndex - currentWordStart) - 1;
if (lineEnd > 0) {
String currentWordExcludedLineText = lineText.substring(0, lineEnd);
lineHeight = computeLineHeight(currentWordExcludedLineText, baseFont);
charIndex = currentWordStart; // New line starts at the beginning of the current word.
lineText = "";
}
else {
lineHeight = computeLineHeight(lineText, baseFont);
lineText = currentChar;
}
}
totalHeight = totalHeight + lineHeight;
}
// If it is at a new word break, reset the current word starting index so that
// the next iteration can set it at the beginning of the next word.
if (charIndex > 0 && isCurrentCharWordSeparator && currentWordStart >= 0) {
currentWordStart = -1;
}
}
lineHeight = computeLineHeight(lineText, baseFont);
totalHeight = totalHeight + lineHeight;
return totalHeight;
}
/**
* Determines if the specified string is a word separator.
* @param c The string to test.
* @return true if the specified string is a word separator; false othewise.
*/
private static boolean isWordSeperator(String c) {
return (c.equals("\n") || c.equals("\t") || c.equals(" "));
}
/**
* Computes the line width of the specified line text with the specified base font.
* @param lineText The line text.
* @param baseFont A BaseFont object representing the base font of the line.
* @return A float representing the width of the line.
*/
private static float computeLineWidth(String lineText, BaseFont baseFont) {
return baseFont.getWidthPoint(lineText, FONT_SIZE);
}
/**
* Computes the line height with the specified parameters.
* @param lineText The line text.
* @param baseFont A BaseFont object representing the base font of the line.
* @return A float value representing the height of the line.
*/
private static float computeLineHeight(String lineText, BaseFont baseFont) {
float lineHeight = baseFont.getAscentPoint(lineText, FONT_SIZE) - baseFont.getDescentPoint(lineText, FONT_SIZE);
if (lineHeight < MIN_LINE_HEIGHT) {
lineHeight = MIN_LINE_HEIGHT;
}
return lineHeight;
}
/**
* Renders the header and footer to the specified Rectangle with the specified page content, header and footer.
* @param rect A Rectangle to render the header and footer.
* @param pageContent A PdfContentByte representing the content of the page.
* @param header The page header.
* @param footer The page footer.
* @throws DocumentException
* @throws IOException
*/
private static void renderHeaderFooter(Rectangle rect, PdfContentByte pageContent, String header, String footer)
throws DocumentException, IOException {
float margin = 36.0f;
int sides = 2;
float footerHeight = (float)Math.ceil(computeWrappedTextHeight(footer, rect.getWidth() - margin * sides, Font.NORMAL));
float headerHeight = (float)Math.ceil(computeWrappedTextHeight(footer, rect.getWidth() - margin * sides, Font.BOLD));
if (headerHeight < MIN_LINE_HEIGHT) {
headerHeight = MIN_LINE_HEIGHT;
}
// Render header.
Font headerFont = getDefaultFont();
headerFont.setStyle(Font.BOLD);
Phrase headerPhrase = new Phrase(header, headerFont);
ColumnText headerRenderer = new ColumnText(pageContent);
headerRenderer.setSimpleColumn(headerPhrase, margin, rect.getHeight() - headerHeight - margin + 4,
rect.getWidth() - margin, rect.getHeight() - margin + 4, MIN_LINE_HEIGHT, Element.ALIGN_RIGHT);
headerRenderer.go();
// Render footer.
Phrase footerPhrase = new Phrase(footer, getDefaultFont());
ColumnText footerRender = new ColumnText(pageContent);
footerRender.setSimpleColumn(footerPhrase, margin, margin, rect.getWidth() - margin, footerHeight + margin, MIN_LINE_HEIGHT, Element.ALIGN_CENTER);
footerRender.go();
}
/**
* Calculates the height of the new page with the specified parameters.
* @param oldPageHeight The height of the old page.
* @param headerHeight The height of header.
* @param footerHeight The height of footer.
* @return The height of the new page.
*/
private static float calculateNewPageHeight(float oldPageHeight, float headerHeight, float footerHeight) {
return oldPageHeight + MARGIN_TOP + headerHeight + footerHeight + MARGIN_BOTTOM;
}
/**
* Calculates the width of the new page with the specified width of old page.
* @param oldPageWidth The width of the old page.
* @return The width of the new page.
*/
private static float calculateNewPageWidth(float oldPageWidth) {
return oldPageWidth + MARGIN_LEFT + MARGIN_RIGHT;
}
private static String getHeader() {
return "This is dynamically added header.";
}
private static String getFooter() {
StringBuilder footerBuilder = new StringBuilder();
footerBuilder.append("This is the dynamically added footer.");
footerBuilder.append("\n\n");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. Footer paragraph 1 content. ");
footerBuilder.append("\n\n");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. Footer paragraph 2 content. ");
footerBuilder.append("\n\n");
footerBuilder.append("Footer paragraph 3 content. Footer paragraph 3 content. Footer paragraph 3 content. Footer paragraph 3 content.");
return footerBuilder.toString();
}
private static Font getDefaultFont() {
return FontFactory.getFont(BaseFont.HELVETICA, FONT_SIZE, Color.BLACK);
}
}
答案 0 :(得分:2)
我需要使用iText以编程方式将页眉和页脚添加到现有的基于表单的PDF中。现有的PDF来自用户,它不包含页眉和页脚的空间。因此,解决方案是通过将现有PDF的内容与页眉和页脚连接来创建新的PDF。
不,这不是一个好的解决方案。您最好使用PdfStamper
,更改现有页面的大小,并在新页面区域中添加页眉和页脚。特别是当你现在已经使用PdfStamper
作为最后一步时。
但是,此方法仅适用于不包含任何形式的PDF。对于包含AcroForm或XFA表单的交互式PDF,它失败如下:(1)AcroForm在新PDF中变得扁平化。
使用您的代码,AcroForm表单元素不应该展平(即它们的外观不应该添加到静态PDF内容中),但它们应该丢失。但是,有时候,边界线或表格字段边界的其他指示实际上已经是静态内容的一部分。这可能是你的情况。
原因是您的代码使用PdfWriter.getImportedPage
,这种方法只接受页面内容流,但没有像AcroForm这样的交互式功能表单字段小部件注释。
(2)XFA表单根本不导入 - 新的PDF显示&#34;请稍候...如果此消息最终未被文档的正确内容替换,则您的PDF查看器可能不是能够显示这种类型的文件...&#34;。
XFA表单是其自己的文档类型,仅使用PDF文件作为传输介质。您的PdfWriter.getImportedPage
甚至没有在文档中看到XFA数据,只复制了XFA PDF文档在没有XFA支持的情况下在PDF查看器上显示的页面。
如果是XFA表单,PDF页面对象通常不会显示最终显示的内容。而是PDF传输XFA XML。因此,对任何现有PDF页面的所有更改都是看不见的。您必须提取该XFA XML,对其进行操作并再次存储。
iText对XFA的支持有限,而您似乎使用的古代版本根本没有。