计算文本的正确宽度

2023-11-30

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

这个构造函数appends (first true) and 压缩(第二true)但不幸的是,它继续处于先前存在的内容留下的图形状态中。

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

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

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

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

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

这与给定的示例文档相关,至少变换矩阵发生了变化。

设置和使用CalRGB色彩空间

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

contentStream.setStrokingColorSpace(new PDCalRGB());
contentStream.setNonStrokingColorSpace(new PDCalRGB());

很遗憾new PDCalRGB() does not创建一个有效的CalRGB颜色空间对象,其必需白点值缺失。因此,在选择校准的色彩空间之前,请对其进行正确的初始化。

此后OP的代码使用设置颜色

contentStream.setStrokingColor(marker.color.r, marker.color.g, marker.color.b);
contentStream.setNonStrokingColor(marker.color.r, marker.color.g, marker.color.b);

These (int, int, int)不幸的是,重载使用RG and rg运算符隐式选择设备RGB色彩空间。要不覆盖当前颜色空间,请使用(float[])改为使用标准化 (0..1) 值重载。

虽然这与观察到的问题无关,但它会导致 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 and 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 for Location.TOP and Location.BOTTOM有道理,特别是考虑到OP的愿望

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

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

的情况下getYTextOffset比较困难。 OP的代码建立在两个误解之上:它假设

  • 所选择的文本位置moveTextPositionByAmount是左下角,并且
  • 字体大小就是字符高度。

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

Glyph origin, width, and bounding box for 'g'

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

字体大小并不表示实际的字符高度,而是表示进行排列,使得紧密间隔的文本行的标称高度为 1 个单位对于字体大小 1。“紧密间隔”意味着字体大小中包含一些少量的额外行间空间。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

计算文本的正确宽度 的相关文章

