PStill: 提高pdf文档中字体的兼容性

Update:  更好的解决方案:

PStill仍然不是那么好安装,而且在Windows/Mac上不能免费用。
在Linux上,如果问题仅仅是替换字体,更容易的方法是:

pdftops filename.pdf
ps2pdf14 -dPDFSETTINGS=/prepress -dEmbedAllFonts=true \
    filename.ps filename.new.pdf

============原文分割线=============

曾经使用Word写论文并提交到IEEE会议的人应该都深有体会,保证pdf嵌入所有的非常用字体是一个令人挠头的技术问题。这一次,我觉得我找到了一个彻底的解决方案。

首先,何为字体嵌入?pdf文档可使用任意字体,但并非每一种字体在所有的计算机上都有安装。如果一个pdf文件使用了一种特殊字体,在一台没有安装此字体的计算机上显示该文档可能会出现两种后果:(1)使用该字体的文字不能直接显示;(2)使用该字体的文字为另一种常用字体替换。无论哪一种都不是我们想看到的结果。于是,pdf就允许将字体嵌入到文档中,这样在没有安装该字体的计算机上也可正常显示。嵌入的方式有两种:字体嵌入(embed)和子集嵌入(subset)。字体嵌入很好理解,就是把整个字体都嵌入到pdf中。但可能字体中的某些文字并没有在文档中出现,实为冗余。子集嵌入这只将文档中出现的使用该字体的字符嵌入,不会使用更多的存储空间。

看起来这个主意不错,不会有什么问题了,可是实际环境中即使软件自动嵌入字体,也并非所有的字体都被嵌入。往往软件只会嵌入非常用字体,默认常用字体在所有的计算机上都有安装。对于只在Window上查看的pdf文档来说这不是个大问题,Window默认安装常用字体。但问题是,不同的操作系统对常用字体有不同定义,比如windows上使用Arial,但Linux则默认Helvetica。此外,字体的格式也不一样。常用的有两种:Type1和TrueType。其中Linux多使用Type1而Windows的ttf字体都是TrueType。

于是问题出现了。以我的文档为例子,我习惯使用office visio做图,用Latex写文章。visio的图片先输出为内嵌字体的pdf文件,然后使用pdftops和ps2eps命令在Linux中转化为eps文件方便Latex使用。以这样的方法生成的pdf,图片中的所有文字有自己的字体并能被拷贝。但由于源pdf由window生成,比如我用的consolas (一种等宽Arial字体)被嵌入,但嵌入为TrueType字体。在较新的Linux系统中还能正常显示,但在较老的,比如Unix系统上,图片中使用consolas的文字成了乱码。

在尝试过多种方法之后,我现在基本认为Latex的命令和ghostscript命令基本对eps中的字体没有控制能力,不能将其替换成其他字体。我需要借助于其他的工具,现在我找到了PStill。

PStill不免费,但是个人非商业用会可在Linux上免费使用。网页地址为http://www.wizards.de/~frank/pstill.html

在Linux上安装后,将所有的pfa和pfb文件拷贝到pstill中的PSFonts文件夹,运行pstill –v -I,pstill即可识别系统中的所有Type1文件。在此之后,我们即可使用PStill将一个pdf中所有没有嵌套或者嵌套为非Type1的字体,替换成对等的内嵌Type1字体。

比如,我有一个文件,使用pdffonts查看字体如下:

name                                 type              emb sub uni object ID
------------------------------------ ----------------- --- --- --- ---------
ZDTJQW+NimbusRomNo9L-Medi            Type 1C           yes yes no       4  0
PYGHTE+NimbusRomNo9L-Regu            Type 1C           yes yes no       5  0
AINPGQ+NimbusRomNo9L-ReguItal        Type 1C           yes yes no       6  0
ZSTGUP+CMMI10                        Type 1C           yes yes no      24  0
TTMMUA+CMR7                          Type 1C           yes yes no      25  0
GWDCFW+CMMI7                         Type 1C           yes yes no      26  0
YXUGEN+CMSY10                        Type 1C           yes yes no      27  0
KHWZWC+CMR10                         Type 1C           yes yes no      28  0
KTICPW+CONSOLAS                      TrueType          yes yes no      17  0
MSGNYE+ARIAL#20UNICODE#20MS_10       TrueType          yes yes no      21  0
MUCBHF+CONSOLAS                      TrueType          yes yes no      35  0
DOPWBF+CONSOLAS                      TrueType          yes yes no      47  0
HXVFHU+CONSOLAS                      TrueType          yes yes no      59  0
OWJARN+CONSOLAS                      TrueType          yes yes no      68  0
IUAHFU+CONSOLAS                      TrueType          yes yes no      77  0
Helvetica                            Type 1            no  no  no      87  0
Symbol                               Type 1            no  no  no      88  0
Helvetica                            Type 1            no  no  no      95  0
Helvetica                            Type 1            no  no  no     102  0
Helvetica                            Type 1            no  no  no     112  0

