You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache PDFBox中如何让GraphicsState印章图显示在交易行上层

问题

我正在使用Apache PDFBox开发一个PDF项目,该项目展示交易列表,当前页面填满后会自动延伸至新页面。交易行采用紫色与白色交替的背景色。我已实现在每页底部添加印章,通过GraphicsState设置了印章图的透明度,但印章图目前像水印一样显示在交易行下方,我希望它能显示在交易行上层,请问该如何实现?


主代码

JsonArray transactionList = SampleDataGenerator
                .generateSampleData()
                .getJsonObject("data")
                .getJsonArray("transactionList");
for (int i = 0; i < transactionList.size(); i++) {
    JsonObject transaction = transactionList.getJsonObject(i);

    PDPageContentStream newPagesContentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);

    if (dataYPosition - rowHeight < margin) {
        page = new PDPage();
        document.addPage(page);
        dataYPosition = page.getMediaBox().getHeight() - 130f; // 调整新页面行高
        PdfImageDrawer.drawColumnHeader(newPagesContentStream, HELVETICA, columnXPosition, columnYPosition + 85f);
        PdfImageDrawer.drawLogo(document, newPagesContentStream);
    }

    List<String> transactionValues = generateTransactionRow.doGenerateRow(transaction);
    for (String dataValues : transactionValues) {
        newPagesContentStream.setNonStrokingColor(i % 2 == 0 ? new Color(229, 229, 255) : Color.WHITE);
        newPagesContentStream.addRect(dataXPosition - 35f, dataYPosition, tableWidth, rectangleHeight);
        newPagesContentStream.fill();
        newPagesContentStream.setNonStrokingColor( new Color(112, 112, 112));

        newPagesContentStream.beginText();
        newPagesContentStream.setFont(HELVETICA, 6.5f);
        newPagesContentStream.newLineAtOffset(dataXPosition, dataYPosition + 25f);
        newPagesContentStream.showText(dataValues);
        newPagesContentStream.endText();
        dataXPosition += 100f;
    }
    dataXPosition += -600f;
    dataYPosition -= rowHeight;

    contentStream.saveGraphicsState();
    PdfImageDrawer.drawTextStampInputStream(newPagesContentStream, stampImage);
    newPagesContentStream.close();
}

//FOOTER
PdfImageDrawer.drawTextStampInputStream(contentStream, stampImage);

contentStream.close();

drawTextStampInputStream方法代码

public static void drawTextStampInputStream(PDPageContentStream contentStream, PDImageXObject stampImage) throws IOException {
    String text1 = "xxxxxxxxxxxxxxxxxxxxxxx";
    String text2 = "xxxxxxxxxxxxxxxxxxxxxxx";
    String text3 = "xxxxxxxxxxxxxxxxxxxxxxx";

    PDType1Font HELVETICA = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
    PDExtendedGraphicsState textGraphicsState = new PDExtendedGraphicsState();
    textGraphicsState.setNonStrokingAlphaConstant(0.050f);
    contentStream.setGraphicsStateParameters(textGraphicsState);

    PdfTextWriter.doWrite(contentStream, HELVETICA, 8f,
            (700f) / 3f, 60f, 0, text1);
    PdfTextWriter.doWrite(contentStream, HELVETICA, 8f,
            140f, 50f, 0, text2);
    PdfTextWriter.doWrite(contentStream, HELVETICA, 8f,
            190f, 40f, 0, text3);


    PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
    graphicsState.setNonStrokingAlphaConstant(0.10f);
    contentStream.setGraphicsStateParameters(graphicsState);
    contentStream.drawImage(stampImage, 480F, 10f,
            stampImage.getWidth() / 4F,
            stampImage.getHeight() / 4F);
    contentStream.restoreGraphicsState();
}

解决方案

PDF的内容层级由绘制顺序决定:先绘制的内容在底层,后绘制的内容在上层。你的印章被交易行覆盖,是因为循环内绘制完交易行后立即绘制印章,后续新增页面的交易行是在印章流之后绘制的,导致交易行叠在印章上方。

要让印章显示在交易行上层,需调整绘制顺序,确保所有页面的交易内容绘制完成后,再统一为每个页面追加绘制印章,具体修改如下:

调整后的主代码

JsonArray transactionList = SampleDataGenerator
                .generateSampleData()
                .getJsonObject("data")
                .getJsonArray("transactionList");
for (int i = 0; i < transactionList.size(); i++) {
    JsonObject transaction = transactionList.getJsonObject(i);

    PDPageContentStream newPagesContentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true);

    if (dataYPosition - rowHeight < margin) {
        page = new PDPage();
        document.addPage(page);
        dataYPosition = page.getMediaBox().getHeight() - 130f; // 调整新页面行高
        PdfImageDrawer.drawColumnHeader(newPagesContentStream, HELVETICA, columnXPosition, columnYPosition + 85f);
        PdfImageDrawer.drawLogo(document, newPagesContentStream);
    }

    List<String> transactionValues = generateTransactionRow.doGenerateRow(transaction);
    for (String dataValues : transactionValues) {
        newPagesContentStream.setNonStrokingColor(i % 2 == 0 ? new Color(229, 229, 255) : Color.WHITE);
        newPagesContentStream.addRect(dataXPosition - 35f, dataYPosition, tableWidth, rectangleHeight);
        newPagesContentStream.fill();
        newPagesContentStream.setNonStrokingColor( new Color(112, 112, 112));

        newPagesContentStream.beginText();
        newPagesContentStream.setFont(HELVETICA, 6.5f);
        newPagesContentStream.newLineAtOffset(dataXPosition, dataYPosition + 25f);
        newPagesContentStream.showText(dataValues);
        newPagesContentStream.endText();
        dataXPosition += 100f;
    }
    dataXPosition += -600f;
    dataYPosition -= rowHeight;

    // 移除循环内的印章绘制逻辑,仅关闭当前内容流
    newPagesContentStream.close();
}

// 所有交易内容绘制完成后,遍历每页追加印章
for (PDPage singlePage : document.getPages()) {
    try (PDPageContentStream stampStream = new PDPageContentStream(document, singlePage, PDPageContentStream.AppendMode.APPEND, true)) {
        PdfImageDrawer.drawTextStampInputStream(stampStream, stampImage);
    }
}

contentStream.close();

额外注意事项

  • 使用PDPageContentStream.AppendMode.APPEND模式打开页面流,确保印章绘制在现有内容之后。
  • 用try-with-resources语法管理流,避免资源泄漏。
  • 确保drawTextStampInputStream中的restoreGraphicsState()调用正确,防止图形状态污染后续操作。

内容的提问来源于stack exchange,提问作者Plus Ultra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 02:20:56