计算文本的正确宽度

and*_*eas 2 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();
Run Code Online (Sandbox Code Playgroud)

*0.043f作为一个文档的近似值,但下一个文档失败.我需要重置除文本矩阵之外的任何其他变换矩阵吗?

编辑:一个完整​​的想法示例项目是在github与测试和示例pdfs:https://github.com/ascheucher/pdf-stamp-prototype

谢谢你的帮助!

mkl*_*mkl 9

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

注释文本应在顶部和底部标记上居中对齐,在右侧标记上与左侧对齐,在左侧标记上与右侧对齐.对齐对我来说不起作用,因为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;
Run Code Online (Sandbox Code Playgroud)

这些似乎被选择为与页面大小相关的光学上令人愉悦.但是,媒体框一般不是最终显示或打印的页面大小,裁剪框是.因此,它应该是

PDRectangle pageSize = page.findCropBox();
Run Code Online (Sandbox Code Playgroud)

(实际上修剪框,修剪后成品页面预期尺寸,甚至可能更合适;修剪框默认为裁剪框.有关详细信息,请阅读此处.)

这与给定的示例文档无关,因为它们不包含显式裁剪框定义,因此裁剪框默认为媒体框.但是,它可能与其他文件有关,例如OP无法包括的文件.

使用哪个PDPageContentStream构造函数

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

PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true);
Run Code Online (Sandbox Code Playgroud)

这个构造函数追加(第一个true)并压缩(第二个true)但不幸的是它继续处于由预先存在的内容留下的图形状态.

手边观察的图形状态的详细信息:

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

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

PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);
Run Code Online (Sandbox Code Playgroud)

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

这与给定的样本文档相关,至少改变了变换矩阵.

设置和使用CalRGB色彩空间

OP的代码将描边和非描边颜色空间设置为校准的颜色空间:

contentStream.setStrokingColorSpace(new PDCalRGB());
contentStream.setNonStrokingColorSpace(new PDCalRGB());
Run Code Online (Sandbox Code Playgroud)

遗憾的是,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);
Run Code Online (Sandbox Code Playgroud)

(int, int, int)不幸的是,这些重载使用RGrg运算符隐式选择DeviceRGB颜色空间.若要不覆盖当前颜色空间,请使用(float[])带有规范化(0..1)值的重载.

虽然这与观察到的问题无关,但它会导致PDF查看器出现错误消息.

计算绘制字符串的宽度

OP的代码使用计算绘制字符串的宽度

float textWidth = font.getStringWidth(marker.id) * 0.043f;
Run Code Online (Sandbox Code Playgroud)

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));
Run Code Online (Sandbox Code Playgroud)

使用方法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;
}
Run Code Online (Sandbox Code Playgroud)

在的情况下,getXTextOffset我怀疑加入fontPaddingLocation.TOPLocation.BOTTOM有意义的,尤其是在OP的欲望的光

The annotating text should be center aligned on the top and bottom marker
Run Code Online (Sandbox Code Playgroud)

要使文本居中,不应偏离中心.

这种情况getYTextOffset比较困难.OP的代码建立在两个误解之上:它假定

  • 选择的文本位置moveTextPositionByAmount是左下角,和
  • 字体大小是字符高度.

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

'g'的字形原点,宽度和边界框

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

并且字体大小不表示实际的字符高度,但是被安排成使得紧密间隔的文本行的标称高度对于字体大小1 是1个单位."紧密间隔"意味着包含一些少量的附加行间空间在字体大小.

实质上,为了垂直居中,必须决定以什么为中心,整个高度或高于基线高度,仅首字母,整个标签或所有字体字形.PDFBox并不容易为所有情况提供必要的信息,但方法PDFont.getFontBoundingBox()应该有所帮助.