如何使用 iText 以编程方式将页眉和页脚添加到现有的基于表单的 PDF?
How to programmatically add header and footer to an existing form-based PDF using iText?
我需要使用 iText 以编程方式将页眉和页脚添加到现有的基于表单的 PDF。现有 PDF 来自用户,它不包含页眉和页脚的 space。因此解决方案是通过将现有 PDF 的内容与页眉和页脚连接起来来创建一个新的 PDF。但是,此方法仅适用于不包含表单的 PDF。对于包含 AcroForm 或 XFA Form 的交互式 PDF,它失败如下: (1) AcroForm 在新 PDF 中变得扁平化。 (2) XFA 表格根本不导入 - 新 PDF 显示 "Please wait...If this message is not eventually replaced by proper contents of the document, your PDF viewer may not be able to display this type of document...".
这是我的代码:
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);
}
}
I need to programmatically add header and footer to an existing form-based PDF using iText. The existing PDF comes from user and it contains no space for header and footer. So the solution is to create a new PDF by concatenating the contents of the existing PDF with the header and footer.
不,这不是一个好的解决方案。您最好使用PdfStamper
,更改现有页面的大小,并在新页面区域添加页眉和页脚。特别是当您现在已经在最后一步使用 PdfStamper
时。
@Mark Storer 在 this old answer 中展示了如何操作 MediaBox 的底部。同样,您也可以更改其顶部。正如马克在他的回答中所说,您可能还必须更改 CropBox.
However, this approach only works for PDF containing no form. For interactive PDF that contains AcroForm or XFA Form, it fails as follows: (1) AcroForm gets flattened in the new PDF.
使用您的代码,AcroForm 表单元素不应变平(即它们的外观不应添加到静态 PDF 内容中)但它们应该丢失。但有时,边框线或其他表单字段边界指示实际上已经是静态内容的一部分。你可能就是这种情况。
原因是您的代码使用了 PdfWriter.getImportedPage
,这种方法只获取页面内容流,但没有像 AcroForm 表单字段小部件注释这样的交互功能。
(2) XFA Form doesn't import at all - the new PDF shows "Please wait...If this message is not eventually replaced by proper contents of the document, your PDF viewer may not be able to display this type of document...".
XFA 表单本身就是一种文档类型,它仅使用 PDF 文件作为传输介质。您的 PdfWriter.getImportedPage
甚至看不到文档中的 XFA 数据,只会复制您的 XFA PDF 文档在不支持 XFA 的 PDF 查看器上显示的页面。
对于 XFA 表单,PDF 页面对象通常不参与最终显示的内容。相反,PDF 传输 XFA XML。因此,您对任何现有 PDF 页面所做的所有更改都不会被看到。您必须提取该 XFA XML、对其进行操作并再次存储。
iText 对 XFA 的支持有限,您使用的旧版本根本没有 none。
我需要使用 iText 以编程方式将页眉和页脚添加到现有的基于表单的 PDF。现有 PDF 来自用户,它不包含页眉和页脚的 space。因此解决方案是通过将现有 PDF 的内容与页眉和页脚连接起来来创建一个新的 PDF。但是,此方法仅适用于不包含表单的 PDF。对于包含 AcroForm 或 XFA Form 的交互式 PDF,它失败如下: (1) AcroForm 在新 PDF 中变得扁平化。 (2) XFA 表格根本不导入 - 新 PDF 显示 "Please wait...If this message is not eventually replaced by proper contents of the document, your PDF viewer may not be able to display this type of document...".
这是我的代码:
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);
}
}
I need to programmatically add header and footer to an existing form-based PDF using iText. The existing PDF comes from user and it contains no space for header and footer. So the solution is to create a new PDF by concatenating the contents of the existing PDF with the header and footer.
不,这不是一个好的解决方案。您最好使用PdfStamper
,更改现有页面的大小,并在新页面区域添加页眉和页脚。特别是当您现在已经在最后一步使用 PdfStamper
时。
@Mark Storer 在 this old answer 中展示了如何操作 MediaBox 的底部。同样,您也可以更改其顶部。正如马克在他的回答中所说,您可能还必须更改 CropBox.
However, this approach only works for PDF containing no form. For interactive PDF that contains AcroForm or XFA Form, it fails as follows: (1) AcroForm gets flattened in the new PDF.
使用您的代码,AcroForm 表单元素不应变平(即它们的外观不应添加到静态 PDF 内容中)但它们应该丢失。但有时,边框线或其他表单字段边界指示实际上已经是静态内容的一部分。你可能就是这种情况。
原因是您的代码使用了 PdfWriter.getImportedPage
,这种方法只获取页面内容流,但没有像 AcroForm 表单字段小部件注释这样的交互功能。
(2) XFA Form doesn't import at all - the new PDF shows "Please wait...If this message is not eventually replaced by proper contents of the document, your PDF viewer may not be able to display this type of document...".
XFA 表单本身就是一种文档类型,它仅使用 PDF 文件作为传输介质。您的 PdfWriter.getImportedPage
甚至看不到文档中的 XFA 数据,只会复制您的 XFA PDF 文档在不支持 XFA 的 PDF 查看器上显示的页面。
对于 XFA 表单,PDF 页面对象通常不参与最终显示的内容。相反,PDF 传输 XFA XML。因此,您对任何现有 PDF 页面所做的所有更改都不会被看到。您必须提取该 XFA XML、对其进行操作并再次存储。
iText 对 XFA 的支持有限,您使用的旧版本根本没有 none。