Example #1
0
int
xps_parse_metadata(xps_context_t *ctx, xps_part_t *part)
{
    XML_Parser xp;
    int code;
    char buf[1024];
    char *s;

    /* Save directory name part */
    xps_strlcpy(buf, part->name, sizeof buf);
    s = strrchr(buf, '/');
    if (s)
        s[0] = 0;

    /* _rels parts are voodoo: their URI references are from
     * the part they are associated with, not the actual _rels
     * part being parsed.
     */
    s = strstr(buf, "/_rels");
    if (s)
        *s = 0;

    ctx->base_uri = buf;
    ctx->part_uri = part->name;

    xp = XML_ParserCreate(NULL);
    if (!xp)
        return gs_throw(-1, "cannot create XML parser");

    XML_SetUserData(xp, ctx);
    XML_SetParamEntityParsing(xp, XML_PARAM_ENTITY_PARSING_NEVER);
    XML_SetStartElementHandler(xp, (XML_StartElementHandler)xps_parse_metadata_imp);

    code = XML_Parse(xp, (char*)part->data, part->size, 1);

    XML_ParserFree(xp);

    ctx->base_uri = NULL;
    ctx->part_uri = NULL;

    if (code == 0)
        return gs_throw1(-1, "cannot parse XML in part: %s", part->name);

    return 0;
}
Example #2
0
xps_item_t *
xps_parse_xml(xps_context_t *ctx, byte *buf, int len)
{
    xps_parser_t parser;
    XML_Parser xp;
    int code;

    parser.ctx = ctx;
    parser.root = NULL;
    parser.head = NULL;
    parser.error = NULL;
    parser.compat = 0;

    xp = XML_ParserCreateNS(NULL, ' ');
    if (!xp)
    {
        gs_throw(-1, "xml error: could not create expat parser");
        return NULL;
    }

    XML_SetUserData(xp, &parser);
    XML_SetParamEntityParsing(xp, XML_PARAM_ENTITY_PARSING_NEVER);
    XML_SetStartElementHandler(xp, (XML_StartElementHandler)on_open_tag);
    XML_SetEndElementHandler(xp, (XML_EndElementHandler)on_close_tag);
    XML_SetCharacterDataHandler(xp, (XML_CharacterDataHandler)on_text);

    code = XML_Parse(xp, (char*)buf, len, 1);
    if (code == 0)
    {
        if (parser.root)
            xps_free_item(ctx, parser.root);
        XML_ParserFree(xp);
        gs_throw1(-1, "xml error: %s", XML_ErrorString(XML_GetErrorCode(xp)));
        return NULL;
    }

    XML_ParserFree(xp);

    if (parser.compat)
        xps_process_compatibility(ctx, parser.root);

    return parser.root;
}
Example #3
0
int
xps_decode_jpeg(xps_context_t *ctx, byte *rbuf, int rlen, xps_image_t *image)
{
    jpeg_decompress_data jddp;
    stream_DCT_state state;
    stream_cursor_read rp;
    stream_cursor_write wp;
    int code;
    int wlen;
    byte *wbuf;
    jpeg_saved_marker_ptr curr_marker;

    s_init_state((stream_state*)&state, &s_DCTD_template, ctx->memory);
    state.report_error = xps_report_error;

    s_DCTD_template.set_defaults((stream_state*)&state);

    state.jpeg_memory = ctx->memory;
    state.data.decompress = &jddp;

    jddp.templat = s_DCTD_template;
    jddp.memory = ctx->memory;
    jddp.scanline_buffer = NULL;

    if ((code = gs_jpeg_create_decompress(&state)) < 0)
        return gs_throw(-1, "cannot gs_jpeg_create_decompress");

    s_DCTD_template.init((stream_state*)&state);

    rp.ptr = rbuf - 1;
    rp.limit = rbuf + rlen - 1;

    /* read the header only by not having a write buffer */
    wp.ptr = 0;
    wp.limit = 0;

    /* Set up to save the ICC marker APP2.
     * According to the spec we should be getting APP1 APP2 and APP13.
     * Library gets APP0 and APP14. */
    jpeg_save_markers(&(jddp.dinfo), 0xe2, 0xFFFF);

    code = s_DCTD_template.process((stream_state*)&state, &rp, &wp, true);
    if (code != 1)
        return gs_throw(-1, "premature EOF or error in jpeg");

    /* Check if we had an ICC profile */
    curr_marker = jddp.dinfo.marker_list;
    while (curr_marker != NULL)
    {
        if (curr_marker->marker == 0xe2)
        {
            /* Found ICC profile. Create a buffer and copy over now.
             * Strip JPEG APP2 14 byte header */
            image->profilesize = curr_marker->data_length - 14;
            image->profile = xps_alloc(ctx, image->profilesize);
            if (image->profile)
            {
                /* If we can't create it, just ignore */
                memcpy(image->profile, &(curr_marker->data[14]), image->profilesize);
            }
            break;
        }
        curr_marker = curr_marker->next;
    }

    image->width = jddp.dinfo.output_width;
    image->height = jddp.dinfo.output_height;
    image->comps = jddp.dinfo.output_components;
    image->bits = 8;
    image->stride = image->width * image->comps;

    if (image->comps == 1)
        image->colorspace = ctx->gray;
    if (image->comps == 3)
        image->colorspace = ctx->srgb;
    if (image->comps == 4)
        image->colorspace = ctx->cmyk;

    if (jddp.dinfo.density_unit == 1)
    {
        image->xres = jddp.dinfo.X_density;
        image->yres = jddp.dinfo.Y_density;
    }
    else if (jddp.dinfo.density_unit == 2)
    {
        image->xres = (int)(jddp.dinfo.X_density * 2.54 + 0.5);
        image->yres = (int)(jddp.dinfo.Y_density * 2.54 + 0.5);
    }
    else
    {
        image->xres = 96;
        image->yres = 96;
    }

    wlen = image->stride * image->height;
    wbuf = xps_alloc(ctx, wlen);
    if (!wbuf)
        return gs_throw1(gs_error_VMerror, "out of memory allocating samples: %d", wlen);

    image->samples = wbuf;

    wp.ptr = wbuf - 1;
    wp.limit = wbuf + wlen - 1;

    code = s_DCTD_template.process((stream_state*)&state, &rp, &wp, true);
    if (code != EOFC)
        return gs_throw1(-1, "error in jpeg (code = %d)", code);

    gs_jpeg_destroy(&state);

    return gs_okay;
}
Example #4
0
static int
xps_report_error(stream_state * st, const char *str)
{
    (void) gs_throw1(-1, "%s", str);
    return 0;
}
Example #5
0
/* (de)crypt a section of text--the procedure is the same
 * in each direction. see strimpl.h for return codes.
 */
