java - 计算正确的文本宽度

标签 java pdf-generation pdfbox

我需要阅读由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

谢谢你的帮助!

最佳答案

不幸的是,问题和评论仅包含(通过运行示例项目)两个源文档的实际结果和说明


带注释的文本在顶部和底部标记上应居中对齐,在右侧标记上应与左侧对齐,在左侧标记上应与右侧对齐。对齐方式对我来说不起作用,因为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不能包括的内容。

使用哪个PDPageContentStream构造函数

OP的代码使用此构造函数将内容流添加到手头的页面上:

PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true);


该构造函数追加(第一个true)并压缩(第二个true),但不幸的是,它在原有内容留下的图形状态下继续运行。

图形的详细状态对于当前观察的重要性:


转换矩阵-可能已更改为缩放(或旋转,倾斜,移动...)以添加任何新内容
字符间距-可能已更改为将任何新添加的字符彼此之间的距离更近或更远
单词间距-可能已更改为将添加的新单词彼此靠近或远离
水平缩放-可能已更改为缩放添加的任何新字符
文本上升-可能已更改为替换垂直添加的任何新字符


因此,应该选择一个构造函数,该构造函数还将重置图形状态:

PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);


第三个true告诉PDFBox重置图形状态,即用保存状态/恢复状态运算符对包围前一个内容。

这与给定的样本文档有关,至少变换矩阵已更改。

设置和使用CalRGB颜色空间

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。
由于OP已忽略,因此他希望使用所选字体大小来显示字符串的宽度。但是字体对象不了解当前字体大小,并且返回字体大小为1的宽度。由于OP作为Math.min(pageWidth, pageHeight) / 20动态选择字体大小,因此该因素有所不同。对于两个给定的样本文档,大约有42个,但其他文档可能完全不同。


定位文字

OP的代码从身份文本矩阵开始按如下所示放置文本:

contentStream.moveTextPositionByAmount(
    marker.endX + marker.getXTextOffset(textWidth, fontPadding),
    marker.endY + marker.getYTextOffset(fontSize, fontPadding));


使用方法getXTextOffsetgetYTextOffset

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,我怀疑为fontPaddingLocation.TOP添加Location.BOTTOM是否有意义,尤其是考虑到OP的需求

The annotating text should be center aligned on the top and bottom marker


为了使文本居中,不应将其偏离中心。

getYTextOffset的情况更加困难。 OP的代码建立在两个误解之上:


moveTextPositionByAmount选择的文本位置在左下方,并且
字体大小是字符高度。


实际上,文本位置位于基线上,下一个绘制的字形的字形原点将位于该位置,例如



因此,必须校正y位置以考虑下降(以整个字形高度为中心),或者仅使用上升(以基线以上字形高度为中心)。

字体大小并不表示实际的字符高度,而是进行排列,以便使紧密间隔的文本行的名义高度对于字体大小1为1单位。“紧密间隔”表示包含少量的额外行间间隔在字体大小。

本质上,要垂直居中,必须确定要居中的内容,整个高度或基线以上的高度,仅第一个字母,整个标签或所有字形。 PDFBox不能随时为所有情况提供必要的信息,但是PDFont.getFontBoundingBox()之类的方法应该会有所帮助。

关于java - 计算正确的文本宽度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27600166/

相关文章:

java - 在java中拖放图像

java - Play Framework : Generate PDF from template that uses Javascript for graphing

java - PDFBox+充气城堡-签名PDF

java - COSStream 已关闭且无法读取。也许其随附的 PDDocument 已关闭?

java - 如何为 JFrame 子类添加一个 keylistener

java - 如何通过仅替换一位数字来继续查找 2 个整数的最大总和?

java - Java ProcessBuilder发起的进程内存消耗

java - PDFBox Matrix 中的参数意味着什么

javascript - 从 Tomcat 提供的网页生成 PDF

java - 很少有人扫描条形码,但很少有人不使用 Zxing