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

基于Apache PDFBox 3.0.0的Java PDF渲染器XOR颜色反转实现

使用Apache PDFBox 3.0.0实现PDF元素的XOR颜色反转

需求背景

我正在用Apache PDFBox 3.0.0在Java中实现PDF渲染器,希望给字符串、矩形、线条等PDF元素添加XOR(异或)模式来反转底层元素颜色,这种效果常用于高亮或图形反转。

示例效果

示例中展示了XOR模式下,图形/文本覆盖在底层内容上时,重叠区域颜色反转的视觉效果。

当前PDFRenderer实现

public class PDFRenderer {
    private PDDocument pdDocument;
    private PDPageContentStream contentStream;

    private float originY;
    private float originX;

    public PDFRenderer() {
        this.pdDocument = new PDDocument();

        PDPage pdPage = new PDPage();
        this.pdDocument.addPage(pdPage);

        try {
            this.contentStream = new PDPageContentStream(pdDocument, pdPage, AppendMode.APPEND, false, false);
        } catch (IOException e) {
            e.printStackTrace();
        }

        originY = pdPage.getMediaBox().getHeight();
        originX = 0;
    }

    /**
     * 在画布上绘制字符串
     *
     * @param text 要绘制的文本
     * @param x 文本左上角的x坐标
     * @param y 文本左上角的y坐标
     * @param width 文本框宽度
     * @param height 文本框高度
     * @param color 文本颜色
     * @param font 使用的字体
     */
    public void drawString(String text, int x, int y, int width, int height, CustomColor color, CustomFont font) {
        try {
            setStrokingColor(color);

            int fontHeight = Math.round((font.getFont().getFontDescriptor().getCapHeight()) / 1000 * height);
            float textWidth = font.getFont().getStringWidth(text) / 1000 * height;

            contentStream.setFont(font.getFont(), height);
            contentStream.setHorizontalScaling(100 + width);
            contentStream.beginText();
            contentStream.newLineAtOffset(originX + x, originY - fontHeight - y);
            contentStream.showText(text);
            contentStream.endText();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 在画布上绘制矩形
     *
     * @param x 矩形左上角的x坐标
     * @param y 矩形左上角的y坐标
     * @param width 矩形宽度
     * @param height 矩形高度
     * @param borderThickness 矩形边框厚度
     * @param color 矩形颜色
     * @param degreeOfCornerRounding 矩形圆角程度,取值范围1到8
     * @param colorInverted 是否反转矩形颜色
     */
    public void drawRectangle(int x, int y, int width, int height, int borderThickness, CustomColor color, int degreeOfCornerRounding, boolean colorInverted) {
        try {
            setStrokingColor(color);

            if (borderThickness > width / 2) {
                borderThickness = width / 2;
            } else if (borderThickness > height / 2) {
                borderThickness = height / 2;
            }

            float borderOffset = borderThickness / 2.0f;
            float adjustedX = originX + x + borderOffset;
            float adjustedY = originY - y - height + borderOffset;
            float adjustedWidth = width - borderThickness;
            float adjustedHeight = height - borderThickness;

            contentStream.setLineWidth(borderThickness);

            contentStream.addRect(adjustedX, adjustedY, adjustedWidth, adjustedHeight);

            contentStream.stroke();
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 在画布上绘制线条
     *
     * @param x 线条起点的x坐标
     * @param y 线条起点的y坐标
     * @param width 线条水平长度
     * @param height 线条垂直长度
     * @param thickness 线条厚度
     * @param color 线条颜色
     */
    public void drawLine(int x, int y, int width, int height, int thickness, CustomColor color) {
        try {
            setStrokingColor(color);

            float adjustedX = originX + x;
            float adjustedY = originY - y;

            contentStream.setLineWidth(thickness);
            contentStream.moveTo(adjustedX, adjustedY);
            contentStream.lineTo(adjustedX + width, adjustedY - height);
            contentStream.stroke();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void setStrokingColor(CustomColor color) throws NumberFormatException, IOException {
        contentStream.setStrokingColor(
                Integer.parseInt(color.getHexadecimal().substring(1, 3), 16) / 255f,
                Integer.parseInt(color.getHexadecimal().substring(3, 5), 16) / 255f,
                Integer.parseInt(color.getHexadecimal().substring(5, 7), 16) / 255f
        );
    }
}

使用示例

PDFRenderer pdfRenderer = new PDFRenderer();

pdfRenderer.drawRectangle(50, 50, 100, 100, 100, CustomColor.BLACK, 2);
pdfRenderer.drawLine(50, 500, 700, 3, 3, CustomColor.BLACK);
pdfRenderer.drawString("Hello, World!", 220, 195, 100, 30, CustomColor.BLACK, CustomFont.A);

具体问题

  • 如何修改PDFRenderer类,在Java中使用Apache PDFBox 3.0.0绘制矩形、线条和文本时实现XOR颜色反转?
  • PDFBox中是否有支持颜色处理XOR模式的内置函数或方法?
  • 能否提供此场景下实现XOR颜色反转的代码示例或建议?

解决方案

1. PDFBox是否支持XOR模式?

PDFBox本身没有直接提供XOR颜色混合模式的API,但PDF规范支持**混合模式(Blend Mode)**中的XOR模式,可以通过设置内容流的图形状态参数来启用该模式。

2. 修改PDFRenderer实现XOR效果

核心思路是在绘制需要XOR效果的元素前,设置内容流的混合模式为XOR,绘制完成后恢复默认模式,避免影响后续元素。

完整修改后的代码

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.pdmodel.font.PDFont;

import java.io.IOException;

public class PDFRenderer {
    private PDDocument pdDocument;
    private PDPageContentStream contentStream;
    private PDExtendedGraphicsState defaultGState;

    private float originY;
    private float originX;

    public PDFRenderer() {
        this.pdDocument = new PDDocument();

        PDPage pdPage = new PDPage();
        this.pdDocument.addPage(pdPage);

        try {
            this.contentStream = new PDPageContentStream(pdDocument, pdPage, PDPageContentStream.AppendMode.APPEND, false, false);
            // 保存默认图形状态,用于后续恢复
            this.defaultGState = new PDExtendedGraphicsState();
        } catch (IOException e) {
            e.printStackTrace();
        }

        originY = pdPage.getMediaBox().getHeight();
        originX = 0;
    }

    // 启用XOR混合模式
    private void enableXorMode() throws IOException {
        PDExtendedGraphicsState xorGState = new PDExtendedGraphicsState();
        xorGState.setBlendMode(BlendMode.XOR);
        contentStream.setGraphicsStateParameters(xorGState);
    }

    // 恢复默认混合模式
    private void restoreDefaultMode() throws IOException {
        contentStream.setGraphicsStateParameters(defaultGState);
    }

    /**
     * 在画布上绘制字符串
     *
     * @param text 要绘制的文本
     * @param x 文本左上角的x坐标
     * @param y 文本左上角的y坐标
     * @param width 文本框宽度
     * @param height 文本框高度
     * @param color 文本颜色
     * @param font 使用的字体
     * @param useXor 是否启用XOR模式
     */
    public void drawString(String text, int x, int y, int width, int height, CustomColor color, CustomFont font, boolean useXor) {
        try {
            if (useXor) enableXorMode();
            
            setStrokingColor(color);
            int fontHeight = Math.round((font.getFont().getFontDescriptor().getCapHeight()) / 1000 * height);
            float textWidth = font.getFont().getStringWidth(text) / 1000 * height;

            contentStream.setFont(font.getFont(), height);
            contentStream.setHorizontalScaling(100 + width);
            contentStream.beginText();
            contentStream.newLineAtOffset(originX + x, originY - fontHeight - y);
            contentStream.showText(text);
            contentStream.endText();

            if (useXor) restoreDefaultMode();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 在画布上绘制矩形
     *
     * @param x 矩形左上角的x坐标
     * @param y 矩形左上角的y坐标
     * @param width 矩形宽度
     * @param height 矩形高度
     * @param borderThickness 矩形边框厚度
     * @param color 矩形颜色
     * @param degreeOfCornerRounding 矩形圆角程度,取值范围1到8
     * @param useXor 是否启用XOR模式
     */
    public void drawRectangle(int x, int y, int width, int height, int borderThickness, CustomColor color, int degreeOfCornerRounding, boolean useXor) {
        try {
            if (useXor) enableXorMode();
            
            setStrokingColor(color);
            if (borderThickness > width / 2) borderThickness = width / 2;
            else if (borderThickness > height / 2) borderThickness = height / 2;

            float borderOffset = borderThickness / 2.0f;
            float adjustedX = originX + x + borderOffset;
            float adjustedY = originY - y - height + borderOffset;
            float adjustedWidth = width - borderThickness;
            float adjustedHeight = height - borderThickness;

            contentStream.setLineWidth(borderThickness);
            contentStream.addRect(adjustedX, adjustedY, adjustedWidth, adjustedHeight);
            contentStream.stroke();

            if (useXor) restoreDefaultMode();
        } catch (NumberFormatException | IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 在画布上绘制线条
     *
     * @param x 线条起点的x坐标
     * @param y 线条起点的y坐标
     * @param width 线条水平长度
     * @param height 线条垂直长度
     * @param thickness 线条厚度
     * @param color 线条颜色
     * @param useXor 是否启用XOR模式
     */
    public void drawLine(int x, int y, int width, int height, int thickness, CustomColor color, boolean useXor) {
        try {
            if (useXor) enableXorMode();
            
            setStrokingColor(color);
            float adjustedX = originX + x;
            float adjustedY = originY - y;

            contentStream.setLineWidth(thickness);
            contentStream.moveTo(adjustedX, adjustedY);
            contentStream.lineTo(adjustedX + width, adjustedY - height);
            contentStream.stroke();

            if (useXor) restoreDefaultMode();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void setStrokingColor(CustomColor color) throws NumberFormatException, IOException {
        contentStream.setStrokingColor(
                Integer.parseInt(color.getHexadecimal().substring(1, 3), 16) / 255f,
                Integer.parseInt(color.getHexadecimal().substring(3, 5), 16) / 255f,
                Integer.parseInt(color.getHexadecimal().substring(5, 7), 16) / 255f
        );
    }

    // 新增:保存文档方法
    public void saveDocument(String filePath) throws IOException {
        contentStream.close();
        pdDocument.save(filePath);
        pdDocument.close();
    }

    // 自定义颜色类
    public static class CustomColor {
        public static final CustomColor BLACK = new CustomColor("#000000");
        private String hexadecimal;

        public CustomColor(String hexadecimal) {
            this.hexadecimal = hexadecimal;
        }

        public String getHexadecimal() {
            return hexadecimal;
        }
    }

    // 自定义字体类
    public static class CustomFont {
        public static final CustomFont A = new CustomFont();
        private PDFont font;

        public CustomFont() {
            // 实际使用时初始化具体字体,例如PDType1Font.HELVETICA
        }

        public PDFont getFont() {
            return font;
        }
    }
}

带XOR效果的使用示例

public class Main {
    public static void main(String[] args) {
        PDFRenderer pdfRenderer = new PDFRenderer();

        // 绘制底层黑色矩形
        pdfRenderer.drawRectangle(50, 50, 200, 100, 2, PDFRenderer.CustomColor.BLACK, 2, false);
        // 用XOR模式绘制白色矩形,重叠区域颜色反转
        pdfRenderer.drawRectangle(100, 75, 100, 50, 2, new PDFRenderer.CustomColor("#FFFFFF"), 2, true);

        // 用XOR模式绘制文本,覆盖黑色区域时颜色反转
        pdfRenderer.drawString("XOR Highlight Text", 70, 90, 100, 30, new PDFRenderer.CustomColor("#FFFFFF"), PDFRenderer.CustomFont.A, true);

        try {
            pdfRenderer.saveDocument("xor-demo.pdf");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. 注意事项

  • XOR混合模式的支持依赖于PDF阅读器,现代主流阅读器(如Adobe Acrobat、Chrome)均支持,老旧阅读器可能不兼容
  • 绘制完成后必须恢复默认图形状态,否则后续所有元素都会使用XOR模式
  • 文本的XOR效果需要确保文本颜色与底层颜色有足够对比度,才能明显看出反转效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:47:04