目录

设备独立文件

简介

设备独立(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]
字体定义
...
字体定义
249 249 q[4] i[1] 223's[4]
p占四个字节,是一个整数,是最后一页的开始位置。num、den、mag与前言一样。 l和u各占四个字节,分别给出了最大的页高度和最大的页宽度。s是最大的栈深度, 也就是压栈和出栈命令的嵌套深度。t命令占两个字节,是最大页数。

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]
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]
k是一个整数,是字体的标识,在页中用它引用字体,在四种格式中所占字节数不一样。 c占四个字节,是字体的校验和,在严格要求的场合,与相应的TFM文件中对应的四个 字节应该一致。s占四个字节,它是一个缩放因子,用于计算字符在字体中的宽度。 d占四个字节,表示字体的设计大小。a占一个字节,是字体文件路径的长度,l占一个 字节,是字体名的长度,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]
236 k[2]
237 k[3]
238 k[4]
k分别占1、2、3、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]
129 c[2]
130 c[3]
131 c[4]
c分别占1、2、3、4个字节。其含义是用当前字体f,把字体中的字符c放在(h,v),并且 将h加上c的宽度
#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]
134 c[2]
135 c[3]
136 c[4]
c分别占1、2、3、4个字节。其含义是用当前字体f,把字体中的字符c放在(h,v)。
#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]
144 b[2]
145 b[3]
146 b[4]
b分别占1、2、3、4个字节。含义是h加上b
#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
148 b[1]
149 b[2]
150 b[3]
151 b[4]
第一个操作数就是state.w,含义是h加上state.w。其它四个将state.w设为b,同时h加上b
#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
153 b[1]
154 b[2]
155 b[3]
156 b[4]
第一个操作数就是state.x,含义是h加上state.x。其它四个将state.x设为b,同时h加上b
#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]
158 a[2]
159 a[3]
160 a[4]
a分别占1、2、3、4个字节,含义是v加上a
#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
162 a[1]
163 a[2]
164 a[3]
165 a[4]
第一个操作数就是state.y,含义是v加上state.y。其它四个将state.v设为a,同时v加上a
#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
167 a[1]
168 a[2]
169 a[3]
170 a[4]
第一个操作数就是state.z,含义是v加上state.z。其它四个将state.z设为a,同时v加上a
#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

XXX命令有四个

239 k[1] x[k]
240 k[2] x[k]
241 k[3] x[k]
242 k[4] x[k]
k表示长度,x存放实际内容。它们是由TeX命令“\special”产生的,图形图像、多媒体、pdf书签、 超链接等都是通过它得到的。简单的dvi处理程序可以跳过这部分内容,因为它们的处理比较复杂,

常见的XXX包括

#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();
}