不幸的是,问题和 cmets 仅包括(通过运行示例项目)两个源文档的实际结果和描述
注释文本应在顶部和底部标记上居中对齐,在右侧标记上与左侧对齐,在左侧标记上与右侧对齐。对齐对我不起作用,因为 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));
使用方法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,我怀疑为Location.TOP 和Location.BOTTOM 添加fontPadding 是否有意义,尤其是考虑到OP 的愿望
The annotating text should be center aligned on the top and bottom marker
对于要居中的文本,它不应偏离中心。
getYTextOffset 的情况比较困难。 OP 的代码建立在两个误解之上:它假设
-
moveTextPositionByAmount选择的文本位置是左下角,并且
- 字体大小就是字符高度。
实际上文本位置是定位在基线上的,下一个绘制字形的字形原点会定位在那里,例如
因此,必须校正 y 定位以考虑下降(以整个字形高度为中心)或仅使用上升(以高于基线字形高度为中心)。
字体大小并不表示实际的字符高度,但 是这样排列的,以便字体大小为 1 的紧密间隔的文本行的标称高度为 1 个单位。“紧密间隔”意味着某些字体大小中包含少量额外的行间空间。
本质上,要垂直居中,必须决定要居中的位置、整个高度或高于基线的高度、仅第一个字母、整个标签或所有字体字形。 PDFBox 无法为所有情况提供必要的信息,但PDFont.getFontBoundingBox() 之类的方法应该会有所帮助。