可见Helvetica和Symbol没有嵌套,consolas和arial嵌套为TrueType。使用PStill替换字体:

pstill –M defaultall –o file.ps file.pdf      
ps2pdf file.ps file_new.pdf

然后查看file_new.pdf的字体:

name                                 type              emb sub uni object ID
------------------------------------ ----------------- --- --- --- ---------
QIAMBY+NimbusRomNo9L-Medi            Type 1C           yes yes no       8  0
IKPUDX+NimbusRomNo9L-Regu            Type 1C           yes yes no      10  0
WCYKGJ+NimbusRomNo9L-ReguItal        Type 1C           yes yes no      12  0
YSGMKE+CMSY10                        Type 1C           yes yes no      29  0
CAPFWR+CMR10                         Type 1C           yes yes no      31  0
KTICPW+CONSOLAS                      Type 1C           yes yes no      19  0
MSGNYE+ARIAL#20UNICODE#20MS_10       Type 1C           yes yes no      21  0
MUCBHF+CONSOLAS                      Type 1C           yes yes no      33  0
TXLMQL+CMMI10                        Type 1C           yes yes no      23  0
UXAXYP+CMR7                          Type 1C           yes yes no      25  0
YWDNBN+CMMI7                         Type 1C           yes yes no      27  0
DOPWBF+CONSOLAS                      Type 1C           yes yes no      40  0
OWJARN+CONSOLAS                      Type 1C           yes yes no      49  0
IUAHFU+CONSOLAS                      Type 1C           yes yes no      51  0
JWBBOE+CONSOLAS                      Type 1C           yes yes no      47  0
OSPTOS+NimbusSanL-Regu               Type 1C           yes yes no      58  0

Helvetica被替换成Nimbus,所有非Type1字体被重新编译为Type1字体。这样,新的pdf文件可以在所有的类Unix系统上正常显示。Windows有较好的兼容能力,能直接显示Type1字体,还未发现问题。

关于字体的替换,PStill安装目录下的fontsub.table文件提供了具体的配置。

另外附上将系统所有字体文件软连接到PSFonts目录的脚本:

#!/bin/bash

rm pfb.list
rm pfa.list
locate *.pfb | grep fonts > pfb.list
locate *.pfa | grep fonts > pfa.list
for ff in `cat pfb.list`
do
  ln -s $ff
done
for ff in `cat pfa.list`
do
  ln -s $ff
done

也可以将以下的脚本放到全局PATH所指向的目录里面,就可以在任意地方使用pstill转化文件

#!/bin/bash

# Wei Song 
# 15/05/2013

# set PSTILL_PATH to your own install directory
PSTILL_PATH=$HOME/tool/pstill

src_file=""
tar_file=""

while test $# -gt 0; do
    case "$1" in
        -h|--help)
            echo "pstill: convert and embedded fonts in a pdf"
            echo " "
            echo "pstill [options] pdf_file "
            echo " "
            echo "options:"
            echo "-h, --help                show brief help"
            echo "-o output_file            specify the output file"
            exit 0
            ;;
        -o)
            shift
            if test $# -gt 0; then
                tar_file=$1
            else
                echo "ERROR: no output files specified!"
                exit 1
            fi
            shift
            ;;
        *)
            src_file=$1
            shift
            ;;
        esac
done

src_file=`echo $src_file | sed -e 's/\.pdf$//g'`

if [ -z $tar_file ]; then
    tar_file=$src_file.new.pdf
fi


$PSTILL_PATH/pstill -M defaultall -o /tmp/tmp$$.ps $PWD/$src_file.pdf > /dev/null 2>&1

if [ -f /tmp/tmp$$.ps ]; then
    ps2pdf /tmp/tmp$$.ps $tar_file > /dev/null 2>&1
    if [ -f $tar_file ]; then
        echo "write $tar_file"
    else
        echo "fail to run: ps2pdf /tmp/tmp$$.ps $tar_file"
        exit 1
    fi
else
    echo "fail to run: $PSTILL_PATH/pstill -M defaultall -o /tmp/tmp$$.ps $PWD/$src_file.pdf"
    exit 1
fi