【问题标题】:Calculate correct width of a text计算文本的正确宽度
【发布时间】:2015-02-20 09:40:54
【问题描述】:

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

感谢您的帮助!

【问题讨论】:

  • 您能否分享示例文档(例如,您的代码可以工作的地方和不工作的地方)和更多代码,尤其是关于标记方法以及如何开始编辑内容流的代码?
  • @mkl:我已经把代码推送到了github。包括测试和测试数据。
  • 我稍后再看。目前正在购买圣诞节。 ;)
  • 不着急,在圣诞节准备期间也是如此。无论如何,直到一月份才有时间......但提前谢谢!祝您和您的家人圣诞快乐!
  • 谢谢。我希望你度过了一个愉快的假期。我目前正在研究样本。我使用 maven,不知道,所以需要一些微小的补丁。你能指出哪个测试显示失败,哪个测试成功吗?当您将大多数测试设置为 @Ignore 时)假设​​其余两个测试证明了问题,不是吗?

标签: java pdf-generation pdfbox


【解决方案1】:

不幸的是,问题和 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) 重载使用 RGrg 运算符隐式选择 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,我怀疑为Location.TOPLocation.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() 之类的方法应该会有所帮助。

【讨论】:

  • 谢谢,只有一点,我不清楚。什么是字体边界框。字形边界框描述的内容很清楚,但字体不知道它描述了哪些字符,也不知道字体的大小。第二个当然可以用计算出的字体大小来缩放,但我不明白字体边界框。
  • 根据规范:一个矩形(见 7.9.5,“矩形”),以字形坐标系表示,应指定字体边界框。这应该是包围形状的最小矩形,如果字体的所有字形都放置在其原点重合的位置,然后进行填充。
  • 希望 Stackoverflow 有办法奖励像这样的优秀答案
猜你喜欢
  • 2014-06-02
  • 2010-12-07
  • 1970-01-01
  • 2011-10-05
  • 2011-11-25
  • 2010-09-12
  • 1970-01-01
  • 2012-03-08
相关资源
最近更新 更多