基于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
相关产品推荐
相关产品推荐

