fz_error
pdf_cacheobject(pdf_xref *xref, int num, int gen)
{
    fz_error error;
    pdf_xrefentry *x;
    int rnum, rgen;

    if (num < 0 || num >= xref->len)
        return fz_throw("object out of range (%d %d R); xref size %d", num, gen, xref->len);

    x = &xref->table[num];

    if (x->obj)
        return fz_okay;

    if (x->type == 'f')
    {
        x->obj = fz_newnull();
        return fz_okay;
    }
    else if (x->type == 'n')
    {
        fz_seek(xref->file, x->ofs, 0);

        error = pdf_parseindobj(&x->obj, xref, xref->file, xref->scratch, sizeof xref->scratch,
                                &rnum, &rgen, &x->stmofs);
        if (error)
            return fz_rethrow(error, "cannot parse object (%d %d R)", num, gen);

        if (rnum != num)
            return fz_throw("found object (%d %d R) instead of (%d %d R)", rnum, rgen, num, gen);

        if (xref->crypt)
            pdf_cryptobj(xref->crypt, x->obj, num, gen);
    }
    else if (x->type == 'o')
    {
        if (!x->obj)
        {
            error = pdf_loadobjstm(xref, x->ofs, 0, xref->scratch, sizeof xref->scratch);
            if (error)
                return fz_rethrow(error, "cannot load object stream containing object (%d %d R)", num, gen);
            if (!x->obj)
                return fz_throw("object (%d %d R) was not found in its object stream", num, gen);
        }
    }
    else
    {
        return fz_throw("assert: corrupt xref struct");
    }

    return fz_okay;
}
static fz_error
pdf_readnewtrailer(pdf_xref *xref, char *buf, int cap)
{
    fz_error error;

    pdf_logxref("load new xref format trailer\n");

    error = pdf_parseindobj(&xref->trailer, xref, xref->file, buf, cap, nil, nil, nil);
    if (error)
        return fz_rethrow(error, "cannot parse trailer (compressed)");
    return fz_okay;
}
static fz_error
pdf_readnewxref(fz_obj **trailerp, pdf_xref *xref, char *buf, int cap)
{
    fz_error error;
    fz_stream *stm;
    fz_obj *trailer;
    fz_obj *index;
    fz_obj *obj;
    int num, gen, stmofs;
    int size, w0, w1, w2;
    int t;
    int i;

    pdf_logxref("load new xref format\n");

    error = pdf_parseindobj(&trailer, xref, xref->file, buf, cap, &num, &gen, &stmofs);
    if (error)
        return fz_rethrow(error, "cannot parse compressed xref stream object");

    obj = fz_dictgets(trailer, "Size");
    if (!obj)
    {
        fz_dropobj(trailer);
        return fz_throw("xref stream missing Size entry (%d %d R)", num, gen);
    }
    size = fz_toint(obj);

    if (size >= xref->cap)
    {
        xref->cap = size + 1; /* for hack to allow broken pdf generators with off-by-one errors */
        xref->table = fz_realloc(xref->table, xref->cap * sizeof(pdf_xrefentry));
    }

    if (size > xref->len)
    {
        for (i = xref->len; i < xref->cap; i++)
        {
            xref->table[i].ofs = 0;
            xref->table[i].gen = 0;
            xref->table[i].stmofs = 0;
            xref->table[i].obj = nil;
            xref->table[i].type = 0;
        }
        xref->len = size;
    }

    if (num < 0 || num >= xref->len)
    {
        if (num == xref->len && num < xref->cap)
        {
            /* allow broken pdf files that have off-by-one errors in the xref */
            fz_warn("object id (%d %d R) out of range (0..%d)", num, gen, xref->len - 1);
            xref->len ++;
        }
        else
        {
            fz_dropobj(trailer);
            return fz_throw("object id (%d %d R) out of range (0..%d)", num, gen, xref->len - 1);
        }
    }

    pdf_logxref("\tnum=%d gen=%d size=%d\n", num, gen, size);

    obj = fz_dictgets(trailer, "W");
    if (!obj) {
        fz_dropobj(trailer);
        return fz_throw("xref stream missing W entry (%d %d R)", num, gen);
    }
    w0 = fz_toint(fz_arrayget(obj, 0));
    w1 = fz_toint(fz_arrayget(obj, 1));
    w2 = fz_toint(fz_arrayget(obj, 2));

    index = fz_dictgets(trailer, "Index");

    error = pdf_openstreamat(&stm, xref, num, gen, trailer, stmofs);
    if (error)
    {
        fz_dropobj(trailer);
        return fz_rethrow(error, "cannot open compressed xref stream (%d %d R)", num, gen);
    }

    if (!index)
    {
        error = pdf_readnewxrefsection(xref, stm, 0, size, w0, w1, w2);
        if (error)
        {
            fz_close(stm);
            fz_dropobj(trailer);
            return fz_rethrow(error, "cannot read xref stream (%d %d R)", num, gen);
        }
    }
    else
    {
        for (t = 0; t < fz_arraylen(index); t += 2)
        {
            int i0 = fz_toint(fz_arrayget(index, t + 0));
            int i1 = fz_toint(fz_arrayget(index, t + 1));
            error = pdf_readnewxrefsection(xref, stm, i0, i1, w0, w1, w2);
            if (error)
            {
                fz_close(stm);
                fz_dropobj(trailer);
                return fz_rethrow(error, "cannot read xref stream section (%d %d R)", num, gen);
            }
        }
    }

    fz_close(stm);

    *trailerp = trailer;

    return fz_okay;
}
static fz_error
readnewxref(fz_obj **trailerp, pdf_xref *xref, char *buf, int cap)
{
    fz_error error;
    fz_stream *stm;
    fz_obj *trailer;
    fz_obj *index;
    fz_obj *obj;
    int oid, gen, stmofs;
    int size, w0, w1, w2;
    int t;

    pdf_logxref("load new xref format\n");

    error = pdf_parseindobj(&trailer, xref, xref->file, buf, cap, &oid, &gen, &stmofs);
    if (error)
        return fz_rethrow(error, "cannot parse compressed xref stream object");

    if (oid < 0 || oid >= xref->len)
    {
        if (oid == xref->len && oid < xref->cap)
        {
            /* allow broken pdf files that have off-by-one errors in the xref */
            fz_warn("object id (%d %d R) out of range (0..%d)", oid, gen, xref->len - 1);
            xref->len ++;
        }
        else
        {
            fz_dropobj(trailer);
            return fz_throw("object id (%d %d R) out of range (0..%d)", oid, gen, xref->len - 1);
        }
    }

    xref->table[oid].type = 'n';
    xref->table[oid].gen = gen;
    xref->table[oid].obj = fz_keepobj(trailer);
    xref->table[oid].stmofs = stmofs;
    xref->table[oid].ofs = 0;

    obj = fz_dictgets(trailer, "Size");
    if (!obj)
    {
        fz_dropobj(trailer);
        return fz_throw("xref stream missing Size entry");
    }
    size = fz_toint(obj);

    obj = fz_dictgets(trailer, "W");
    if (!obj) {
        fz_dropobj(trailer);
        return fz_throw("xref stream missing W entry");
    }
    w0 = fz_toint(fz_arrayget(obj, 0));
    w1 = fz_toint(fz_arrayget(obj, 1));
    w2 = fz_toint(fz_arrayget(obj, 2));

    index = fz_dictgets(trailer, "Index");

    error = pdf_openstream(&stm, xref, oid, gen);
    if (error)
    {
        fz_dropobj(trailer);
        return fz_rethrow(error, "cannot open compressed xref stream");
    }

    if (!index)
    {
        error = readnewxrefsection(xref, stm, 0, size, w0, w1, w2);
        if (error)
        {
            fz_dropstream(stm);
            fz_dropobj(trailer);
            return fz_rethrow(error, "cannot read xref stream");
        }
    }
    else
    {
        for (t = 0; t < fz_arraylen(index); t += 2)
        {
            int i0 = fz_toint(fz_arrayget(index, t + 0));
            int i1 = fz_toint(fz_arrayget(index, t + 1));
            error = readnewxrefsection(xref, stm, i0, i1, w0, w1, w2);
            if (error)
            {
                fz_dropstream(stm);
                fz_dropobj(trailer);
                return fz_rethrow(error, "cannot read xref stream section");
            }
        }
    }

    fz_dropstream(stm);

    *trailerp = trailer;

    return fz_okay;
}