更改页面时出现 PdfBox 问题

Posted

技术标签:

【中文标题】更改页面时出现 PdfBox 问题【英文标题】:PdfBox issue while changing page 【发布时间】:2015-08-26 08:56:27 【问题描述】:

我不太喜欢问这类问题,但是,现在已经整整 3 天了,试图解决我的代码中的这个错误。

我知道这是一个逻辑问题,并且我知道如何在脑海中解决它,但是在将我的想法转化为代码时,我无法让它按我的意愿工作。

我正在处理合同背书(合同的修改),它比较 2 个表中的数据,如果其中任何一个发生了变化,那么它只绘制该信息。

有时,条件等发生变化的信息会变长或变短,这就是问题所在。

我做了一个算法,一旦它完成了信息的绘制,它就会得到最小的 Y 坐标,但是当我更改页面时,Y 坐标必须重置为 700f 并从那里重新开始绘制。

我使用的代码最初是由另一个不再在这里的程序员编写的,我不知道如何用 PDF 编写MCVE。

但这里有一些我认为可以帮助你帮助我的方法。

我如何在 PDF 上比较和绘制信息的示例:

sOffAsOffE 是来自名为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”错误

尝试更改上传目录时出现空白页面

如何从左下角到左上角更改pdf页面中文本的坐标

由于启动计算机时出现了页面配置问题

尝试使用 struts 中的 ajax 更新 jsp 页面上的内容时出现问题(触发 onChange 事件时)

如何将 PDFBox 添加到 Android 项目或建议替代方案