- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我们正在使用 PDFBox 从 Java 桌面应用程序打印一些 PDF,并且 PDF 包含太多空格(不幸的是,修复 PDF 生成器不是一个选项)。
我遇到的问题是确定页面上实际内容的位置,因为裁剪/媒体/修剪/艺术/出血框没有用。有没有比将页面呈现为图像并检查哪些像素保持白色更好/更快的简单有效的方法?
最佳答案
正如您在评论中提到的那样
it can be assumed that there is no background or other elements that would need special handling,
我将展示一个没有任何此类特殊处理的基本解决方案。
要在不实际渲染位图和检查位图像素的情况下找到边界框,必须扫描页面内容流的所有指令以及从那里引用的任何 XObject。确定每条指令绘制的东西的边界框,并最终将它们组合成一个框。
这里介绍的简单框查找器通过简单地返回它们联合的边界框来组合它们。
为了扫描内容流的指令,PDFBox 提供了许多基于PDFStreamEngine
的类。简单框查找器派生自 PDFGraphicsStreamEngine
,它通过一些与 vector 图形相关的方法扩展了 PDFStreamEngine
。
public class BoundingBoxFinder extends PDFGraphicsStreamEngine {
public BoundingBoxFinder(PDPage page) {
super(page);
}
public Rectangle2D getBoundingBox() {
return rectangle;
}
//
// Text
//
@Override
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode, Vector displacement)
throws IOException {
super.showGlyph(textRenderingMatrix, font, code, unicode, displacement);
Shape shape = calculateGlyphBounds(textRenderingMatrix, font, code);
if (shape != null) {
Rectangle2D rect = shape.getBounds2D();
add(rect);
}
}
/**
* Copy of <code>org.apache.pdfbox.examples.util.DrawPrintTextLocations.calculateGlyphBounds(Matrix, PDFont, int)</code>.
*/
private Shape calculateGlyphBounds(Matrix textRenderingMatrix, PDFont font, int code) throws IOException
{
GeneralPath path = null;
AffineTransform at = textRenderingMatrix.createAffineTransform();
at.concatenate(font.getFontMatrix().createAffineTransform());
if (font instanceof PDType3Font)
{
// It is difficult to calculate the real individual glyph bounds for type 3 fonts
// because these are not vector fonts, the content stream could contain almost anything
// that is found in page content streams.
PDType3Font t3Font = (PDType3Font) font;
PDType3CharProc charProc = t3Font.getCharProc(code);
if (charProc != null)
{
BoundingBox fontBBox = t3Font.getBoundingBox();
PDRectangle glyphBBox = charProc.getGlyphBBox();
if (glyphBBox != null)
{
// PDFBOX-3850: glyph bbox could be larger than the font bbox
glyphBBox.setLowerLeftX(Math.max(fontBBox.getLowerLeftX(), glyphBBox.getLowerLeftX()));
glyphBBox.setLowerLeftY(Math.max(fontBBox.getLowerLeftY(), glyphBBox.getLowerLeftY()));
glyphBBox.setUpperRightX(Math.min(fontBBox.getUpperRightX(), glyphBBox.getUpperRightX()));
glyphBBox.setUpperRightY(Math.min(fontBBox.getUpperRightY(), glyphBBox.getUpperRightY()));
path = glyphBBox.toGeneralPath();
}
}
}
else if (font instanceof PDVectorFont)
{
PDVectorFont vectorFont = (PDVectorFont) font;
path = vectorFont.getPath(code);
if (font instanceof PDTrueTypeFont)
{
PDTrueTypeFont ttFont = (PDTrueTypeFont) font;
int unitsPerEm = ttFont.getTrueTypeFont().getHeader().getUnitsPerEm();
at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
}
if (font instanceof PDType0Font)
{
PDType0Font t0font = (PDType0Font) font;
if (t0font.getDescendantFont() instanceof PDCIDFontType2)
{
int unitsPerEm = ((PDCIDFontType2) t0font.getDescendantFont()).getTrueTypeFont().getHeader().getUnitsPerEm();
at.scale(1000d / unitsPerEm, 1000d / unitsPerEm);
}
}
}
else if (font instanceof PDSimpleFont)
{
PDSimpleFont simpleFont = (PDSimpleFont) font;
// these two lines do not always work, e.g. for the TT fonts in file 032431.pdf
// which is why PDVectorFont is tried first.
String name = simpleFont.getEncoding().getName(code);
path = simpleFont.getPath(name);
}
else
{
// shouldn't happen, please open issue in JIRA
System.out.println("Unknown font class: " + font.getClass());
}
if (path == null)
{
return null;
}
return at.createTransformedShape(path.getBounds2D());
}
//
// Bitmaps
//
@Override
public void drawImage(PDImage pdImage) throws IOException {
Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
add(ctm.transformPoint(x, y));
}
}
}
//
// Paths
//
@Override
public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException {
addToPath(p0, p1, p2, p3);
}
@Override
public void clip(int windingRule) throws IOException {
}
@Override
public void moveTo(float x, float y) throws IOException {
addToPath(x, y);
}
@Override
public void lineTo(float x, float y) throws IOException {
addToPath(x, y);
}
@Override
public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {
addToPath(x1, y1);
addToPath(x2, y2);
addToPath(x3, y3);
}
@Override
public Point2D getCurrentPoint() throws IOException {
return null;
}
@Override
public void closePath() throws IOException {
}
@Override
public void endPath() throws IOException {
rectanglePath = null;
}
@Override
public void strokePath() throws IOException {
addPath();
}
@Override
public void fillPath(int windingRule) throws IOException {
addPath();
}
@Override
public void fillAndStrokePath(int windingRule) throws IOException {
addPath();
}
@Override
public void shadingFill(COSName shadingName) throws IOException {
}
void addToPath(Point2D... points) {
Arrays.asList(points).forEach(p -> addToPath(p.getX(), p.getY()));
}
void addToPath(double newx, double newy) {
if (rectanglePath == null) {
rectanglePath = new Rectangle2D.Double(newx, newy, 0, 0);
} else {
rectanglePath.add(newx, newy);
}
}
void addPath() {
if (rectanglePath != null) {
add(rectanglePath);
rectanglePath = null;
}
}
void add(Rectangle2D rect) {
if (rectangle == null) {
rectangle = new Rectangle2D.Double();
rectangle.setRect(rect);
} else {
rectangle.add(rect);
}
}
void add(Point2D... points) {
for (Point2D point : points) {
add(point.getX(), point.getY());
}
}
void add(double newx, double newy) {
if (rectangle == null) {
rectangle = new Rectangle2D.Double(newx, newy, 0, 0);
} else {
rectangle.add(newx, newy);
}
}
Rectangle2D rectanglePath = null;
Rectangle2D rectangle = null;
}
(github 上的 BoundingBoxFinder)
如您所见,我从 PDFBox 示例类中借用了 calculateGlyphBounds
辅助方法。
您可以像这样使用 BoundingBoxFinder
为 PDDocument pdDocument
的给定 PDPage pdPage
沿边界框边缘绘制边框线:
void drawBoundingBox(PDDocument pdDocument, PDPage pdPage) throws IOException {
BoundingBoxFinder boxFinder = new BoundingBoxFinder(pdPage);
boxFinder.processPage(pdPage);
Rectangle2D box = boxFinder.getBoundingBox();
if (box != null) {
try ( PDPageContentStream canvas = new PDPageContentStream(pdDocument, pdPage, AppendMode.APPEND, true, true)) {
canvas.setStrokingColor(Color.magenta);
canvas.addRect((float)box.getMinX(), (float)box.getMinY(), (float)box.getWidth(), (float)box.getHeight());
canvas.stroke();
}
}
}
( DetermineBoundingBox 辅助方法)
结果是这样的:
请注意,BoundingBoxFinder
确实不是很复杂;特别是它不会忽略不可见的内容,如白色背景矩形、在“不可见”渲染模式下绘制的文本、白色填充路径覆盖的任意内容、位图图像的白色部分……此外,它确实忽略了剪辑路径,很奇怪混合模式、注释、...
扩展类以正确处理这些情况非常简单,但要添加的代码总和将超出堆栈溢出答案的范围。
对于此答案中的代码,我使用了当前的 PDFBox 3.0.0-SNAPSHOT 开发分支,但对于当前的 2.x 版本它也应该开箱即用。
关于java - 如何使用 PDFBox 确定实际 PDF 内容的位置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52821421/
我正在为我的按钮使用 onClick 功能。我的按钮代码如下所示: Button 1 Button 2 我的 JS 函数如下所示: function fadeNext(selectedId, spee
首先,我想提一下,我理解每个人在不提供至少一些试验或错误的情况下提出问题的感受,但这纯粹是一种知识需求,话虽如此,我会去提前问。 我一直无法弄清楚如何将保存在 MySQL 表中的 600-1000 个
我想做的事情有点令人困惑,而且我英语不太好,所以我先把代码贴在这里,这样你就可以很容易地理解: 以下是表单内容: Testing for Stackoverflow Option1
我学习 SDL 二维编程已有一段时间了,现在我想创建一个结合使用 SDL 和 OpenGL 的程序。我是这样设置的: SDL_Init(SDL_INIT_VIDEO); window = SDL_Cr
我创建了 2 个 data-* 标签。数据类别和数据标签。单击 href 标签后,我想复制该数据类别和数据标签以形成输入。我的代码是:
我想用 CSS 换行。我正在使用内容。 td:before { content: "Test\A Test2"; } 它不工作。如何正确
这个问题已经有答案了: Java Class that implements Map and keeps insertion order? (8 个回答) 已关闭 6 年前。 我有一个 HashMap
我正在尝试使用 JMeter 执行端到端测试。测试涉及写入SFTP文件夹并从另一个SFTP文件夹读取写入操作生成的文件。 我能够使用 JMeter SSH SFTP 插件连接到 SFTP 文件夹,并能
您好,我有带有标准服务器端 Servlet 的 GWT 客户端。 我可以从 GWT 客户端上传文件并在服务器端读取其内容 我可以将其作为字符串发送回客户端 但是 我有 GWT FormPanel与操作
我在 Plone 4.3.9 中创建了一个自定义类型的灵巧性,称为 PersonalPage,必须只允许在特定文件夹中使用 成员文件夹/用户文件夹 . 在他的 FTI 中,默认情况下 False .
在新(更新)版本的应用程序中更改小部件布局的最佳做法是什么?当新版本提供更新、更好的小部件时,如何处理现有小部件? 最佳答案 我认为您必须向用户显示一个弹出窗口,说明“此版本中的新功能”并要求他们重新
在我的应用程序中,我使用支持 View 寻呼机和 PagerTabStrip。进入查看寻呼机我有一些 fragment ,进入其中一个我正在使用支持卡片 View 。运行应用程序后,所有卡片 View
我有以下布局文件。基本上我有谷歌地图,在左上角我有一个 TextView,我需要在其中每 15 秒保持一次计数器以刷新 map 。布局很好。
我使用如下结构: HashMap > > OverallMap 如果我这样做: OverallMap . clear ( ) clear() 丢弃的所有内容(HashMap 对象、Integer 对
我在数据库中有 1000 张图像。在页面加载时,我随机显示 60 张图片,当用户滚动时,我通过 AJAX 请求添加 20 张图片。 第一种方法 我所做的是将所有图像加载到一个容器中,然后隐藏所有图像并
我正在使用 woocommerce 创建一个网上商店。 我想在每个产品上添加一个包含产品信息的表格,例如颜色、交货时间等等。 但是当我添加这张表时。本产品消失后的所有内容。 我的表的代码: td {
This question already has an answer here: What does an empty value for the CSS property content do?
因此,我正在与我的 friend 一起为 Google Chrome 开发一个扩展程序,对于大多数功能(即日历、设置等),我们打开一个模式,这样我们就不必重定向到另一个页面。当您在内容之外单击时,我们
我将可变高度的 CSS 框设置为在更大的 div 中向左浮动。现在我想添加一个标题,其中文本在框的左侧垂直显示(旋转 90 度),如下面的链接所示(抱歉还不能发布图片)。 http://imagesh
相关页面位于 www.codykrauskopf.com/circus 如果您查看我页面的右侧,在半透明容器和浏览器窗口边缘之间有一个间隙。我看了看,出于某种原因,wrap、main、content
我是一名优秀的程序员,十分优秀!