更改页面时出现 PdfBox 问题
Posted
技术标签:
【中文标题】更改页面时出现 PdfBox 问题【英文标题】:PdfBox issue while changing page 【发布时间】:2015-08-26 08:56:27 【问题描述】:我不太喜欢问这类问题,但是,现在已经整整 3 天了,试图解决我的代码中的这个错误。
我知道这是一个逻辑问题,并且我知道如何在脑海中解决它,但是在将我的想法转化为代码时,我无法让它按我的意愿工作。
我正在处理合同背书(合同的修改),它比较 2 个表中的数据,如果其中任何一个发生了变化,那么它只绘制该信息。
有时,条件等发生变化的信息会变长或变短,这就是问题所在。
我做了一个算法,一旦它完成了信息的绘制,它就会得到最小的 Y 坐标,但是当我更改页面时,Y 坐标必须重置为 700f
并从那里重新开始绘制。
我使用的代码最初是由另一个不再在这里的程序员编写的,我不知道如何用 PDF 编写MCVE。
但这里有一些我认为可以帮助你帮助我的方法。
我如何在 PDF 上比较和绘制信息的示例:
sOffA
和sOffE
是来自名为SubscriptionOffer
的类的对象,A
代表Agreement
,而E
代表Endorsement
。背书是 mysql 上表 Agreement
的副本,但信息已修改。可能这是无关紧要的信息,也可能不是。
if (!nullOrEmpty(sOffA.getGeneralConditions()) &&
!nullOrEmpty(sOffE.getGeneralConditions()))
if (!getStringValue(sOffA.getGeneralConditions()).
equals(getStringValue(sOffE.getGeneralConditions())))
minYs[0] = pdf.rText(LEFT_MARGIN, y, 10,
constants.generalConditions(),
getStringValue(sOffA.
getGeneralConditions()));
minYs[1] = pdf.rText(HALF_PAGE, y, 10,
constants.generalConditions(),
getStringValue(sOffE.
getGeneralConditions()));
y = checkY(pdf, minYs);
else if (nullOrEmpty(sOffA.getGeneralConditions()) &&
!nullOrEmpty(sOffE.getGeneralConditions()))
minYs[0] = pdf.rText(LEFT_MARGIN, y, 10, "", "");
minYs[1] = pdf.rText(HALF_PAGE, y, 10,
constants.generalConditions(),
getStringValue(sOffE.
getGeneralConditions()));
y = checkY(pdf, minYs);
else if (!nullOrEmpty(sOffA.getGeneralConditions()) &&
nullOrEmpty(sOffE.getGeneralConditions()))
minYs[0] = pdf.rText(LEFT_MARGIN, y, 10,
constants.generalConditions(),
getStringValue(sOffA.
getGeneralConditions()));
minYs[1] = pdf.rText(HALF_PAGE, y, 10, "", "");
y = checkY(pdf, minYs);
我使用的数组是这个:
private float[] minYs = new float[] 700, 700, 700, 700, 700, 700, 700,
700, 700;
这是checkY
方法,它检查数组中的哪个Y(如上所示)是最低的,然后将整个数组重新启动到所有元素的700f。然后检查最低 Y 坐标下方的空间是否足以绘制下一个项目。
private float checkY(PdfRenderingEndorsement pdf, float... ys) throws Exception
float y2 = getMinY(pdf, ys);
for (int i = 0; i < ys.length; i++)
ys[i] = 700;
y2 = pdf.checkContentStream(y2, 5, 10);
return y2;
这种方法是我猜我的逻辑问题所在,你可以看到我尝试了不同的东西,原始算法只是for-each
部分然后我尝试了不同的东西没有成功。
private float getMinY(PdfRenderingEndorsement pdf, float... ys)
float result = 700f;
float lowest1 = 0, lowest2 = 0;
for (int i = 0; i < ys.length; i++)
for (int j = 0; j < ys.length; j++)
if (ys[j] > ys[i])
float aux = ys[i];
ys[i] = ys[j];
ys[j] = aux;
//lowest1 = ys[i];
//lowest2 = ys[j];
if (ys.length > 1)
lowest1 = ys[0];
lowest2 = ys[1];
LOGGER.trace("lowest1: " + lowest1);
LOGGER.trace("lowest2: " + lowest2);
LOGGER.trace("newPage " + pdf.getNewPage());
/*if(pdf.getNewPage())
return lowest1 > lowest2 ? lowest2 : lowest1;
*/
/*for (float y : ys)
if (y < result)
result = y;
*/
return lowest1 > lowest2 && pdf.getNewPage() ? lowest1 : lowest2;
//return lowest1;
获取对象的字符串值并检查它们是否为空或空的方法:
private boolean nullOrEmpty(String s)
return s == null || s.isEmpty();
private String getStringValue(Object o)
if (o == null)
return "";
return getStringValue(o, null);
private String getStringValue(Object o, Class< ? extends Unit> clazz)
if (o instanceof Boolean)
Boolean bd = (Boolean) o;
if (bd)
return constants.getString("dbeditorYes");
else
return constants.getString("dbeditorNo");
else if (o instanceof Date)
Date date = (Date) o;
DateFormat df = new SimpleDateFormat(DateConstants.DATE_FORMAT);
return df.format(date);
else if (o instanceof Enum)
Enum en = (Enum) o;
return constants.enumMap().get(en.toString());
else if (o instanceof Integer)
Integer integer = (Integer) o;
if (clazz == null)
return String.valueOf(integer);
else
Unit entry = unitSvc.get(clazz, integer);
String[] params = entry.toString().split("\\|");
String label = params.length == 1 ? params[0] : params[1];
return label;
else if (o instanceof BigDecimal)
BigDecimal bd = (BigDecimal) o;
DecimalFormat df = new DecimalFormat("#,##0");
df = new DecimalFormat("#,##0.00");
return df.format(bd);
else if (o instanceof Float)
Float bd = (Float) o;
DecimalFormat df = new DecimalFormat("#,##0");
df = new DecimalFormat("#,##0.00");
return df.format(bd);
else if (o instanceof String)
String td = (String) o;
return td;
return "";
以上所有方法都对应PdfEndorsement
类。
这是PdfRenderingEndorsement
类,它是实际绘制数据的类(这是完整的类,因为使用了所有方法):
public class PdfRenderingEndorsement
private static final Logger LOGGER = Logger.
getLogger(PdfRenderingEndorsement.class);
private static final float BOTTOM_MARGIN = 60;
private static final int DESC_WIDTH = 269; //For description fields
private static final int FIELD_WIDTH = 70;
private static final int FIELD_WIDTH2 = 60;
private static final int FIELD_WIDTH3 = 60;
private static final int FIELD1 = 112;
private static final int VALUE1 = 112;
private static final int FIELD2 = 80;
private static final int VALUE2 = 80;
private static final int VALUE_WIDTH = 80;
private static final int VALUE_WIDTH2 = 60;
private static final int VALUE_WIDTH3 = 80;
private static final int HALF_WIDTH = 325;
private static final int TEXT_WIDTH = 410; //For text fields
private final RwaConstants constants = ConstantsGetter.getInstance();
private final PDDocument doc;
private final String logoPath;
private final String[] header;
private int count = 0;
private boolean newPage;
private PDPageContentStream content;
/**
* Empty constructor. Used only to initialize the rendering class and call
* it's methods.
*/
public PdfRenderingEndorsement(PDDocument doc, String logoPath,
String[] header)
this.doc = doc;
this.logoPath = logoPath;
this.header = header;
public float checkContentStream2(float y, int lines, int space)
throws Exception
float newY = checkYCoord2(y, lines, space);
if (newY == 700)
if (content != null)
content.close();
File file = new File(logoPath);
PDJpeg logoImg = new PDJpeg(doc, new FileInputStream(file));
PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
doc.addPage(page);
content = new PDPageContentStream(doc, page);
content.drawImage(logoImg, 50, 720);
rHeader();
return newY;
private float checkYCoord2(float y, int lines, int space)
float newY = y;
for (int i = 0; i < lines; i++)
if ((newY - space) <= BOTTOM_MARGIN)
newY = 700f;
return newY;
else
newY = newY - space;
return y;
public boolean getNewPage()
return newPage;
public float checkContentStream(float y) throws Exception
float newY = checkYCoord(y, 1, 10);
if (newY == 700)
if (content != null)
content.close();
File file = new File(logoPath);
PDJpeg logoImg = new PDJpeg(doc, new FileInputStream(file));
PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
doc.addPage(page);
content = new PDPageContentStream(doc, page);
content.drawImage(logoImg, 50, 720);
rHeader();
return newY;
public float checkYCoord(float y, int lines, int space)
float newY = y;
for (int i = 0; i < lines; i++)
if ((newY - space) <= BOTTOM_MARGIN)
newY = 700f;
return newY;
else
newY = newY - space;
return y;
public float checkContentStream(float y, int lines, int space)
throws Exception
float newY = checkYCoord(y, lines, space);
if (newY == 700)
if (content != null)
content.close();
File file = new File(logoPath);
PDJpeg logoImg = new PDJpeg(doc, new FileInputStream(file));
PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
doc.addPage(page);
content = new PDPageContentStream(doc, page);
content.drawImage(logoImg, 50, 720);
rHeader();
return newY;
public void closeContentStream() throws Exception
if (content != null)
content.close();
/**
* Renders the header for slip documents.
*/
public void rHeader() throws Exception
float y = 760f;
content.setLineWidth(.5f);
content.setFont(PDType1Font.TIMES_ROMAN, 9);
content.setNonStrokingColor(Color.GRAY);
content.drawLine(50, 710, 562, 710);
y = rText(150, y + 19, 10, constants.endorsement(), null,
TEXT_WIDTH, 0);
y = rText(150, y + 9, 10, header[0], null, TEXT_WIDTH, 0);
y = rText(150, y + 9, 10, header[1], null, TEXT_WIDTH, 0);
y = rText(150, y + 9, 10, header[2], null, TEXT_WIDTH, 0);
y = rText(150, y + 9, 10, header[3], null, TEXT_WIDTH, 0);
content.setNonStrokingColor(Color.BLACK);
content.setFont(PDType1Font.TIMES_ROMAN, 9);
public float rText(float x, float y, int space, String labelField,
String value)
throws Exception
return rText(x, y, space, labelField, value, FIELD_WIDTH,
HALF_WIDTH - 2 * FIELD_WIDTH - 10);
public float rTextLR(float x, float y, int space, String labelField,
String value)
throws Exception
return rText(x, y, space, labelField, value, 0,
HALF_WIDTH - 2 * FIELD_WIDTH - 10);
public float rText(float x, float y, int space, String labelField,
String value, int fieldWidth)
throws Exception
if (fieldWidth == 0)
return rText(x, y, space, labelField, value, FIELD_WIDTH2,
VALUE_WIDTH2);
else if (fieldWidth == 1)
return rText(x, y, space, labelField, value, FIELD_WIDTH3,
VALUE_WIDTH3);
else if (fieldWidth == 2)
return rText(x, y, space, labelField, value, TEXT_WIDTH,
TEXT_WIDTH);
return y;
public float getFieldSize(int fs)
switch(fs)
case 1:
return (FIELD_WIDTH + VALUE_WIDTH);
case 2:
return (FIELD_WIDTH + DESC_WIDTH);
case 3:
return (FIELD_WIDTH + TEXT_WIDTH);
case 4:
return (HALF_WIDTH - FIELD_WIDTH);
case 5:
return (FIELD_WIDTH + TEXT_WIDTH);
case 6:
return (FIELD_WIDTH + TEXT_WIDTH);
case 7:
return (FIELD1 + 19) / 2;
case 8:
return (FIELD2 + 19) / 2;
default:
return 0;
public void paintLinesH(float y) throws Exception
content.drawLine(49, y - 6, 327, y - 6);
content.drawLine(335, y - 6, 563, y - 6);
public void paintLinesV(float x, float yMax, float yMin)
throws Exception
content.drawLine(x - 1, yMax - 6, x - 1, yMin - 6);
public float rText(float x, float y, int space, String labelField,
String value, int fieldWidth, int valueWidth)
throws Exception
PDFont font = PDType1Font.TIMES_BOLD;
content.setFont(font, 9);
float y1 = 0f;
float y2 = 0f;
if (value == null)
return rText(labelField, fieldWidth, x, y - 19, space, font, false);
else
if (labelField == null)
font = PDType1Font.TIMES_ROMAN;
content.setFont(font, 9);
return rText(value, valueWidth, x, y - 19, space, font, true);
else
y1 = rText(labelField, fieldWidth, x, y - 30, space, font,
false);
font = PDType1Font.TIMES_ROMAN;
content.setFont(font, 9);
float y3 = y;
y2 = rText(value, valueWidth, x + fieldWidth + 10, y - 30,
space, font, true);
if (y3 < y2)
return y2;
else
if (y1 >= y2)
return y2;
else
return y1;
private ArrayList<String> getRows(String text, int width, PDFont font)
throws Exception
float textWidth = font.getStringWidth(text) / 1000f * 9f;
ArrayList<String> result = Lists.newArrayList();
if (textWidth < width)
result.add(text);
return result;
float spaceWidth = font.getStringWidth(" ") / 1000f * 9f;
String[] paragraphs = text.split("\n|\r\n|\r");
for (String paragraph : paragraphs)
float pWidth = font.getStringWidth(paragraph) / 1000f * 9f;
if (pWidth < width)
result.add(paragraph);
continue;
float widthCount = 0f;
String[] words = paragraph.trim().split(" ");
StringBuilder sb = new StringBuilder();
for (int j = 0; j < words.length; j++)
if (words[j].trim().length() == 0)
continue;
float wWidth = font.getStringWidth(words[j]) / 1000f * 9f;
float totalWidth = widthCount + wWidth + spaceWidth;
if (totalWidth < width + spaceWidth)
sb.append(words[j]);
sb.append(" ");
widthCount = totalWidth;
else
result.add(sb.toString().trim());
sb = new StringBuilder();
sb.append(words[j]);
sb.append(" ");
widthCount = totalWidth - widthCount;
result.add(sb.toString().trim());
return result;
private float rText(String text, int width, float x, float y, int space,
PDFont font, boolean isValue) throws Exception
float newY = y;
int rowHeight = 0;
newPage = false;
ArrayList<String> rowList = getRows(text, width, font);
if (isValue)
for (String row : rowList)
if (rowHeight >= 10)
newY = checkContentStream(newY - 10);
newY = newY == 700 ? 680 : newY;
if (newY <= 700 && !newPage)
newPage = true;
rowHeight = newY == 680 ? 0 : rowHeight;
content.beginText();
content.moveTextPositionByAmount(x, newY);
content.drawString(row);
content.endText();
rowHeight = rowHeight + 10;
else
for (String row : rowList)
content.beginText();
content.moveTextPositionByAmount(x, newY - rowHeight);
content.drawString(row);
content.endText();
rowHeight = rowHeight + 10;
newY -= (rowHeight - 10);
return newY;
这是使用原始 (for-each
) 方法的输出的PDF Example。
如果您看不到 PDF,请告诉我,但这里还有一些截图:
这是输出的PDF Example,getMinY
方法上的未注释代码。
这个修改的输出如下:
您可以看到第一个输出“跳转”到下一页,因为假设“Texto de Poliza”右侧的文本以 Y 坐标 680、670 或类似的方式结束,但在左侧我画了一个空字段 ("")
以接近这些数字的 90 或 100 结尾。
然后它比较 100 BOTTOM_MARGIN(即 60),所以它关闭实际页面(现在是文本结束的地方,但认为它在它之前的页面上)并创建一个新页面。
我在here 之前差不多一年问过一个类似的问题,这些类几乎是这些文件的副本,但我的逻辑上的这个错误只出现在这些文件中,因为数据的处理方式有点不同。
好吧,在那堵文字墙之后,我希望有人能读懂它并真正帮助我,也许我错过了一个重要的部分,但在 atm 找不到它。
提前致谢。
编辑
在我的方法中添加@mkl 的答案后,我发现当双方没有任何信息发生变化时,它会造成差距并且看起来不太好。
这就是我向PdfRenderingEndorsementAlternative
发送数据的方式:
for (String[] data: sOppData)
//float y = renderer.getPreviousBandBase;
for (int i = 0; i < data.length - 2; i += 3)
if (!nullOrEmpty(data[i + 1]) &&
!nullOrEmpty(data[i + 2]))
if (!data[i + 1].equals(data[i + 2]))
renderer.
render(new BandColumn(leftHalfPageField,
data[i], data[i + 1]),
new BandColumn(rightHalfPageField,
data[i], data[i + 2])
);
else if (nullOrEmpty(data[i + 1]) &&
!nullOrEmpty(data[i + 2]))
renderer.
render(new BandColumn(leftHalfPageField,
"", ""),
new BandColumn(rightHalfPageField,
data[i], data[i + 2])
);
else if (!nullOrEmpty(data[i + 1]) &&
nullOrEmpty(data[i + 2]))
renderer.
render(new BandColumn(leftHalfPageField,
data[i], data[i + 1]),
new BandColumn(rightHalfPageField,
"", "")
);
//float y2 = renderer.getPreviousBandBase();
/*if (y2 < y)
renderer.gap(20);
*/
renderer.gap(20);
以上评论的验证是否正确,或者我会回到过去产生错误的方法?我应该在 PdfRenderingEndorsementAlternative 上添加 getPreviousBandBase()
方法还是以其他方式直接在 render()
方法上添加?
这就是我获取当前双方信息相同的数据的方式。
这方面的一个例子是:
sOppA.getContractName()
和 sOppE.getContractName()
都是 "Hello World"
,因为两者相等,所以会留下屏幕截图所示的间隙。
private ArrayList<String []> renderSubscriptionOpportunity(
SubscriptionOpp sOppA, SubscriptionOpp sOppE,
ArrayList<Location> lcA, ArrayList<Location> lcE)
throws Exception
ArrayList <String[]> sOppData = new ArrayList<String[]>();
sOppData.add(new String[] constants.currencyId(),
getStringValue(sOppA.getCurrencyId(),
Currency.class),
getStringValue(sOppE.getCurrencyId(),
Currency.class));
sOppData.add(new String[] constants.contractName(),
getStringValue(sOppA.getContractName()),
getStringValue(sOppE.getContractName()));
sOppData.add(new String[] constants.mainActivityId(),
getStringValue(sOppA.getMainActivityId(),
MainActivity.class),
getStringValue(sOppE.getMainActivityId(),
MainActivity.class));
//here add location table
if (lcA.size() > 1 && lcE.size() > 1)
int lastIdA = 0;
int lastIdE = 0;
int size = lcA.size() >= lcE.size() ? lcA.size() : lcE.size();
LOGGER.trace("size: " + size + " lcA.size(): " + lcA.size() +
" lcE.size(): " + lcE.size());
for (int pos = 1; pos < lcA.size(); pos++)
StringBuilder aSb = new StringBuilder();
StringBuilder eSb = new StringBuilder();
String valueA = "";
String valueE = "";
if (pos < lcA.size())
Country countryA = unitSvc.get(Country.class,
lcA.get(pos).getCountryId());
LOGGER.trace("Entro1");
if (countryA.getId() != lastIdA)
aSb.append(countryA.getName());
lastIdA = countryA.getId();
else
aSb.append("");
else
aSb.append("");
if (pos < lcE.size())
Country countryE = unitSvc.get(Country.class,
lcE.get(pos).getCountryId());
LOGGER.trace("Entro2");
if (countryE.getId() != lastIdE)
eSb.append(countryE.getName());
lastIdE = countryE.getId();
else
eSb.append("");
else
eSb.append("");
valueA = aSb.toString();
valueE = eSb.toString();
sOppData.add(new String[] pos == 1 ? constants.countryId() :
"", valueA, valueE);
return sOppData;
【问题讨论】:
正如我已经评论过你之前的问题,我怀疑检查页面切换是个好主意从rtext内部,它应该通过某种方法控制要为 all 列中的当前条目添加的内容。 @mkl 你有一个例子,也许是该方法的另一个问题的链接?或者我可以分析的类似的东西?很抱歉迟到的答案。谢谢。我现在将阅读一些文档并尝试一些方法。我对 pdf 编程不是很有经验,所以我犯了很多错误(我不知道如何用 pdf 做一个简单的“Hello World”,我的意思不是从 0 开始) 我已经开始阅读如何使用 PDFBox 和 Maven 从 0 开始 PDF,我仍然坚持使用 Maven 配置,但是一旦我有了 MCVE,我就会发布它。 你在你的 for 循环中每次都做renderer.gap(20);
,而不关心你是否渲染了一个条目。仅在渲染条目后添加间隙。
我以前怎么看不到?我在想else continue
的if(!data[i+1].equals(data[i+2])
我会在星期一测试它...
【参考方案1】:
正如评论中已经暗示的那样(实际上已经在对您的former question 的评论中),我认为您的渲染类的整个体系结构需要大修。根据您的PdfRenderingEndorsement
,我创建了以下类PdfRenderingEndorsementAlternative
,它代表了该渲染的另一种方法:
public class PdfRenderingEndorsementAlternative implements AutoCloseable
//
// misc constants
//
static final int FIELD_WIDTH = 70;
static final int HALF_WIDTH = 325;
static final int TEXT_WIDTH = 410;
static final float BOTTOM_MARGIN = 70;
static final int LEFT_MARGIN = 50;
//
// rendering
//
public void gap(int size)
previousBandBase-=size;
public void render(BandColumn... columns) throws IOException
if (content == null)
newPage();
final List<Chunk> chunks = new ArrayList<Chunk>();
for (BandColumn column : columns)
chunks.addAll(column.toChunks());
float offset = 0;
while (!chunks.isEmpty())
float lowestAddedY = previousBandBase;
float highestBaseBeforeNonAdded = Float.NEGATIVE_INFINITY;
List<Chunk> added = new ArrayList<Chunk>();
for (Chunk chunk: chunks)
float y = previousBandBase + chunk.y + offset;
if (y >= BOTTOM_MARGIN)
content.beginText();
content.setFont(chunk.font, 9);
content.moveTextPositionByAmount(chunk.x, y);
content.drawString(chunk.text);
content.endText();
// draw
if (y < lowestAddedY)
lowestAddedY = y;
added.add(chunk);
else
float baseBefore = chunk.y + chunk.space;
if (baseBefore > highestBaseBeforeNonAdded)
highestBaseBeforeNonAdded = baseBefore;
chunks.removeAll(added);
if (!chunks.isEmpty())
newPage();
offset = -highestBaseBeforeNonAdded;
else
previousBandBase = lowestAddedY;
static public class BandColumn
public enum Layout
headerText(150, TEXT_WIDTH, 0, 10),
leftHalfPageField(LEFT_MARGIN, FIELD_WIDTH, HALF_WIDTH - 2 * FIELD_WIDTH - 10, 10),
rightHalfPageField(HALF_WIDTH, FIELD_WIDTH, HALF_WIDTH - 2 * FIELD_WIDTH - 10, 10);
Layout(float x, int fieldWidth, int valueWidth, int space)
this.x = x;
this.fieldWidth = fieldWidth;
this.valueWidth = valueWidth;
this.space = space;
final float x;
final int fieldWidth, valueWidth, space;
public BandColumn(Layout layout, String labelField, String value)
this(layout.x, layout.space, labelField, value, layout.fieldWidth, layout.valueWidth);
public BandColumn(float x, int space, String labelField, String value, int fieldWidth, int valueWidth)
this.x = x;
this.space = space;
this.labelField = labelField;
this.value = value;
this.fieldWidth = fieldWidth;
this.valueWidth = valueWidth;
List<Chunk> toChunks() throws IOException
final List<Chunk> result = new ArrayList<Chunk>();
result.addAll(toChunks(0, fieldWidth, PDType1Font.TIMES_BOLD, labelField));
result.addAll(toChunks(10 + fieldWidth, valueWidth, PDType1Font.TIMES_ROMAN, value));
return result;
List<Chunk> toChunks(int offset, int width, PDFont font, String text) throws IOException
if (text == null || text.length() == 0)
return Collections.emptyList();
final List<Chunk> result = new ArrayList<Chunk>();
float y = -space;
List<String> rows = getRows(text, width, font);
for (String row: rows)
result.add(new Chunk(x+offset, y, space, font, row));
y-= space;
return result;
final float x;
final int space, fieldWidth, valueWidth;
final String labelField, value;
//
// constructor
//
public PdfRenderingEndorsementAlternative(PDDocument doc, InputStream logo,
String[] header) throws IOException
this.doc = doc;
this.header = header;
logoImg = new PDJpeg(doc, logo);
//
// AutoCloseable implementation
//
@Override
public void close() throws IOException
if (content != null)
content.close();
content = null;
//
// helper methods
//
void newPage() throws IOException
close();
PDPage page = new PDPage(PDPage.PAGE_SIZE_LETTER);
doc.addPage(page);
content = new PDPageContentStream(doc, page);
content.drawImage(logoImg, 50, 720);
content.setLineWidth(.5f);
content.setNonStrokingColor(Color.GRAY);
content.drawLine(50, 710, 562, 710);
previousBandBase = 770;
render(new BandColumn(BandColumn.Layout.headerText, "ENDOSO", null));
for (String head: header)
render(new BandColumn(BandColumn.Layout.headerText, head, null));
content.setNonStrokingColor(Color.BLACK);
previousBandBase = 680;
// original method
static List<String> getRows(String text, int width, PDFont font) throws IOException
float textWidth = font.getStringWidth(text) / 1000f * 9f;
ArrayList<String> result = new ArrayList<String>();// Lists.newArrayList();
if (textWidth < width)
result.add(text);
return result;
float spaceWidth = font.getStringWidth(" ") / 1000f * 9f;
String[] paragraphs = text.split("\n|\r\n|\r");
for (String paragraph : paragraphs)
float pWidth = font.getStringWidth(paragraph) / 1000f * 9f;
if (pWidth < width)
result.add(paragraph);
continue;
float widthCount = 0f;
String[] words = paragraph.trim().split(" ");
StringBuilder sb = new StringBuilder();
for (int j = 0; j < words.length; j++)
if (words[j].trim().length() == 0)
continue;
float wWidth = font.getStringWidth(words[j]) / 1000f * 9f;
float totalWidth = widthCount + wWidth + spaceWidth;
if (totalWidth < width + spaceWidth)
sb.append(words[j]);
sb.append(" ");
widthCount = totalWidth;
else
result.add(sb.toString().trim());
sb = new StringBuilder();
sb.append(words[j]);
sb.append(" ");
widthCount = totalWidth - widthCount;
result.add(sb.toString().trim());
return result;
//
// helper classes
//
static class Chunk
Chunk(float x, float y, int space, PDFont font, String text)
this.x = x;
this.y = y;
this.space = space;
this.font = font;
this.text = text;
final float x, y;
final int space;
final PDFont font;
final String text;
//
// members
//
private final PDDocument doc;
private final PDJpeg logoImg;
private final String[] header;
private PDPageContentStream content = null;
private float previousBandBase = 0;
(PdfRenderingEndorsementAlternative.java)
此类基于带的概念,即内容的水平条纹,具有包含字段名称和/或字段值的任意数量的列。
可以这样使用:
PDDocument document = new PDDocument();
PdfRenderingEndorsementAlternative renderer = new PdfRenderingEndorsementAlternative(document, logoStream, header);
renderer.render(
new BandColumn(leftHalfPageField, "Nombre del contrato/asegurado:", "Prueba Jesus Fac No Prop"),
new BandColumn(rightHalfPageField, "Nombre del contrato/asegurado:", "Prueba Jesus Fac No Prop con Endoso")
);
renderer.gap(20);
renderer.render(
new BandColumn(leftHalfPageField, "País:", "México"),
new BandColumn(rightHalfPageField, "País:", "México")
);
renderer.close();
document.save(new File(RESULT_FOLDER, "Endorsement.pdf"));
(RenderEndorsement.java)
如您所见,调用者不必再关心 y 位置,一切都在渲染器类中完成。结果:
我使用您的第一个示例 PDF 中的数据作为输入,结果如下:
如您所见,也没有页面跳转和重叠文本。
【讨论】:
我正在测试解决方案,我花了一些时间来适应项目,你介意我在 4 小时内开始赏金吗(在我可以添加它之前缺少时间)和周末接受你的回答,这样你就得到了赏金?顺便说一句,非常感谢你,看到我想我试图重新发明***的方法(并且以错误的方式)。无论如何,我将继续尝试配置 Maven 以从 0 开始制作项目,并在未来学习如何为 PDF 做 MCVE。 你介意... - 我不介意,但请不要觉得有义务这样做。我希望这个代码示例可以帮助到你。 当然对我有帮助。我不觉得有义务,但很感激。非常感谢:) 我有一个疑问,你会在哪里检查双方是否都没有改变?即即使没有信息改变,它也会产生 20 的差距。我正在考虑采用当前的 Y 和结束 Y,但我想我会回到我的方法,这会带来更多的错误。 我用更新的方法编辑了问题......嗯,我猜我发现了我的错误,我已经添加了所有数据,即使它没有改变,我试图以这种方式减少代码行但那赢了我猜这不可能。我会在星期一检查。我现在有3种可能的方法来解决它。如果有任何作品我会更新它以上是关于更改页面时出现 PdfBox 问题的主要内容,如果未能解决你的问题,请参考以下文章
使用 djangocms 发布页面更改时出现“NodeAlreadySaved”错误