【问题标题】:java / pdf text renderingjava/pdf文本渲染
【发布时间】:2013-08-02 14:49:02
【问题描述】:

我正在使用 Java 编写自己的 pdf 生成库,但在字体/文本渲染方面遇到了一些问题。 Java 中显示的文本(字体、字间距、字符间距...)与 PDF 中显示的不同。

在下面的示例中,我使用的是 PDF 基本字体之一的字体“Time New Roman”(因此我不必计算所有字体指标并将其输出到 pdf 中)。

所以具体来说,在我生成的 PDF 中,我有这个:

BT
/F5 16 Tf
849 921 Td
(Normal Return Distribution) Tj
ET

字体 F5 由对象 29 0 R 定义,即(仅 basefont,因此未指定文本度量):

29 0 obj <</Type /Font /Subtype /Type1 /BaseFont /Times-Roman>>
endobj

在 Java 中,我使用的是:

g2d.setFont(new Font("TimesRoman", Font.PLAIN, 16));
g2d.drawString("Normal Return Distribution", 849, 921);

我已将文本绘制成与文本边界匹配的矩形,在 Java 中一切正常(我在 java 中计算了字符串边界),但在 adobe acrobat reader 中,文本大于矩形。

这是一个屏幕截图(我是通过截取显示我的 PDF 的 Adob​​e Acrobat Reader 的屏幕截图并截取显示缓冲图像的程序的屏幕截图来构建的;然后复制/粘贴 pdf 屏幕截图的下面部分将我的程序屏幕截图的矩形转换为 MSPaint。要具有相同的矩形大小,我必须在 Adob​​e 中以原始大小的 65.5% 显示 pdf):

所以我们可以看到java en adobe中用来显示文字的字体是一样的。但文字在 Adob​​e 中似乎有点大。事实上,如果我叠加两个单词(一个来自 java 的,一个来自 adobe 的顶部),似乎单词间距是相同的,字母间距也是一样,但有些字母有 1 个像素的宽度差异。

为什么? 我能做些什么来解决这个问题?我尝试使用字符间距(Tc 运算符)、字间距(Tw 运算符)、水平缩放(Tz 运算符)来播放(pdf 格式);我认为它可以“工作”;但是为什么两个程序中的缩放/间距/...不一样?这些(默认)参数不是字体文件的一部分(这是一个真正的类型)?以及如何正确检索它们(无需手动将参数放入我的 java 代码中)?

谢谢

编辑

所以,正如你们俩所解释的那样,我正在调查不使用 pdf 基本字体,以确保 Java 和 Adob​​e Reader 使用相同的字体(ttf 文件)。但我仍然有一个问题(同样的?)。

在 PDF 输出中,我正在生成这样的字体:

31 0 obj <<
/Type /Font
/FirstChar 0
/LastChar 255
/Widths[1298 ... 646]
/Name /F7
/Encoding /WinAnsiEncoding
/Subtype /TrueType /BaseFont /Tahoma /FontDescriptor 32 0 R
>>
endobj

32 0 obj <<
/Type /FontDescriptor
/Ascent 1299
/CapHeight 1298
/Descent -269
/Flags 32
/FontBBox [0 -269 2012 1299]
/FontName /Tahoma
/ItalicAngle 0
/StemV 126
/XHeight 1298
>>
endobj

如果我正确理解了规范,所有数字(宽度、上升、下降……)都与字形单元(基于 1em?)相关,其中 1em = 1000(而 1em 是 M 字符的宽度) .

所以要从 java 生成所有这些参数,我首先尝试找到正确的 java 字体大小以使 M 字符的宽度等于 1000(因为 Java 没有在 Font 类或其他类中访问这些参数类;即使这些信息在 ttf 文件中,PDF 也需要它??)。

float size = 1f;
while (true) {
    font = font.deriveFont(size);
    fm = g2d.getFontMetrics(font);
    int em = fm.charWidth('M');
    if (em >= 1000)
        break ;
    size += 1;
}

然后我可以生成所有需要的参数。例如,对于 Widths 数组(即每个字符的宽度):

String pdfWidths = "";
for (int i = 0; i <= 255; ++i) {
    int width = fm.charWidth(i);
    pdfWidths += width + " ";
}

