更改页面时 PdfBox 问题

PdfBox issue while changing page

我不太喜欢问这类问题,但是好吧,我已经花了整整 3 天时间来解决我代码中的这个错误。

我知道这是一个逻辑问题,我知道如何在我的脑海中解决它,但是当我把我的想法转化为代码时,我就是不能让它像我想要的那样工作。

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

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

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

我使用的代码最初是由另一个已经不在的程序员编写的,我不知道如何用 PDF 进行 MCVE

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

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

sOffAsOffE 是来自 Class 的对象 SubscriptionOffer 并且 A 代表 AgreementE Endorsement。背书是 table Agreement 上 MySQL 的副本,但修改了信息。可能这是无关紧要的信息,可能不是。

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 坐标下方的 space 是否足以绘制下一个项目。

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 部分然后我尝试了不同的东西 w/o成功。

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;
}

获取对象的字符串值并检查它们是否为 null 或空的方法:

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 class.

这是 PdfRenderingEndorsement class,这是实际绘制数据的 class(这是完整的 class,因为使用了所有方法):

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 方法中未注释的代码。

此修改的输出如下:

正如您在下一页中看到的第一个输出 "jumps",因为假设 "Texto de Poliza" 右侧的文本以 Y 坐标 680、670 或类似的东西结束,但在左边我画了一个空白字段 ("") 以 90 或 100 结尾的数字接近这些数字。

然后比较 100 < 670 ?是的,然后我取 50 到 100,它低于我的 BOTTOM_MARGIN(即 60),所以它关闭实际页面(现在是文本结束的地方,但认为它在它之前的页面上)并创建一个新的一.

我几乎在 here 前一年问过类似的问题,这些 Classes 几乎是那些文件的副本,但我逻辑上的这个错误只出现在这些文件中,因为数据是处理方式有点不同。

好吧,在那堵文字墙之后,我希望有人能阅读它并实际帮助我,也许我遗漏了一个重要的部分,但在 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;
}

正如已经在评论中暗示的那样(实际上已经在对您的 former question 的评论中)我认为您渲染的整个架构 class 需要彻底改革。根据您的 PdfRenderingEndorsement,我创建了以下 class 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)

此 class 基于条带的概念,即内容的水平条纹,包含字段名称和/或字段值的任意数量的列。

可以这样使用:

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 位置,一切都在渲染器中完成 class。结果:

我使用您的第一个示例 PDF 中的数据作为输入,结果如下:

如您所见,没有页面跳转,也没有重叠文本。