目录 |
设备独立文件 |
设备独立(Device-independent)文件是TeX及其衍生系统的标准输出之一, 由于其扩展名为“.dvi”,所以多数时候我们都称其为dvi文件。它由一系列 命令组成,每条命令由一个或多个字节组成。这些命令精确地表达了TeX及其 衍生系统的排版结果,结构经凑,内容简练,同样内容的dvi文件要比pdf文件 小得多,因而非常适合传输。
一个dvi处理程序就像一个被一组指令驱动着的设备,一条一条地执行这些指令, 不同之处仅仅在于程序想要做什么,所以这些程序的主要框架都非常相似。我们在此 提供一个这样的程序框架,以帮助那些有兴趣处理dvi文件的读者。
设备独立文件的结构非常简单,以导言开始,然后是一系列页,最后是后记。 每一部分由一个或多个命令组成。每个命令的第一个字节是操作码。一些命令还带有参数, 参数本身又由若干字节组成。
我们假定你正在编写一个文档处理程序,DVI文件是其处理的格式之一,所以我们以C++语法 定义一个DVIDocument类,dvi文件成员File是一个假象的类
class DVIDocument : public Doccument
{
public:
    DVIDocument();
    int doPage(int pn);
private:
    int dobop();
    int dodown1();
    int dodown2();
    int dodown3();
    int dodown4();
    int dofont1();
    int dofont2();
    int dofont3();
    int dofont4();
    int dofontdef1();
    int dofontdef2();
    int dofontdef3();
    int dofontdef4();
    int dopop();
    int dopush();
    int doput1();
    int doput2();
    int doput3();
    int doput4();
    int doputrule();
    int doright1();
    int doright2();
    int doright3();
    int doright4();
    int doset1();
    int doset2();
    int doset3();
    int doset4();
    int dosetrule();
    int dow0();
    int dow1();
    int dow2();
    int dow3();
    int dow4();
    int dox0();
    int dox1();
    int dox2();
    int dox3();
    int dox4();
    int doxxx1();
    int doxxx2();
    int doxxx3();
    int doxxx4();
    int doy0();
    int doy1();
    int doy2();
    int doy3();
    int doy4();
    int doz0();
    int doz1();
    int doz2();
    int doz3();
    int doz4();
    int dofont(long id);
    int dostring(uchar * buf, int len);
    int drawchar(double x, double y, long c, Font * fnt);
    int drawrule(double x, double y, double wd, double ht);
    int drawstring(double x, double y, long * buf, int len, Font * fnt);
    DVIFont * finddvifont(long tex_id);
    int readdviinfo();
    int readdvifont();
    int readpost();
    char readsignedbyte();
    short readsignedpair();
    long readsignedquad();
    long readsignedtriple();
    uchar readunsignedbyte();
    ushort readunsignedpair();
    ulong readunsignedquad();
    ulong readunsignedtriple();
    int getc();
    int read(char * buf, int len);
    void seek(ulong pos);
    ulong size();
private:
    File *file;
    int dpi;
    double totalmag,dvi2dev;
    ulong num, den, mag, mediawidth,mediaheight;
    ulong * postloc;
    long numpages;
    DVIFont ** dvifonts;
    int numfonts;
    ulong *pageloc;
    DVIState * stack, state;
    long depth,maxdepth;
    long f;
};
处理一页,我们假定传入的页码是从1开始的。将文件定位到页开始的地方,也就是一个DVI_BOP指令 所在的位置,然后逐条读取并执行指令,直到遇到页结束指令DVI_EOP,执行完这条指令后一页处理结束
int DVIDocument::doPage(int pn)
{
    if (pn < 1 || pn > numpages>)
        return -1;
    seek(pageloc[pn - 1]);
    uchar opcode = 0;
    int len = 0;
    uchar buf[1025];
    for(;;)
    {
        len = 0;
        while ((opcode=readunsignedbyte()) < DVI_SET1 && len < 1024)
            buf[len++] = opcode;
        if (len > 0)
            dostring(buf, len);
        if (len == 1024)
            continue;
        if (opcode >= DVI_FNTNUM0 && opcode < DVI_FNT1)
        {
            dofont(opcode);
            continue;
        }
        switch (opcode)
        {
            case DVI_BOP:
                dobop();
                break;
            case DVI_EOP:
                doeop();
                return 0;
                break;
            case DVI_FNT1:
                dofont1();
                break;
            case DVI_FNT2:
                dofont2();
                break;
            case DVI_FNT3:
                dofont3();
                break;
            case DVI_FNT4:
                dofont4();
                break;
            case DVI_SET1:
                doset1();
                break;
            case DVI_SET2:
                doset2();
                break;
            case DVI_SET3:
                doset3();
                break;
            case DVI_SET4:
                doset4();
                break;
            case DVI_PUT1:
                doput1();
                break;
            case DVI_PUT2:
                doput2();
                break;
            case DVI_PUT3:
                doput3();
                break;
            case DVI_PUT4:
                doput4();
                break;
            case DVI_SETRULE:
                dosetrule();
                break;
            case DVI_PUTRULE:
                doputrule();
                break;
            case DVI_XXX1:
                doxxx1();
                break;
            case DVI_XXX2:
                doxxx2();
                break;
            case DVI_XXX3:
                doxxx3();
                break;
            case DVI_XXX4:
                doxxx4();
                break;
            case DVI_RIGHT1:
                doright1();
                break;
            case DVI_RIGHT2:
                doright2();
                break;
            case DVI_RIGHT3:
                doright3();
                break;
            case DVI_RIGHT4:
                doright4();
                break;
            case DVI_W0:
                dow0();
                break;
            case DVI_W1:
                dow1();
                break;
            case DVI_W2:
                dow2();
                break;
            case DVI_W3:
                dow3();
                break;
            case DVI_W4:
                dow4();
                break;
            case DVI_X0:
                dox0();
                break;
            case DVI_X1:
                dox1();
                break;
            case DVI_X2:
                dox2();
                break;
            case DVI_X3:
                dox3();
                break;
            case DVI_X4:
                dox4();
                break;
            case DVI_DOWN1:
                dodown1();
                break;
            case DVI_DOWN2:
                dodown2();
                break;
            case DVI_DOWN3:
                dodown3();
                break;
            case DVI_DOWN4:
                dodown4();
                break;
            case DVI_Y0:
                doy0();
                break;
            case DVI_Y1:
                doy1();
                break;
            case DVI_Y2:
                doy2();
                break;
            case DVI_Y3:
                doy3();
                break;
            case DVI_Y4:
                doy4();
                break;
            case DVI_Z0:
                doz0();
                break;
            case DVI_Z1:
                doz1();
                break;
            case DVI_Z2:
                doz2();
                break;
            case DVI_Z3:
                doz3();
                break;
            case DVI_Z4:
                doz4();
                break;
            case DVI_NOP:
                break;
            case DVI_PUSH:
                dopush();
                break;
            case DVI_POP:
                dopop();
                break;
            case DVI_FNTDEF1:
                dofontdef1();
                break;
            case DVI_FNTDEF2:
                dofontdef2();
                break;
            case DVI_FNTDEF3:
                dofontdef3();
                break;
            case DVI_FNTDEF4:
                dofontdef4();
                break;
            default:
                return -1;
                break;
        }
    }
}
用给定的字体绘制一个字符,字符引用点在(x,y),字符码是c,字体是fnt
int DVIDocument::drawchar(double x, double y, long c, Font * fnt)
{
    ...;
    return 0;
}
绘制一个用黑色填充的矩形,矩形的左下角是(x,y),宽是wd,高是ht
int DVIDocument::drawrule(double x, double y, double wd, double ht)
{
    ...;
    return 0;
}
用给定的字体绘制一个字符串,第一个字符的引用点在(x,y),字符串buf的长度是len,字体为fnt
int DVIDocument::drawstring(double x, double y, long * buf, int len, Font * fnt)
{
    ...;
    return 0;
}
从文件中读取一个有符号字节
char DVIDocument::readsignedbyte()
{
    int b = readunsignedbyte();
    if (b >= 0x80)
        b = b - 0x100;
    return (char)b;
}
从文件中读取两个字节,拼凑为一个有符号整数
short DVIDocument::readsignedpair()
{
    int pair = 0;
    for (int i = 0; i < 2; i++)
        pair = pair*0x100u + readunsignedbyte();
    if (pair >= 0x8000)
        pair -= 0x10000l;
    return (short)pair;
}
从文件中读取四个字节,拼凑为一个有符号整数
long DVIDocument::readsignedquad()
{
    int b = readunsignedbyte();
    long quad = b;
    if (quad >= 0x80)
        quad = b - 0x100;
    for (int i = 0; i < 3; i++)
        quad = quad*0x100u + readunsignedbyte();
    return quad;
}
从文件中读取三个字节,拼凑为一个有符号整数
long DVIDocument::readsignedtriple()
{
    long triple = 0;
    for (int i = 0; i < 3; i++)
    {
       triple = triple * 0x100 + readunsignedbyte();
    if (triple >= 0x800000l)
        triple -= 0x1000000l;
    return triple;
}
从文件中读取一个无符号字节
uchar DVIDocument::readunsignedbyte()
{
    int c = getc();
    return (uchar)c;
}
从文件中读取两个字节,拼凑为一个无符号整数
ushort DVIDocument::readunsignedpair()
{
    int pair = 0;
    for (int i = 0; i < 2; i++)
        pair = pair*0x100u + readunsignedbyte();
    return (ushort)pair;
}
从文件中读取四个字节,拼凑为一个无符号整数
ulong DVIDocument::readunsignedquad()
{
    ulong quad = 0;
    for (int i = 0; i < 4; i++)
        quad = quad*0x100u + readunsignedbyte();
    return quad;
}
从文件中读取三个字节,拼凑为一个无符号整数
ulong DVIDocument::readunsignedtriple()
{
    ulong triple = 0;
    for (int i = 0; i < 3; i++)
        triple = triple*0x100u + readunsignedbyte();
    return triple;
}
从dvi文件中读取一个字节
int DVIDocument::getc()
{
}
从dvi文件中读取指定长度的字节
void DVIDocument::read(char * buf, int len)
{
}
你必须能够定位到指定的dvi文件位置,所以文件必须是随机存取文件
ulong DVIDocument::seek(ulong pos)
{
}
你需要实现获取dvi文件大小的操作
void DVIDocument::size()
{
}
导言的格式是
247 i[1] num[4] den [4] mag[4] k[1] x[k]i一个字节长,是dvi文件格式的标志,一般是“2”,某些编译器如xetex也输出“3”和“5”。 num和den各占四个字节,是两个正整数,dvi文件中所有的长度值都乘以num/den,得到是 以10的-7次方米为单位的长度。mag也占四个字节,是一个整数,表示希望的放大系数的 1000倍。k开始部分是注释,多数编译器把自己的名字放在注释中。k占一个字节,表示注释 (x开始的字节数组)的长度,所以注释不能超过255个字节。
#define DVI_PRE 247
#define DVI_ID_BYTE 2
int DVIDocument::readdviinfo()
{
    seek(0);
    uchar pre = readunsignedbyte();
    if (pre != DVI_PRE)
        return -1;
    int dvi_id_byte = getc();
    if (dvi_id_byte != DVI_ID_BYTE)
        return -1;
    num = readunsignedquad();
    den = readunsignedquad();
    mag = readunsignedquad();
    mediaheight = readunsignedquad();
    mediawidth = readunsignedquad();
    stackdepth = readunsignedpair();
    int len = readunsignedbyte();
    char buf[256];
    read(buf,len);
    totalmag = (double)mag/1000.0;
    dvi2dev = (double)num/(double)den;
    dvi2dev *= ((double)dpi/254000.0);
    return 0;
}
后记的格式是
248 p[4] num[4] den[4] mag [4] l[4] u[4] s[2] t[2]p占四个字节,是一个整数,是最后一页的开始位置。num、den、mag与前言一样。 l和u各占四个字节,分别给出了最大的页高度和最大的页宽度。s是最大的栈深度, 也就是压栈和出栈命令的嵌套深度。t命令占两个字节,是最大页数。
字体定义
...
字体定义
249 249 q[4] i[1] 223's[4]
q占四个字节,是一个指针,指向后记开始的地方。i占一个字节,表示其后的填充 字节的个数,每一个填充字节都是“223”。
#define DVI_POST 248
#define DVI_POSTPOST 249
#define DVI_PADDING 223
int DVIDocument::readpost()
{
    ulong cur_pos = size();
    if (cur_pos == 0)
        return -1;
    ulong file_size = cur_pos;
    uchar c = 0;
    do
    {
        cur_pos--;
        seek(cur_pos);
    } while ((c=getc()) == DVI_PADDING && cur_pos > 0);
    if ((file_size - cur_pos) < 4 || cur_pos == 0)
        return -1;
    cur_pos -= 5;
    seek(cur_pos);
    if ((c=readunsignedbyte()) != DVI_POSTPOST)
        return -1;
    cur_pos = readsignedquad();
    postloc = cur_pos;
    seek(postloc);
    if ((c=readunsignedbyte()) != DVI_POST)
        return -1;
    seek(postloc + 25);
    maxdepth = readunsignedpair();
    depth = 0;
    stack = new DVIState[maxdepth];
    numpages = readunsignedpair();
    pageloc = (ulong*)malloc(numpages * sizeof(ulong));
    seek(postloc + 1);
    pageloc[numpages - 1] = readunsignedquad();
    if (pageloc[numpages - 1] + 41 > file_size)
        return -1;
    for (int i = numpages - 2; i >= 0; i--)
    {
        seek(pageloc[i + 1] + 41);
        pageloc[i] = readunsignedquad();
    }
    return readdvifont();
}
“字体定义”由一个或多个字体定义命令组成,四种字体定义的格式分别是
243 k[1] c[4] s[4] d[4] a[1] l[1] n[a + l]k是一个整数,是字体的标识,在页中用它引用字体,在四种格式中所占字节数不一样。 c占四个字节,是字体的校验和,在严格要求的场合,与相应的TFM文件中对应的四个 字节应该一致。s占四个字节,它是一个缩放因子,用于计算字符在字体中的宽度。 d占四个字节,表示字体的设计大小。a占一个字节,是字体文件路径的长度,l占一个 字节,是字体名的长度,n是一个字节数组,长度是a+l。
244 k[2] c[4] s[4] d[4] a[1] l[1] n[a + l]
245 k[3] c[4] s[4] d[4] a[1] l[1] n[a + l]
246 k[4] c[4] s[4] d[4] a[1] l[1] n[a + l]
DVIFont类要最终对应到实际的字体,所以它应该包含一个指向实际字体的成员 (Font是一个假象的类,表示实际字体)。tex字体和实际字体的对应关系一般是 可配置的,所以你可能需要维护一个配置文件。
#define DVI_FNTDEF1 243
#define DVI_FNTDEF2 244
#define DVI_FNTDEF3 245
#define DVI_FNTDEF4 246
class DVIFont
{
public:
    DVIFont();
    long getcharwidth(char * buf, int len);
    long getstringwidth(char * buf, int len);
    long getFontChar(long c);
    long * getFontString(char * buf, int & len);
    Font * getRealFont();
public:
    long texid;
    ulong pointsize,designsize;
    char * texname;
    Font * font;
};
int DVIDocument::readdvifont()
{
    long tex_id = 0;
    int c = 0;
    numfonts = 0;
    dvifonts = (DVIFont**)malloc(256 * sizeof(DVIFont*));
    seek(postloc + 29);
    while ((c=readunsignedbyte()) != DVI_POSTPOST)
    {
        switch (c)
        {
            case DVI_FNTDEF1:
                tex_id = readunsignedbyte();
                break;
            case DVI_FNTDEF2:
                tex_id = readunsignedpair();
                break;
            case DVI_FNTDEF3:
                tex_id = readunsignedtriple();
                break;
            case DVI_FNTDEF4:
                tex_id = readunsignedquad();
                break;
            default:
                return -1;
                break;
        }
        readunsignedquad();
        DVIFont * fnt = new DVIFont;
        fnt->texid = tex_id;
        fnt->pointsize = readunsignedquad();
        fnt->designsize = readunsignedquad();
        int dir_len = readunsignedbyte();
        char buf[256];
        read(buf, dir_len);
        int name_len = readunsignedbyte();
        fnt->texname = new char[name_len + 1];
        read(fnt->texname, name_len);
        fnt->texname[name_len] = 0;
        dvifonts[numfonts++] = fnt;
    }
    return 0;
}
dvi格式的设计非常紧凑,同时又非常容易解释。紧凑是通过所谓隐含而非明显的表达 实现的。当程序处理一页时,它保持对下面几个量的维护:
class DVIState
{
public:
    DVIState();
public:
    long h,v,w,x,y,z;
}
页开始的格式是
139 c0[4] c1[4] ... c9[4] p[4]c0到c9各占四个字节,是编译器产生的页标识。p占四个字节,是一个指针,指向前一页,首页的这个 值是-1。
在页开始时最重要的就是将(h,v,w,x,y,z)置为(0,0,0,0,0,0)。之后可依据实际情况,建立必须的资源, 初始化你的参数
#define DVI_BOP 139
int DVIDocument::dobop()
{
    for (int i = 0; i < 10; i++)
        readsignedquad();
    state.h=state.v=state.w=state.x=state.y=state.z=0;
    ...;
}
页的结束命令就一个字节“140”,一些不需要的资源可以在这里释放
#define DVI_EOP 140
int DVIDocument::doeop()
{
    ...;
}
当前字体是一个整数,就是字体定义的第一个参数。你可以在此时检查DVI字体是否已经 对应到实际的字体,如果没有,可以通过配置文件找到实际字体的文件名,找到字体文件, 从文件产生实际字体
Font * DVIFont::getRealFont()
{
    ...;
}
DVIFont * DVIDocument::finddvifont(long tex_id)
{
    DVIFont * fnt = 0;
    for (int i = 0; i < numfonts;i++)
    {
        fnt = dvifonts[i];
        if (fnt->texid == tex_id)
            break;
    }
    return fnt;
}
设置字体的命令有两组。第一组只有操作码,操作数是隐含的
#define DVI_FNTNUM0 171
int DVIDocument::dofont(long tex_id)
{
    if (tex_id < 0 || tex_id > numfonts)
        return -1;
    f = tex_id;
    return 0;
}
设置字体的第二组命令是
235 k[1]k分别占1、2、3、4个字节
236 k[2]
237 k[3]
238 k[4]
#define DVI_FNT1 235
#define DVI_FNT2 236
#define DVI_FNT3 237
#define DVI_FNT4 238
int DVIDocument::dofont1()
{
    long tex_id = readunsignedbyte();
    return dofont(tex_id);
}
int DVIDocument::dofont2()
{
    long tex_id = readunsignedpair();
    return dofont(tex_id);
}
int DVIDocument::dofont3()
{
    long tex_id = readunsignedtriple();
    return dofont(tex_id);
}
int DVIDocument::dofont4()
{
    long tex_id = readunsignedquad();
    return dofont(tex_id);
}
首先要明确的是dvi的坐标系统,坐标原点在左上角,x轴向右,y轴向下,因此要注意把dvi坐标 转换为设备坐标,在示例中我们假定设备坐标的原点在左下角,x轴向右,y轴向上。
其次要知道,dvi文件中的字符不一定是实际字体的字符。如果配置文件指出使用了子字体,就要 用子字体文件把dvi文件中的字符转换为实际字体的字符。更复杂的情况是虚拟字体的使用。
放置字符的命令分为三类,第一类只有操作码,操作数是隐含的,就是操作码本身
#define DVI_SETCHAR0 0
long DVIFont::getcharwidth(long c)
{
    ...;
    ...;
    ...;
    return wd;
}
long DVIFont::getstringwidth(char * buf, int len)
{
    ...;
    ...;
    ...;
    return wd;
}
long DVIFont::getFontChar(long c)
{
    ...;
}
long * DVIFont::getFontString(char * buf, int & len)
{
    假定以new分配数组;
}
int DVIDocument::dostring(char * buf, int len)
{
    DVIFont * fnt = finddvifont(f);
    wd = fnt->getstringwidth(buf, len);
    long * str = fnt->getFontString(buf, len);
    int code = drawstring(dvi2dev * state.h, -dvi2dev * state.v, str, len, fnt);
    state.h += wd;
    delete [] str;
    return code;
}
第二类放置字符的命令有四个
128 c[1]c分别占1、2、3、4个字节。其含义是用当前字体f,把字体中的字符c放在(h,v),并且 将h加上c的宽度
129 c[2]
130 c[3]
131 c[4]
#define DVI_SET1 128
#define DVI_SET2 129
#define DVI_SET3 130
#define DVI_SET4 131
int DVIDocument::dosetchar(long c)
{
    DVIFont * fnt = finddvifont(f);
    wd = fnt->getcharwidth(c);
    long ch = fnt->getFontChar(c);
    int code = drawchar(dvi2dev * state.h, -dvi2dev * state.v, ch, fnt);
    state.h += wd;
    return code;
}
int DVIDocument::doset1()
{
    long c = readunsignedbyte();
    return dosetchar(c);
}
int DVIDocument::doset2()
{
    long c = readunsignedpair();
    return dosetchar(c);
}
int DVIDocument::doset3()
{
    long c = readunsignedtriple();
    return dosetchar(c);
}
int DVIDocument::doset4()
{
    long c = readunsignedquad();
    return dosetchar(c);
}
第三类放置字符命令有四个
133 c[1]c分别占1、2、3、4个字节。其含义是用当前字体f,把字体中的字符c放在(h,v)。
134 c[2]
135 c[3]
136 c[4]
#define DVI_PUT1 133
#define DVI_PUT2 134
#define DVI_PUT3 135
#define DVI_PUT4 136
int DVIDocument::doputchar(long c)
{
    DVIFont * fnt = finddvifont(f);
    long ch = fnt->getFontChar(c);
    return drawchar(dvi2dev * state.h, -dvi2dev * state.v, ch, fnt);
}
int DVIDocument::doput1()
{
    long c = readunsignedbyte();
    return doputchar(c);
}
int DVIDocument::doput2()
{
    long c = readunsignedpair();
    return doputchar(c);
}
int DVIDocument::doput3()
{
    long c = readunsignedtriple();
    return doputchar(c);
}
int DVIDocument::doput4()
{
    long c = readunsignedquad();
    return doputchar(c);
}
绘制矩形的命令有两条,第一条是
132 a[4] b[4]a占四个字节,表示矩形的高,b占四个字节,表示矩形的宽。这条命令的含义是以 (h,v)作为矩形的左下角,绘制一个填充为黑色的矩形,并将h加上矩形的宽度。另 一条命令非常相似
137 a[4] b[4]只是不改变h。
#define DVI_SETRULE 132
#define DVI_PUTRULE 137
int DVIDocument::dosetrule()
{
    long ht = readsignedquad();
    long wd = readsignedquad();
    int code = drawrule(dvi2dev * state.h, -dvi2dev * state.v, dvi2dev * wd, dvi2dev * ht);
    state.h += wd;
    return code;
}
int DVIDocument::doputrule()
{
    long ht = readsignedquad();
    long wd = readsignedquad();
    return drawrule(dvi2dev * state.h, -dvi2dev * state.v, dvi2dev * wd, dvi2dev * ht);
}
改变横坐标的命令有三组。第一组是
143 b[1]b分别占1、2、3、4个字节。含义是h加上b
144 b[2]
145 b[3]
146 b[4]
#define DVI_RIGHT1 143
#define DVI_RIGHT2 144
#define DVI_RIGHT3 145
#define DVI_RIGHT4 146
int DVIDocument::doright1()
{
    long wd = readsignedbyte();
    state.h += wd;
    return 0;
}
int DVIDocument::doright2()
{
    long wd = readsignedpair();
    state.h += wd;
    return 0;
}
int DVIDocument::doright3()
{
    long wd = readsignedtriple();
    state.h += wd;
    return 0;
}
int DVIDocument::doright4()
{
    long wd = readsignedquad();
    state.h += wd;
    return 0;
}
第二组是
147第一个操作数就是state.w,含义是h加上state.w。其它四个将state.w设为b,同时h加上b
148 b[1]
149 b[2]
150 b[3]
151 b[4]
#define DVI_W0 147
#define DVI_W1 148
#define DVI_W2 149
#define DVI_W3 150
#define DVI_W4 151
int DVIDocument::dow0()
{
    state.h += state.w;
    return 0;
}
int DVIDocument::dow1()
{
    long w = readsignedbyte();
    state.w = w;
    state.h += w;
    return 0;
}
int DVIDocument::dow2()
{
    long w = readsignedpair();
    state.w = w;
    state.h += w;
    return 0;
}
int DVIDocument::dow3()
{
    long w = readsignedtriple();
    state.w = w;
    state.h += w;
    return 0;
}
int DVIDocument::dow4()
{
    long w = readsignedquad();
    state.w = w;
    state.h += w;
    return 0;
}
第三组是
152第一个操作数就是state.x,含义是h加上state.x。其它四个将state.x设为b,同时h加上b
153 b[1]
154 b[2]
155 b[3]
156 b[4]
#define DVI_X0 152
#define DVI_X1 153
#define DVI_X2 154
#define DVI_X3 155
#define DVI_X4 156
int DVIDocument::dox0()
{
    state.h += state.x;
    return 0;
}
int DVIDocument::dox1()
{
    long x = readsignedbyte();
    state.x = x;
    state.h += x;
    return 0;
}
int DVIDocument::dox2()
{
    long x = readsignedpair();
    state.x = x;
    state.h += x;
    return 0;
}
int DVIDocument::dox3()
{
    long x = readsignedtriple();
    state.x = x;
    state.h += x;
    return 0;
}
int DVIDocument::dox4()
{
    long x = readsignedquad();
    state.x = x;
    state.h += x;
    return 0;
}
改变纵坐标的操作有三组。第一组是
157 a[1]a分别占1、2、3、4个字节,含义是v加上a
158 a[2]
159 a[3]
160 a[4]
#define DVI_DOWN1 157
#define DVI_DOWN2 158
#define DVI_DOWN3 159
#define DVI_DOWN4 160
int DVIDocument::dodown1()
{
    long ht = readsignedbyte();
    state.v += ht;
    return 0;
}
int DVIDocument::dodown2()
{
    long ht = readsignedpair();
    state.v += ht;
    return 0;
}
int DVIDocument::dodown3()
{
    long ht = readsignedtriple();
    state.v += ht;
    return 0;
}
int DVIDocument::dodown4()
{
    long ht = readsignedquad();
    state.v += ht;
    return 0;
}
第二组是
161第一个操作数就是state.y,含义是v加上state.y。其它四个将state.v设为a,同时v加上a
162 a[1]
163 a[2]
164 a[3]
165 a[4]
#define DVI_Y0 161
#define DVI_Y1 162
#define DVI_Y2 163
#define DVI_Y3 164
#define DVI_Y4 165
int DVIDocument::doy0()
{
    state.v += state.y;
    return 0;
}
int DVIDocument::doy1()
{
    long y = readsignedbyte();
    state.y = y;
    state.v += y;
    return 0;
}
int DVIDocument::doy2()
{
    long y = readsignedpair();
    state.y = y;
    state.v += y;
    return 0;
}
int DVIDocument::doy3()
{
    long y = readsignedtriple();
    state.y = y;
    state.v += y;
    return 0;
}
int DVIDocument::doy4()
{
    long y = readsignedquad();
    state.y = y;
    state.v += y;
    return 0;
}
第三组是
166第一个操作数就是state.z,含义是v加上state.z。其它四个将state.z设为a,同时v加上a
167 a[1]
168 a[2]
169 a[3]
170 a[4]
#define DVI_Z0 166
#define DVI_Z1 167
#define DVI_Z2 168
#define DVI_Z3 169
#define DVI_Z4 170
int DVIDocument::doz0()
{
    state.v += state.z;
    return 0;
}
int DVIDocument::doz1()
{
    long z = readsignedbyte();
    state.z = z;
    state.v += z;
    return 0;
}
int DVIDocument::doz2()
{
    long z = readsignedpair();
    state.z = z;
    state.v += z;
    return 0;
}
int DVIDocument::doz3()
{
    long z = readsignedtriple();
    state.z = z;
    state.v += z;
    return 0;
}
int DVIDocument::doz4()
{
    long z = readsignedquad();
    state.z = z;
    state.v += z;
    return 0;
}
压栈操作是
141意思是把当前的(h,v,w,x,y,z)放到栈顶
#define DVI_PUSH 141
int DVIDocument::dopush()
{
    stack[depth++] = state;
    return 0;
}
出栈操作是
142意思是把栈顶的(h,v,w,x,y,z)设置为当前的(h,v,w,x,y,z)
#define DVI_POP 142
int DVIDocument::dopop()
{
    state = stack[--depth];
    return 0;
}
XXX命令有四个
239 k[1] x[k]k表示长度,x存放实际内容。它们是由TeX命令“\special”产生的,图形图像、多媒体、pdf书签、 超链接等都是通过它得到的。简单的dvi处理程序可以跳过这部分内容,因为它们的处理比较复杂, 常见的XXX包括
240 k[2] x[k]
241 k[3] x[k]
242 k[4] x[k]
#define DVI_XXX1 239
#define DVI_XXX2 240
#define DVI_XXX3 241
#define DVI_XXX4 242
int DVIDocument::doxxx(int len)
{
    uchar * buf = new uchar[len+1];;
   for (ulong i = 0; i < len; i++)
        buf[i] = readunsignedbyte();
    ...;
    return 0;
}
int DVIDocument::doxxx1()
{
    long len = readunsignedbyte();
    return doxxx(len);
}
int DVIDocument::doxxx2()
{
    long len = readunsignedpair();
    return doxxx(len);
}
int DVIDocument::doxxx3()
{
    long len = readunsignedtriple();
    return doxxx(len);
}
int DVIDocument::doxxx4()
{
    long len = readunsignedquad();
    return doxxx(len);
}
字体定义在读取“后记”时已经获得,所以页中的字体定义只需简单跳过
int DVIDocument::dofontdef()
{
    readsignedquad();
    readsignedquad();
    readsignedquad();
    long area_len = readunsignedbyte();
    long name_len = readunsignedbyte();
    for (long i = 0; i < area_len + name_len; i++)
        readunsignedbyte();
    return 0;
}
int DVIDocument::dofontdef1()
{
    readunsignedbyte();
    return dofontdef();
}
int DVIDocument::dofontdef2()
{
    readunsignedpair();
    return dofontdef();
}
int DVIDocument::dofontdef3()
{
    readunsignedtriple();
    return dofontdef();
}
int DVIDocument::dofontdef4()
{
    readunsignedquad();
    return dofontdef();
}