但是这样做,我的文本仍然与 Adob​​e Viewer 中的矩形重叠。 因此,对于 Tahoma 字体,我必须将我的 EM 限制(进入我的蛮力循环)设置为 780; Verdana 字体为 850;... 显示相似的文本(不完全相同,但可能是由于抗锯齿算法?)(见下面的屏幕截图)。所以它不是一个恒定的“限制”(理论上必须等于 1000),而是一个可变的限制……对吗? (我认为不是)如果是,如何找到这个限制?如果不是,有什么问题?

再次感谢。

编辑

只需将字体大小设置为 1000 并且无需暴力破解即可找到 EM/Line 高度大小,pdf 中的结果确实是 java。

font = font.deriveFont(1000f);
fm = g2d.getFontMetrics(font);
//Retrieve Widths attribute
_pdfWidths = "";
for (int i = _firstChar; i <= _lastChar; ++i) {
    int width = fm.charWidth(i);
    _pdfWidths += width + " ";
}

但还是有一点点不同,可能是文字绘制算法的原因(字距可能与java和adobe reader不同?)。见下图,我们可以看到,使用 Verdana,pdf 中的文本比 java 中的文本要小一些(宽度)。

【问题讨论】:

  • 本质上:您的应用程序使用 Java AWT 认为的 TimesRoman plain at 16pt 以自己的方式应用字体度量;您的 PDF 查看器使用它认为的 Times-Roman 在 16 个用户空间单位应用 PDF 规范中指定的字体度量。您所能期待的只是一些相似性(否则其中一个上下文会做出非常糟糕的选择),但根本不是同一性。
  • 顺便说一句:从 PDF 1.5 开始,对标准 14 字体的特殊处理已被弃用。ISO 32000-1 中的第 9.6.2.1 节)。因此,通过不在 PDF 中明确包含字体指标,您所做的事情已被弃用多年。
  • (基于 1em?) - 不,不是基于 em,而是:字体以一种标准大小定义字形。该标准的安排使得紧密间隔的文本行的标称高度为 1 个单位。 因此,1000 个字形空间单位是该标称行的高度。
  • Hhmmm,Widths 数组不应该独立于当前字体大小吗?不应该用 1000 * fm.charWidth(i) 填充,其中 fm 是大小为 1 的字体的度量?或者,当 AWT 使用 int 宽度时,使用 fm.charWidth(i) 其中 fm 是大小为 1000 的字体的度量?
  • 查看FontMetrics.charWidth 方法注释:注意String 的前进不一定是其字符前进的总和。 AWT 还适用字距调整等导致轻微偏差。然而,在 PDF 中,使用单个 Tj 操作,这些进步确实加起来了。

标签: java pdf text fonts


【解决方案1】:

这个答案本质上是对我的 cmets 的总结。

