2

我正在用 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 与 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),而是一个可变的限制……对吗?(我认为不是)如果是,如何找到这个限制?如果不是,有什么问题?

Java 与 PDF 文本输出 - EM

再次感谢。

编辑

只需将字体大小设置为 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 与 PDF 文本输出 - 1000

4

2 回答 2

5

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

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

尝试使用标准 14 Times-Roman

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

大卫实际上在他的回答中更详细地解释了第 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数组不应该填充大小为 1 的字体度量在1000 * fm.charWidth(i)哪里?fm或者,当 AWT 使用 int 宽度时,大小为 1000 的字体的度量在fm.charWidth(i)哪里?fm

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

尝试使用嵌入字体和正确的字符宽度

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

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

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

于 2013-08-05T12:52:51.003 回答
1

对此有许多可能的解释,所有这些都有助于使用 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 字体来模拟非嵌入式字体。

  • 此外,您在 PDF 中呈现文本的方式不使用字符间字距调整,而 Java 很可能足够聪明,可以应用一些额外的字距调整或使用字符替换(例如使用一个字形来表示组合“ffl”而不是三个单独的字符)。PDF 能够使用字距调整和那些特殊字形,但您必须完成工作以确保它们被使用......

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

于 2013-08-01T17:46:08.553 回答