随机推荐

  • 使用 jquery 删除不带

    我有一个定义列表 我需要删除所有 dt 没有标签的人 dd 在这种特殊情况下 Herramientas Suplementos Repuestos Herramientas 和 Antipincaduras 该列表可能会有很大差异 因为它取
  • 更改 PHP 中的时区

    好吧 快速提问 服务器正在东部时间运行 PHP程序需要使用中央时间进行日期计算 目前 我将这一行放在脚本的最顶部 putenv TZ US Central 这是最好的方法吗 还是有一些我不知道的 PHP 技巧 Cheers 您可以使用dat
  • Xcode 4异常断点过滤

    中断 Objective C 异常确实非常有用 并且是调试问题的最佳方法NSArray等等 然而 在实际编程时 异常也是一个很好用的东西 Xcode 提供了两种中断 Obj C 异常的选择 每当抛出异常时就中断 每当捕获异常时就中断 打破捕
  • 用于类延续的 Xcode 代码片段?

    我有一个用于创建属性的 Xcode 4 6 代码片段 一个用于弱属性 一个用于强属性 Typing propstrong在接口声明 h 文件 中工作得很好 但是当我在课堂延续中执行此操作时 没有向我提供代码片段 interface MyCl
  • 搜索和链接库目录的顺序

    我很难理解搜索目录以链接到库的顺序 我有一个CentOS6系统和3个版本的gcc 4 4 7 4 7 2 4 9 2 系统版本为4 4 7 版本4 7 2和4 9 2为模块 在 etc ld so conf d 有两个文件 gcc 4 7
  • 如何安装适用于 Python 2.7 的 PyQT4?

    我正在尝试在 Python 2 7 9 上安装 PyQT4 我在 Mac OS X 上 所以我尝试通过 Homebrew 和 Macports 安装它 不幸的是它们似乎都不起作用 这是我尝试过的 brew install python qt
  • 如何检查一个数组元素是否完全存在于php中的另一个数组中[重复]

    这个问题在这里已经有答案了 我有两个数组 例如 array1 1 2 3 4 5 6 7 8 9 array2 4 6 9 有没有什么函数可以让我确定array2完全存在于array1 我知道我可以使用in array 循环中的函数 但在我
  • 包含文件中的包含路径失败

    我在 PHP 包含路径方面遇到了一些麻烦 并且不明白 那里出了什么问题 首先 我想向您展示我的文件 目录结构 文件 目录结构 index php foo baz php bar inc php asdf qwerty inc php ind
  • 批处理脚本帮助 - 将 DelayedExpansion Var 的子字符串替换为另一个 DelayedExpansion Var

    基本上我正在尝试做 var1 SomeText var2 但这段代码不起作用 我缺少什么 在执行使用变量进行搜索和 或替换的搜索和替换操作时 扩展顺序至关重要 内部变量必须在外部搜索和替换扩展发生之前扩展 尝试对两者都使用延迟扩展显然是行不
  • 如何在我的本地 Geth 账户中获取一些以太币?

    我已经设置了 Geth 并创建了一些没有余额的帐户 所以我无法进行任何交易 因为它需要花费 Gas 费 如何创建具有一些初始余额的帐户 以便我可以测试我的合同 我使用以下命令创建了帐户 gt personal newAccount 假设您正
  • 确保 for 循环中的可观察对象在执行其他代码之前全部完成

    我有一段代码 如下所示 getPersons subscribe persons gt for const person of persons getAddress person id subscribe address gt person
  • Android 11 - 访问Android/data目录

    除了 root 之外 还有什么方法可以访问 Android 11 上的 SD 卡的 android data 目录吗 我的 非 Play 商店 应用程序需要访问另一个应用程序的公共 Android data com appname 文件夹
  • PHP 8.1.1 的 __toString() 方法问题 - 如果在声明类本身之前创建实例,则导致找不到类

    从 PHP 7 3 升级到 8 1 1 后遇到问题 当然还有很多事情要做 但这有点奇怪 这个例子对我不起作用 出现错误致命错误 未捕获错误 找不到类 TestC C xampp81 htdocs helpdesk811 test81 ind
  • 修复了滚动项目时导航抽屉中的导航标题

    当前状态 具有 NavigationHeader 和 NavigationMenu 项的 NavigationDrawer 这些项目数量很大 因此需要滚动才能访问底部的项目 要求 向下滚动到底部时 导航标题应保持固定 这是我的 Activi
  • Flutter:英雄过渡+小部件动画同时进行?

    因此 我对 Flutter 的特定动画案例有一些疑问 基本上 我想做的是同时运行用于路线更改的英雄过渡和相邻小部件上的自定义动画 具体来说 我的根目录中有一个自定义的 InheritedWidget 它从 StatefulWidget 父级
  • 获取 XMPP 聊天历史记录 OpenFire

    我正在尝试使用 iOS 中的 XMPPFramework 和 OpenFire 服务器来实现聊天应用程序 我的聊天工作正常 我正在尝试从服务器检索聊天历史记录 当然 我已经在服务器上启用了邮件存档 这是我发送的请求
  • 如何在 Objective-C 中保持会话?

    所以我遇到了以下问题 我有一个登录视图控制器和一个表单视图控制器 在登录视图控制器上 我向 PHP 脚本发送 POST 请求 该脚本验证用户是否具有访问权限 该脚本仅返回 1 或 0 因此我可以选择关闭或维护视图控制器 当凭据正确传递时 用
  • C# 中的对象指针(object*)

    我想要做的是 我想传递一个指向函数的指针 该函数可以是任何类型的变量 int long string 甚至是一个类 我的意思是我应该能够传递任何变量的指针 我就是这样做的 unsafe class whatever whatever obj
  • 声明原子指针与原子指针

    据我所知 以下声明创建了一个值数组 每个值都是原子的 Atomic int x 10 但是 我不清楚这是否 Atomic int x x calloc 10 sizeof int 创建一个包含 10 个原子整数的数组 或者是指向非原子整数数
  • 计算文本的正确宽度

    我需要阅读由 AutoCAD 导出为 PDF 的计划 并使用 PDFBox 在其上放置一些带有文本的标记 除了计算写在标记旁边的文本宽度之外 一切正常 我浏览了整个 PDF 规范并详细阅读了涉及图形和文本的部分 但无济于事 据我了解 字形坐