第一次尝试使用字体“Time New Roman”(实际上是 Times-Roman这是 PDF 基础字体之一(不是计算所有字体度量并将其输出到 pdf 中) 用于 PDF 和“TimesRoman”用于 Java AWT,导致

本质上:您的应用程序使用 Java AWT 认为的 TimesRoman plain at 16pt 以自己的方式应用字体度量;您的 PDF 查看器使用它认为的 Times-Roman 在 16 个用户空间单位应用 PDF 规范中指定的字体度量。您所能期待的只是一些相似性(否则其中一个上下文会做出非常糟糕的选择),但根本不是同一性。

David 在他的回答中实际上在第 1 项(不同的字体)和第 3 项(字距调整和替换的不同应用)中更详细地解释了这一点。

此外,

顺便说一句:从 PDF 1.5 开始,对标准 14 字体的特殊处理已被弃用。 (ISO 32000-1 中的第 9.6.2.1 节)。因此,通过在 PDF 中不明确包含字体指标,您所做的事情已被弃用多年。

下一次尝试涉及不使用 pdf 基本字体以确保 Java 和 Adob​​e Reader 使用相同的字体(ttf 文件),需要计算嵌入 PDF 中的字符宽度。在这种情况下,假设 所有数字(宽度、上升、下降,...)都与字形单元(基于 1em?)相关,其中 1em = 1000(而 1em 是 M 字符的宽度) .因此尝试找到正确的java字体大小以使M字符的宽度等于1000,然后生成所有需要的参数那个字体。

不,不是基于 em,而是:字体以一种标准大小定义字形。该标准的安排使得紧密间隔的文本行的标称高度为 1 个单位。 因此,1000 个字形空间单位是该标称行的高度。

这导致了一个问题“标称线”到底是什么。幸运的是,反过来处理这个问题更容易:根据定义,大小为 1 的字体是“标称线”的高度为 1。因此,

Widths 数组不应该用1000 * fm.charWidth(i) 填充,其中fm 是1 号字体的度量吗?或者,当 AWT 使用 int 宽度时,fm.charWidth(i) 其中fm 是大小为 1000 的字体的度量?

考虑到这一点,只需将字体大小设置为 1000 并且无需暴力破解即可找到 EM/Line 高度大小,pdf 中的结果确实是 java.util.但是还是有一点点不同,可能是文本绘制算法的原因(字距可能与java和adobe reader不同?)。见下图,我们可以看到,使用 Verdana,pdf 中的文本比 java 中的文本要小一些(宽度)。

查看FontMetrics.charWidth 方法注释:请注意,字符串的前进不一定是其字符前进的总和。 AWT 还应用字距调整等导致轻微偏差。然而,在 PDF 中,使用单个 Tj 操作,这些进步确实加起来了。

如果您想在 PDF 中使用字距调整,您必须明确写出与标准宽度的偏差。这里的 TJ 运算符非常方便,它允许将字符串和偏移量的混合数组作为参数。

如果您想用例如替换某些字符连字,你也必须自己做

【讨论】:

  • 我明白你在说什么,但请你解释一下如何将生成的 Widths 数组应用到 iText baseFont?
【解决方案2】:

对此有多种可能的解释,所有这些都说明使用 PDF 中定义的标准 14 种字体可能是合法的,但通常不是明智的做法。它介绍了您遇到的歧义。 PDF 通常是为了避免这样的歧义而设计的;从这个意义上说,允许非嵌入和未正确指定的字体是一个坏主意。

  • 如果您仔细查看文本中的字符形状,我可能会冒昧地说您实际上是在查看不同的字体。相似,却又不同。以“i”为例,在一种情况下,“i”上的点要高多少。造成这种情况的原因可能是 Adob​​e Reader 有它自己的字体集并且不使用系统字体(比如 Java 可能使用的字体)。想一想,Adobe Reader 怎么能始终正确显示这些字体,而不管它运行在什么系统上?

  • 实际上可能更糟。如果我搜索我的 Adob​​e Reader 安装,我没有找到 Times 字体(不是你说的“Times New Roman”,而是一种 不同 字体)。因此,Adobe Reader 很可能使用不同的字体来模仿 Times(以及其他一些 14 种基本字体)。我不能 100% 确定这一点,但我不认为 Acrobat 和 Reader 曾经使用 MultiMaster 字体来模拟非嵌入式字体。

  • 1234563表示组合“ffl”而不是三个单独的字符)。 PDF 能够使用字距调整和那些特殊字形,但您必须做一些工作以确保它们被使用......

如果您想绝对确定您的 PDF 看起来与您的 Java 渲染完全相同,请找出 Java 中的字符位置。然后以这样的方式编写您的 PDF 文件,使每个字符都位于完全相同的位置...

【讨论】:

  • 关于第2项:即使PDF查看器使用替换字体,也需要使用规范中指定的原始字体度量(尤其包括字符宽度)。因此,这种替换字体不会使事情变得更糟,尤其是就字符宽度而言。 @Alexxx 尽管如此,显然这些指标并不需要与 Java AWT 使用的 TimesRoman 字体的指标相匹配,参见。大卫的第一件物品。
  • 你说得对——替换字体应该使用相同的字体宽度,这样它就不会使这个特殊问题变得更糟。它可能会导致不同的字符形状(我猜字符间距不正确,因为字形可能有不同的轮廓)。
猜你喜欢
  • 2015-03-08
  • 1970-01-01
  • 2011-10-28
  • 2014-09-02
  • 1970-01-01
  • 2018-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多