我需要阅读AutoCAD导出到PDF的计划,并使用PDFBox在其上放置一些带有文本的标记。 一切都运行正常,除了计算文本的宽度,这是在标记旁边写的。
我浏览了整个PDF规范并详细阅读了处理图形和文本的部分,但无济于事。据我所知,字形坐标空间设置在用户坐标空间的1/1000。因此,宽度需要按比例增加1000,但它仍然只是实际宽度的一小部分。
这就是我正在做的定位文本:
float textWidth = font.getStringWidth(marker.id) * 0.043f;
contentStream.beginText();
contentStream.setTextScaling(1, 1, 0, 0);
contentStream.moveTextPositionByAmount(
marker.endX + marker.getXTextOffset(textWidth, fontPadding),
marker.endY + marker.getYTextOffset(fontSize, fontPadding));
contentStream.drawString(marker.id);
contentStream.endText();
* 0.043f作为一个文档的近似值,但下一个文档失败。 我是否需要重置除文本矩阵之外的任何其他变换矩阵?
编辑:一个完整的想法示例项目在github上有测试和示例pdf:https://github.com/ascheucher/pdf-stamp-prototype
感谢您的帮助!
答案 0 :(得分:8)
不幸的是,问题和评论仅包括(通过运行示例项目)两个源文档的实际结果和描述
注释文本应在顶部和底部标记上居中对齐,在右侧标记上与左侧对齐,在左侧标记上与右侧对齐。对齐对我来说不起作用,因为font.getSTringWidth(..)只返回它看起来的一小部分。两种PDF中的差异似乎都不同。
但不是修复的具体样本差异。
但是,代码中存在一些问题,可能导致此类观察(以及其他问题!)。应该先修复它们;这可能已经解决了OP所观察到的问题。
OP的代码从媒体框中导出几个值:
PDRectangle pageSize = page.findMediaBox();
float pageWidth = pageSize.getWidth();
float pageHeight = pageSize.getHeight();
float lineWidth = Math.max(pageWidth, pageHeight) / 1000;
float markerRadius = lineWidth * 10;
float fontSize = Math.min(pageWidth, pageHeight) / 20;
float fontPadding = Math.max(pageWidth, pageHeight) / 100;
这些似乎被选择为与页面大小相关的光学上令人愉悦。但是,媒体框通常不是最终的显示或打印的页面大小,裁剪框是。因此,它应该是
PDRectangle pageSize = page.findCropBox();
(实际上修剪框,修剪后的预期尺寸,甚至可能更合适;修剪框默认为裁剪框。详情请阅读here。 )
这与给定的示例文档无关,因为它们不包含显式裁剪框定义,因此裁剪框默认为媒体框。但是,它可能与其他文件有关,例如OP无法包括的那些。
OP的代码使用以下构造函数将内容流添加到手头的页面:
PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true);
此构造函数追加(第一个true
)和压缩(第二个true
)但不幸的是它继续处于由预先存在的内容。
手头观察的重要图形状态详情:
因此,应该选择一个也重置图形状态的构造函数:
PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);
第三个true
告诉PDFBox重置图形状态,即用保存状态/恢复状态运算符对包围前一个内容。
这与给定的样本文档相关,至少转换矩阵已更改。
OP的代码将描边和非描边颜色空间设置为校准色域:
contentStream.setStrokingColorSpace(new PDCalRGB());
contentStream.setNonStrokingColorSpace(new PDCalRGB());
不幸的是new PDCalRGB()
不创建有效的 CalRGB 色彩空间对象,缺少所需的 WhitePoint 值。因此,在选择校准色空间之前,请将其正确初始化。
此后OP的代码使用
设置颜色contentStream.setStrokingColor(marker.color.r, marker.color.g, marker.color.b);
contentStream.setNonStrokingColor(marker.color.r, marker.color.g, marker.color.b);
不幸的是,这些(int, int, int)
重载会使用 RG 和 rg 运算符隐式选择 DeviceRGB 颜色空间。要不覆盖当前颜色空间,请使用带有标准化(0..1)值的(float[])
重载。
虽然这与观察到的问题无关,但它会导致PDF查看器出现错误消息。
OP代码使用
计算绘制字符串的宽度float textWidth = font.getStringWidth(marker.id) * 0.043f;
并且OP很惊讶
* 0.043f作为一个文档的近似值,但在下一个文档中失败。
有两个因素构建了这个"魔术"号:
由于OP已注明,因此在用户坐标空间的1/1000中设置了字形坐标空间,并且该数字在字形空间中,因此为0.001。< / p>
由于OP忽略了他想要使用他选择的字体大小的字符串宽度。但是字体对象不知道当前的字体大小,并返回字体大小为1的宽度。由于OP选择字体大小为Math.min(pageWidth, pageHeight) / 20
,因此该因素会有所不同。如果两个给定的样本文件大约有42个,但在其他文件中可能完全不同。
OP的代码从身份文本矩阵开始这样定位文本:
contentStream.moveTextPositionByAmount(
marker.endX + marker.getXTextOffset(textWidth, fontPadding),
marker.endY + marker.getYTextOffset(fontSize, fontPadding));
使用方法getXTextOffset
和getYTextOffset
:
public float getXTextOffset(float textWidth, float fontPadding) {
if (getLocation() == Location.TOP)
return (textWidth / 2 + fontPadding) * -1;
else if (getLocation() == Location.BOTTOM)
return (textWidth / 2 + fontPadding) * -1;
else if (getLocation() == Location.RIGHT)
return 0 + fontPadding;
else
return (textWidth + fontPadding) * -1;
}
public float getYTextOffset(float fontSize, float fontPadding) {
if (getLocation() == Location.TOP)
return 0 + fontPadding;
else if (getLocation() == Location.BOTTOM)
return (fontSize + fontPadding) * -1f;
else
return fontSize / 2 * -1;
}
如果getXTextOffset
我怀疑为fontPadding
和Location.TOP
添加Location.BOTTOM
是否有意义,特别是考虑到OP的愿望
The annotating text should be center aligned on the top and bottom marker
对于要居中的文本,不应偏离中心。
getYTextOffset
的情况更难。 OP的代码建立在两个误解之上:它假定
moveTextPositionByAmount
选择的文字位置是左下角,实际上文本位置位于基线上,下一个绘制的字形的字形原点将位于那里,例如。
因此,必须更正y定位以考虑下降(以整个字形高度为中心)或仅使用上升(以中心基线字形高度为中心)。
字体大小并不表示实际的字符高度,但的排列方式使得紧密间隔的文本行的标称高度为1个单位,用于字体大小1.&#34;紧密间隔&# 34;意味着字体大小中包含少量额外的行间空间。
本质上,为了垂直居中,必须决定以什么为中心,整个高度或高于基线的高度,仅首字母,整个标签或所有字体字形。 PDFBox并不容易为所有情况提供必要的信息,但PDFont.getFontBoundingBox()
等方法应该有所帮助。