static int
s_aes_process(stream_state * ss, stream_cursor_read * pr,
                  stream_cursor_write * pw, bool last)
{
    stream_aes_state *const state = (stream_aes_state *) ss;
    const unsigned char *limit;
    const long in_size = pr->limit - pr->ptr;
    const long out_size = pw->limit - pw->ptr;
    unsigned char temp[16];
    int status = 0;

    /* figure out if we're going to run out of space */
    if (in_size > out_size) {
        limit = pr->ptr + out_size;
        status = 1; /* need more output space */
    } else {
        limit = pr->limit;
        status = last ? EOFC : 0; /* need more input */
    }

    /* set up state and context */
    if (state->ctx == NULL) {
      /* allocate the aes context. this is a public struct but it
         contains internal pointers, so we need to store it separately
         in immovable memory like any opaque structure. */
      state->ctx = (aes_context *)gs_alloc_bytes_immovable(state->memory,
                sizeof(aes_context), "aes context structure");
      if (state->ctx == NULL) {
        gs_throw(gs_error_VMerror, "could not allocate aes context");
        return ERRC;
      }
      if (state->keylength < 1 || state->keylength > SAES_MAX_KEYLENGTH) {
        gs_throw1(gs_error_rangecheck, "invalid aes key length (%d bytes)",
                state->keylength);
        return ERRC;
      }
      aes_setkey_dec(state->ctx, state->key, state->keylength * 8);
    }
    if (!state->initialized) {
        /* read the initialization vector from the first 16 bytes */
        if (in_size < 16) return 0; /* get more data */
        memcpy(state->iv, pr->ptr + 1, 16);
        state->initialized = 1;
        pr->ptr += 16;
    }

    /* decrypt available blocks */
    while (pr->ptr + 16 <= limit) {
      aes_crypt_cbc(state->ctx, AES_DECRYPT, 16, state->iv,
                                pr->ptr + 1, temp);
      pr->ptr += 16;
      if (last && pr->ptr == pr->limit) {
        /* we're on the last block; unpad if necessary */
        int pad;

        if (state->use_padding) {
          /* we are using RFC 1423-style padding, so the last byte of the
             plaintext gives the number of bytes to discard */
          pad = temp[15];
          if (pad < 1 || pad > 16) {
            gs_throw1(gs_error_rangecheck, "invalid aes padding byte (0x%02x)",
                  (unsigned char)pad);
            return ERRC;
          }
        } else {
          /* not using padding */
          pad = 0;
        }

        memcpy(pw->ptr + 1, temp, 16 - pad);
        pw->ptr +=  16 - pad;
        return EOFC;
      }
      memcpy(pw->ptr + 1, temp, 16);
      pw->ptr += 16;
    }

    /* if we got to the end of the file without triggering the padding
       check, the input must not have been a multiple of 16 bytes long.
       complain. */
    if (status == EOFC) {
      gs_throw(gs_error_rangecheck, "aes stream isn't a multiple of 16 bytes");
      return ERRC;
    }

    return status;
}
Example #6
0
static int
xps_true_callback_glyph_name(gs_font *pfont, gs_glyph glyph, gs_const_string *pstr)
{
    /* This function is copied verbatim from plfont.c */

    int table_length;
    int table_offset;

    ulong format;
    uint numGlyphs;
    uint glyph_name_index;
    const byte *postp; /* post table pointer */

    /* guess if the font type is not truetype */
    if ( pfont->FontType != ft_TrueType )
    {
        glyph -= 29;
        if ( glyph >= 0 && glyph < 258 )
        {
            pstr->data = (byte*) pl_mac_names[glyph];
            pstr->size = strlen((char*)pstr->data);
            return 0;
        }
        else
        {
            return gs_throw1(-1, "glyph index %lu out of range", (ulong)glyph);
        }
    }

    table_offset = xps_find_sfnt_table((xps_font_t*)pfont->client_data, "post", &table_length);

    /* no post table */
    if (table_offset < 0)
        return gs_throw(-1, "no post table");

    /* this shoudn't happen but... */
    if ( table_length == 0 )
        return gs_throw(-1, "zero-size post table");

    ((gs_font_type42 *)pfont)->data.string_proc((gs_font_type42 *)pfont,
                                                table_offset, table_length, &postp);
    format = u32(postp);

    /* Format 1.0 (mac encoding) is a simple table see the TT spec.
     * We don't implement this because we don't see it in practice.
     * Format 2.5 is deprecated.
     * Format 3.0 means that there is no post data in the font file.
     * We see this a lot but can't do much about it.
     * The only format we support is 2.0.
     */
    if ( format != 0x20000 )
    {
        /* Invent a name if we don't know the table format. */
        char buf[32];
        sprintf(buf, "glyph%d", (int)glyph);
        pstr->data = (byte*)buf;
        pstr->size = strlen((char*)pstr->data);
        return 0;
    }

    /* skip over the post header */
    numGlyphs = u16(postp + 32);
    if ( glyph < 0 || glyph > numGlyphs - 1)
    {
        return gs_throw1(-1, "glyph index %lu out of range", (ulong)glyph);
    }

    /* glyph name index starts at post + 34 each entry is 2 bytes */
    glyph_name_index = u16(postp + 34 + (glyph * 2));

    /* this shouldn't happen */
    if ( glyph_name_index < 0 && glyph_name_index > 0x7fff )
        return gs_throw(-1, "post table format error");

    /* mac easy */
    if ( glyph_name_index < 258 )
    {
        // dprintf2("glyph name (mac) %d = %s\n", glyph, pl_mac_names[glyph_name_index]);
        pstr->data = (byte*) pl_mac_names[glyph_name_index];
        pstr->size = strlen((char*)pstr->data);
        return 0;
    }

    /* not mac */
    else
    {
        byte *mydata;

        /* and here's the tricky part */
        const byte *pascal_stringp = postp + 34 + (numGlyphs * 2);

        /* 0 - 257 lives in the mac table above */
        glyph_name_index -= 258;

        /* The string we want is the index'th pascal string,
         * so we "hop" to each length byte "index" times. */
        while (glyph_name_index > 0)
        {
            pascal_stringp += ((int)(*pascal_stringp)+1);
            glyph_name_index--;
        }

        /* length byte */
        pstr->size = (int)(*pascal_stringp);

        /* + 1 is for the length byte */
        pstr->data = pascal_stringp + 1;

        /* sanity check */
        if ( pstr->data + pstr->size > postp + table_length || pstr->data - 1 < postp)
            return gs_throw(-1, "data out of range");

        /* sigh - we have to allocate a copy of the data - by the
         * time a high level device makes use of it the font data
         * may be freed. This is a necessary leak. */
        mydata = gs_alloc_bytes(pfont->memory, pstr->size + 1, "glyph to name");
        if ( mydata == 0 )
            return -1;
        memcpy(mydata, pascal_stringp + 1, pstr->size);
        pstr->data = mydata;

        mydata[pstr->size] = 0;

        return 0;
    }
}
Example #7
0
int
xps_decode_jpegxr(xps_context_t *ctx, byte *buf, int len, xps_image_t *output)
{
    FILE *file;
    char *name = xps_alloc(ctx, gp_file_name_sizeof);
    struct state state;
    jxr_container_t container;
    jxr_image_t image;
    int offset, alpha_offset;
    int rc;

    if (!name) {
        return gs_throw(gs_error_VMerror, "cannot allocate scratch file name buffer");
    }

    memset(output, 0, sizeof(*output));

    file = gp_open_scratch_file(ctx->memory, "jpegxr-scratch-", name, "wb+");
    if (!file) {
        xps_free(ctx, name);
        return gs_throw(gs_error_invalidfileaccess, "cannot open scratch file");
    }
    rc = fwrite(buf, 1, len, file);
    if (rc != len) {
        xps_free(ctx, name);
        return gs_throw(gs_error_invalidfileaccess, "cannot write to scratch file");
    }
    fseek(file, 0, SEEK_SET);

    container = jxr_create_container();
    rc = jxr_read_image_container(container, file);
    if (rc < 0) {
        xps_free(ctx, name);
        return gs_throw1(-1, "jxr_read_image_container: %s", jxr_error_string(rc));
    }

    offset = jxrc_image_offset(container, 0);
    alpha_offset = jxrc_alpha_offset(container, 0);

    output->xres = jxrc_width_resolution(container, 0);
    output->yres = jxrc_height_resolution(container, 0);

    image = jxr_create_input();
    jxr_set_PROFILE_IDC(image, 111);
    jxr_set_LEVEL_IDC(image, 255);
    jxr_set_pixel_format(image, jxrc_image_pixelformat(container, 0));
    jxr_set_container_parameters(image,
        jxrc_image_pixelformat(container, 0),
        jxrc_image_width(container, 0),
        jxrc_image_height(container, 0),
        jxrc_alpha_offset(container, 0),
        jxrc_image_band_presence(container, 0),
        jxrc_alpha_band_presence(container, 0), 0);

    jxr_set_block_output(image, xps_decode_jpegxr_block);
    state.ctx = ctx;
    state.output = output;
    jxr_set_user_data(image, &state);

    fseek(file, offset, SEEK_SET);
    rc = jxr_read_image_bitstream(image, file);
    if (rc < 0) {
        xps_free(ctx, name);
        return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc));
    }

    jxr_destroy(image);

    if (alpha_offset > 0)
    {
        image = jxr_create_input();
        jxr_set_PROFILE_IDC(image, 111);
        jxr_set_LEVEL_IDC(image, 255);
        jxr_set_pixel_format(image, jxrc_image_pixelformat(container, 0));
        jxr_set_container_parameters(image,
            jxrc_image_pixelformat(container, 0),
            jxrc_image_width(container, 0),
            jxrc_image_height(container, 0),
            jxrc_alpha_offset(container, 0),
            jxrc_image_band_presence(container, 0),
            jxrc_alpha_band_presence(container, 0), 0);

        jxr_set_block_output(image, xps_decode_jpegxr_alpha_block);
        state.ctx = ctx;
        state.output = output;
        jxr_set_user_data(image, &state);

        fseek(file, alpha_offset, SEEK_SET);
        rc = jxr_read_image_bitstream(image, file);
        if (rc < 0) {
            xps_free(ctx, name);
            return gs_throw1(-1, "jxr_read_image_bitstream: %s", jxr_error_string(rc));
        }

        jxr_destroy(image);
    }

    jxr_destroy_container(container);

    fclose(file);
    unlink(name);
    xps_free(ctx, name);

    return gs_okay;
}
Example #8
0
int
gslt_render_font_glyph(gs_state *pgs, gslt_font_t *xf, gs_matrix *tm, int gid, gslt_glyph_bitmap_t *slot)
{
    gs_fixed_point subpixel = {0, 0}; /* we don't use subpixel accurate device metrics */
    gs_log2_scale_point oversampling = {0, 0}; /* we don't use oversampling */
    gs_text_params_t params;
    gs_text_enum_t *textenum;
    gs_matrix matrix;
    cached_fm_pair *ppair;
    cached_char *cc;
    int code;

    /* get the real font matrix (this is a little dance) */
    gs_setfont(pgs, xf->font); /* set pgs->font and invalidate existing charmatrix */
    gs_setcharmatrix(pgs, tm); /* set the charmatrix to ctm * tm */
    gs_currentcharmatrix(pgs, &matrix, true); /* extract charmatrix (and multiply by FontMatrix) */

    // dprintf4("tm = [%g %g %g %g]\n", matrix.xx, matrix.xy, matrix.yx, matrix.yy);

    /* find the font/matrix pair (or add it) */
    code = gx_lookup_fm_pair(xf->font, &matrix, &oversampling, false, &ppair);
    if (code != 0)
        return gs_throw(-1, "cannot gx_lookup_fm_pair()");

    cc = gx_lookup_cached_char(xf->font, ppair, gid, 0, 1, &subpixel);
    if (!cc)
    {
        /* No luck ... now we need to get it into the cache somehow.
         *
         * We do this by rendering one glyph (that's why we need a device and pgs).
         * The renderer always renders the bitmap into the cache, and draws
         * from out of the cache when blitting to the device.
         *
         * Things don't get evicted from the cache until there is a collision,
         * so we have a safe window to snarf it back out of the cache
         * after it's been drawn to the device.
         */

        // dprintf1("cache miss for glyph %d\n", gid);

        params.operation = TEXT_FROM_SINGLE_GLYPH | TEXT_DO_DRAW | TEXT_RETURN_WIDTH;
        params.data.d_glyph = gid;
        params.size = 1;

	gs_moveto(pgs, 100.0, 100.0); // why?

        code = gs_text_begin(pgs, &params, xf->font->memory, &textenum);
	if (code != 0)
            return gs_throw1(-1, "cannot gs_text_begin() (%d)", code);

        code = gs_text_process(textenum);
	if (code != 0)
            return gs_throw1(-1, "cannot gs_text_process() (%d)", code);

        gs_text_release(textenum, "gslt font render");

        cc = gx_lookup_cached_char(xf->font, ppair, gid, 0, 1, &subpixel);
        if (!cc)
        {
            /* merde! it rendered but was not placed in the cache. */
            return gs_throw(-2, "cannot render from cache");
        }
    }

    /* copy values from the cache into the client struct */
    slot->w = cc->width;
    slot->h = cc->height;
    slot->stride = cc_raster(cc);
    slot->lsb = fixed2int(cc->offset.x);
    slot->top = fixed2int(cc->offset.y);

    slot->cc = cc;
    slot->data = cc_bits(cc);
    gx_retain_cached_char(cc);

#define XXX
#ifndef XXX
    static int xxx = 0; /* declaration out in the wild not allowed in ansi c */
    dprintf1("glyph %d\n", xxx);
    debug_dump_bitmap(cc_bits(cc), cc_raster(cc), cc->height, "");
    {
        char fn[32];
        sprintf(fn, "glyph%d.pbm", xxx);
        FILE *fo = fopen(fn, "wb");
        if (!fo)
            return -1;
        fprintf(fo, "P4\n%d %d\n", cc->width, cc->height);
        int y;
        int s = (cc->width + 7) / 8;
        for (y = 0; y < cc->height; y++)
            fwrite(cc_bits(cc) + y * cc_raster(cc), 1, s, fo);
        fclose(fo);
    }
    xxx ++;
#endif

    return 0;
}
Example #9
0
int
xps_parse_fixed_page(xps_context_t *ctx, xps_part_t *part)
{
    xps_item_t *root, *node;
    xps_resource_t *dict;
    char *width_att;
    char *height_att;
    char base_uri[1024];
    char *s;
    int code;

    if_debug1m('|', ctx->memory, "doc: parsing page %s\n", part->name);

    xps_strlcpy(base_uri, part->name, sizeof base_uri);
    s = strrchr(base_uri, '/');
    if (s)
        s[1] = 0;

    root = xps_parse_xml(ctx, part->data, part->size);
    if (!root)
        return gs_rethrow(-1, "cannot parse xml");

    if (strcmp(xps_tag(root), "FixedPage"))
        return gs_throw1(-1, "expected FixedPage element (found %s)", xps_tag(root));

    width_att = xps_att(root, "Width");
    height_att = xps_att(root, "Height");

    if (!width_att)
        return gs_throw(-1, "FixedPage missing required attribute: Width");
    if (!height_att)
        return gs_throw(-1, "FixedPage missing required attribute: Height");

    dict = NULL;

    /* Setup new page */
    {
        gs_memory_t *mem = ctx->memory;
        gs_state *pgs = ctx->pgs;
        gx_device *dev = gs_currentdevice(pgs);
        gs_param_float_array fa;
        float fv[2];
        gs_c_param_list list;

        gs_c_param_list_write(&list, mem);

        fv[0] = atoi(width_att) / 96.0 * 72.0;
        fv[1] = atoi(height_att) / 96.0 * 72.0;
        fa.persistent = false;
        fa.data = fv;
        fa.size = 2;

        code = param_write_float_array((gs_param_list *)&list, ".MediaSize", &fa);
        if ( code >= 0 )
        {
            gs_c_param_list_read(&list);
            code = gs_putdeviceparams(dev, (gs_param_list *)&list);
        }
        gs_c_param_list_release(&list);

        /* nb this is for the demo it is wrong and should be removed */
        gs_initgraphics(pgs);

        /* 96 dpi default - and put the origin at the top of the page */

        gs_initmatrix(pgs);

        code = gs_scale(pgs, 72.0/96.0, -72.0/96.0);
        if (code < 0)
            return gs_rethrow(code, "cannot set page transform");

        code = gs_translate(pgs, 0.0, -atoi(height_att));
        if (code < 0)
            return gs_rethrow(code, "cannot set page transform");

        code = gs_erasepage(pgs);
        if (code < 0)
            return gs_rethrow(code, "cannot clear page");
    }

    /* Pre-parse looking for transparency */

    ctx->has_transparency = 0;

    for (node = xps_down(root); node; node = xps_next(node))
    {
        if (!strcmp(xps_tag(node), "FixedPage.Resources") && xps_down(node))
            if (xps_resource_dictionary_has_transparency(ctx, base_uri, xps_down(node)))
                ctx->has_transparency = 1;
        if (xps_element_has_transparency(ctx, base_uri, node))
            ctx->has_transparency = 1;
    }

    /* save the state with the original device before we push */
    gs_gsave(ctx->pgs);

    if (ctx->use_transparency && ctx->has_transparency)
    {
        code = gs_push_pdf14trans_device(ctx->pgs, false);
        if (code < 0)
        {
            gs_grestore(ctx->pgs);
            return gs_rethrow(code, "cannot install transparency device");
        }
    }

    /* Draw contents */

    for (node = xps_down(root); node; node = xps_next(node))
    {
        if (!strcmp(xps_tag(node), "FixedPage.Resources") && xps_down(node))
        {
            code = xps_parse_resource_dictionary(ctx, &dict, base_uri, xps_down(node));
            if (code)
            {
                gs_pop_pdf14trans_device(ctx->pgs, false);
                gs_grestore(ctx->pgs);
                return gs_rethrow(code, "cannot load FixedPage.Resources");
            }
        }
        code = xps_parse_element(ctx, base_uri, dict, node);
        if (code)
        {
            gs_pop_pdf14trans_device(ctx->pgs, false);
            gs_grestore(ctx->pgs);
            return gs_rethrow(code, "cannot parse child of FixedPage");
        }
    }

    if (ctx->use_transparency && ctx->has_transparency)
    {
        code = gs_pop_pdf14trans_device(ctx->pgs, false);
        if (code < 0)
        {
            gs_grestore(ctx->pgs);
            return gs_rethrow(code, "cannot uninstall transparency device");
        }
    }

    /* Flush page */
    {
        code = xps_show_page(ctx, 1, true); /* copies, flush */
        if (code < 0)
        {
            gs_grestore(ctx->pgs);
            return gs_rethrow(code, "cannot flush page");
        }
    }

    /* restore the original device, discarding the pdf14 compositor */
    gs_grestore(ctx->pgs);

    if (dict)
    {
        xps_free_resource_dictionary(ctx, dict);
    }

    xps_free_item(ctx, root);

    return 0;
}