【发布时间】: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 的 Adobe Acrobat Reader 的屏幕截图并截取显示缓冲图像的程序的屏幕截图来构建的;然后复制/粘贴 pdf 屏幕截图的下面部分将我的程序屏幕截图的矩形转换为 MSPaint。要具有相同的矩形大小,我必须在 Adobe 中以原始大小的 65.5% 显示 pdf):
所以我们可以看到java en adobe中用来显示文字的字体是一样的。但文字在 Adobe 中似乎有点大。事实上,如果我叠加两个单词(一个来自 java 的,一个来自 adobe 的顶部),似乎单词间距是相同的,字母间距也是一样,但有些字母有 1 个像素的宽度差异。
为什么? 我能做些什么来解决这个问题?我尝试使用字符间距(Tc 运算符)、字间距(Tw 运算符)、水平缩放(Tz 运算符)来播放(pdf 格式);我认为它可以“工作”;但是为什么两个程序中的缩放/间距/...不一样?这些(默认)参数不是字体文件的一部分(这是一个真正的类型)?以及如何正确检索它们(无需手动将参数放入我的 java 代码中)?
谢谢
编辑
所以,正如你们俩所解释的那样,我正在调查不使用 pdf 基本字体,以确保 Java 和 Adobe 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 + " ";
}
但是这样做,我的文本仍然与 Adobe 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 认为的
TimesRomanplain 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 操作,这些进步确实加起来了。