summaryrefslogtreecommitdiff
path: root/gs/base/gxdhtserial.c
blob: da73037ac4e537282d998e7955e63d557604b788 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
/* Copyright (C) 2001-2006 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied, modified
   or distributed except as expressly authorized under the terms of that
   license.  Refer to licensing information at http://www.artifex.com/
   or contact Artifex Software, Inc.,  7 Mt. Lassen Drive - Suite A-134,
   San Rafael, CA  94903, U.S.A., +1(415)492-9861, for further information.
*/

/* $Id$ */
/* Serialization and de-serialization for (traditional) halftones */

#include "memory_.h"
#include "gx.h"
#include "gscdefs.h"
#include "gserrors.h"
#include "gsstruct.h"
#include "gsutil.h"             /* for gs_next_ids */
#include "gzstate.h"
#include "gxdevice.h"           /* for gzht.h */
#include "gzht.h"
#include "gswts.h"
#include "gxdhtres.h"
#include "gsserial.h"
#include "gxdhtserial.h"

/*
 * Declare the set of procedures that return resident halftones. This
 * declares both the array of procedures and their type. It is used
 * only to check if a transmitted halftone order matches one in ROM.
 */
extern_gx_device_halftone_list();

/*
 * An enumeration of halftone transfer functions. These must distinguish
 * between cases in which no transfer function is present, and when one
 * is present but provides the identity transformation (an empty
 * PostScript array).
 */
typedef enum {
    gx_ht_tf_none = 0,
    gx_ht_tf_identity,
    gx_ht_tf_complete
} gx_ht_tf_type_t;

/* enumeration to distinguish well-tempered screening orders from others */
typedef enum {
    gx_ht_traditional,
    gx_ht_wts
} gx_ht_order_type_t;

/*
 * Serialize a transfer function. These will occupy one byte if they are
 * not present or provide an identity mapping,
 * 1 + transfer_map_size * sizeof(frac) otherwise.
 *
 * Returns:
 *
 *    0, with *psize set the the amount of space required, if successful
 *
 *    gs_error_rangecheck, with *psize set to the size required, if the
 *        original *psize was not large enough
 */
static int
gx_ht_write_tf(
    const gx_transfer_map * pmap,
    byte *                  data,
    uint *                  psize )
{
    int                     req_size = 1;   /* minimum of one byte */

    /* check for sufficient space */
    if ( pmap != 0 && pmap->proc != gs_identity_transfer)
        req_size += sizeof(pmap->values);
    if (req_size > *psize) {
        *psize = req_size;
        return gs_error_rangecheck;
    }

    if (req_size == 1)
        *data = (byte)(pmap == 0 ? gx_ht_tf_none : gx_ht_tf_identity);
    else {
        *data++ = (byte)gx_ht_tf_complete;
        memcpy(data, pmap->values, sizeof(pmap->values));
    }

    *psize = req_size;
    return 0;
}

/*
 * Reconstruct a transfer function from its serial representation. The
 * buffer provided is expected to be large enough to hold the entire
 * transfer function.
 *
 * Returns the number of bytes read, or < 0 in the event of an error.
 */
static int
gx_ht_read_tf(
    gx_transfer_map **  ppmap,
    const byte *        data,
    uint                size,
    gs_memory_t *       mem )
{
    gx_ht_tf_type_t     tf_type;
    gx_transfer_map *   pmap;

    /* read the type byte */
    if (size == 0)
        return_error(gs_error_rangecheck);
    --size;
    tf_type = (gx_ht_tf_type_t)*data++;

    /* if no transfer function, exit now */
    if (tf_type == gx_ht_tf_none) {
        *ppmap = 0;
        return 1;
    }

    /* allocate a transfer map */
    rc_alloc_struct_1( pmap,
                       gx_transfer_map,
                       &st_transfer_map,
                       mem,
                       return_error(gs_error_VMerror),
                       "gx_ht_read_tf" );

    pmap->id = gs_next_ids(mem, 1);
    pmap->closure.proc = 0;
    pmap->closure.data = 0;
    if (tf_type == gx_ht_tf_identity) {
        gx_set_identity_transfer(pmap);
        return 1;
    } else if (tf_type == gx_ht_tf_complete && size >= sizeof(pmap->values)) {
        memcpy(pmap->values, data, sizeof(pmap->values));
        pmap->proc = gs_mapped_transfer;
        *ppmap = pmap;
        return 1 + sizeof(pmap->values);
    } else {
        rc_decrement(pmap, "gx_ht_read_tf");
        return_error(gs_error_rangecheck);
    }
}

static int
gx_ht_write_component_wts(const wts_screen_t *wts, byte *data, uint *psize)
{
    uint hdr_size = wts_size(wts);
    uint cell_nsamples = wts->cell_width * wts->cell_height;
    uint cell_size = cell_nsamples * sizeof(wts_screen_sample_t);
    uint req_size = 1 + hdr_size + cell_size;

    if (req_size > *psize) {
        *psize = req_size;
        return gs_error_rangecheck;
    }

    /* identify this as a wts halftone. */
    *data++ = (byte)gx_ht_wts;

    /* copy in wts header */
    memcpy(data, wts, hdr_size);
    ((wts_screen_t *)data)->samples = NULL;
    data += hdr_size;

    /* copy in treshold cell */
    memcpy(data, wts->samples, cell_size);
    *psize = req_size;
    return 0;
}

/*
 * Serialize a halftone component. The only part that is serialized is the
 * halftone order; the other two components are only required during
 * halftone construction.
 *
 * Returns:
 *
 *    0, with *psize set the the amount of space required, if successful
 *
 *    gs_error_rangecheck, with *psize set to the size required, if the
 *        original *psize was not large enough
 *
 *    some other error code, with *psize unchanged, in the event of an
 *        error other than lack of space
 */
static int
gx_ht_write_component(
    const gx_ht_order_component *   pcomp,
    byte *                          data,
    uint *                          psize )
{
    const gx_ht_order *             porder = &pcomp->corder;
    byte *                          data0 = data;
    int                             code, levels_size, bits_size;
    uint			    tmp_size = 0;
    int                             req_size;

    /*
     * There is no need to transmit the comp_number field, as this must be
     * the same as the index in the component array (see gx_ht_write).
     *
     * There is also no reason to transmit the colorant name (cname), as
     * this is only used by some high-level devices that would not be targets
     * of the command list device (and even those devices should be able to
     * get the information from their color models).
     *
     * This leaves the order itself.
     *
     * Check if we are a well-tempered-screening order. Serialization of these
     * is handled in a separate function.
     */
    if (porder->wts != 0)
        return gx_ht_write_component_wts(porder->wts, data, psize);

    /*
     * The following order fields are not transmitted:
     *
     *  params          Only required during halftone cell construction
     *
     *  wse, wts        Only used for well-tempered screens (see above)
     *
     *  raster          Can be re-calculated by the renderer from the width
     *
     *  orig_height,    The only potential use for these parameters is in
     *  orig_shift      this routine; they are not useful to the renderer.
     *
     *  full_height     Can be re-calculated by the renderer from the
     *                  height, width, and shift values.
     *
     *  data_memory     Must be provided by the renderer.
     *
     *  cache           Must be provided by the renderer.
     *
     *  screen_params   Ony required during halftone cell construction
     *
     * In addition, the procs parameter is passed as an index into the
     * ht_order_procs_table, as the renderer may not be in the same address
     * space as the writer.
     *
     * Calculate the size required.
     */
    levels_size = porder->num_levels * sizeof(porder->levels[0]);
    bits_size = porder->num_bits * porder->procs->bit_data_elt_size;
    req_size =   1          /* gx_ht_type_t */
               + enc_u_sizew(porder->width)
               + enc_u_sizew(porder->height)
               + enc_u_sizew(porder->shift)
               + enc_u_sizew(porder->num_levels)
               + enc_u_sizew(porder->num_bits)
               + 1          /* order procs, as index into table */
               + levels_size
               + bits_size;
    code = gx_ht_write_tf(porder->transfer, data, &tmp_size);
    if (code < 0 && code != gs_error_rangecheck)
        return code;
    req_size += tmp_size;
    if (req_size > *psize) {
        *psize = req_size;
        return gs_error_rangecheck;
    }

    /* identify this as a traditional halftone */
    *data++ = (byte)gx_ht_traditional;

    /* write out the dimensional data */
    enc_u_putw(porder->width, data);
    enc_u_putw(porder->height, data);
    enc_u_putw(porder->shift, data);
    enc_u_putw(porder->num_levels, data);
    enc_u_putw(porder->num_bits, data);

    /* white out the procs index */
    *data++ = porder->procs - ht_order_procs_table;

    /* copy the levels array and whitening order array */
    memcpy(data, porder->levels, levels_size);
    data += levels_size;
    memcpy(data, porder->bit_data, bits_size);
    data += bits_size;

    /* write out the transfer function */
    tmp_size = *psize - (data - data0);
    if ((code = gx_ht_write_tf(porder->transfer, data, &tmp_size)) == 0)
        *psize = tmp_size + (data - data0);
    return code;
}

static int
gx_ht_read_component_wts(gx_ht_order_component *pcomp,
                         const byte *data, uint size,
                         gs_memory_t *mem)
{
    const wts_screen_t *ws = (const wts_screen_t *)data;
    int hdr_size = wts_size(ws);
    int cell_size = ws->cell_width * ws->cell_height *
        sizeof(wts_screen_sample_t);
    int bufsize = 1+hdr_size+cell_size;

    memset(&pcomp->corder, 0, sizeof(pcomp->corder));

    if (size < bufsize)
        return -1;
    pcomp->corder.wts = gs_wts_from_buf(data, bufsize);
    pcomp->cname = 0;
    if (pcomp->corder.wts == NULL)
        return -1;

    return bufsize;
}

/*
 * Reconstruct a halftone component from its serial representation. The
 * buffer provided is expected to be large enough to hold the entire
 * halftone component.
 *
 * Because halftone components are allocated in arrays (an unfortunate
 * arrangement, as it prevents component sharing), a pointer to an
 * already allocated component structure is passed as an operand, as
 * opposed to the more normal mechanism that would have a read routine
 * allocate the component. The memory pointer is still passed, however,
 * as the levels and bit_data arrays must be allocated.
 *
 * Returns the number of bytes read, or < 0 in the event of an error.
 */
static int
gx_ht_read_component(
    gx_ht_order_component * pcomp,
    const byte *            data,
    uint                    size,
    gs_memory_t *           mem )
{
    gx_ht_order             new_order;
    const byte *            data0 = data;
    const byte *            data_lim = data + size;
    gx_ht_order_type_t      order_type;
    int                     i, code, levels_size, bits_size;
    const gx_dht_proc *     phtrp = gx_device_halftone_list;

    /* check the order type */
    if (size == 0)
        return_error(gs_error_rangecheck);
    --size;
    order_type = (gx_ht_order_type_t)*data++;

    /* currently only the traditional halftone order are supported */
    if (order_type != gx_ht_traditional)
        return gx_ht_read_component_wts(pcomp, data, size, mem);

    /*
     * For performance reasons, the number encoding macros do not
     * support full buffer size verification. The code below verifies
     * that a minimum number of bytes is available, then converts
     * blindly and does not check again until the various integers are
     * read. Obviously this can be hazardous, but should not be a
     * problem in practice, as the calling code should have verified
     * that the data provided holds the entire halftone.
     */
    if (size < 7)
        return_error(gs_error_rangecheck);
    enc_u_getw(new_order.width, data);
    enc_u_getw(new_order.height, data);
    enc_u_getw(new_order.shift, data);
    enc_u_getw(new_order.num_levels, data);
    enc_u_getw(new_order.num_bits, data);
    if (data >= data_lim)
        return_error(gs_error_rangecheck);
    new_order.procs = &ht_order_procs_table[*data++];

    /* calculate the space required for levels and bit data */
    levels_size = new_order.num_levels * sizeof(new_order.levels[0]);
    bits_size = new_order.num_bits * new_order.procs->bit_data_elt_size;

    /* + 1 below is for the minimal transfer function */
    if (data + bits_size + levels_size + 1 > data_lim)
        return_error(gs_error_rangecheck);

    /*
     * Allocate the levels and bit data structures. The gx_ht_alloc_ht_order
     * has a name that is both strange and misleading. The routine does
     * not allocate a halftone order. Rather, it initializes the order,
     * and allocates the levels and bit data arrays. In particular, it
     * sets all of the following fields:
     *
     *    wse = 0,
     *    wts = 0,
     *    width = operand width
     *    height = operand height
     *    raster = bitmap_raster(operand width)
     *    shift = operand shift
     *    orig_height = operand height
     *    orig_shift = operand strip_shift
     *    num_levels = operand num_levels
     *    num_bits = operand num_bits
     *    procs = operand procs
     *    levels = newly allocated array
     *    bit_data = new allocated array
     *    cache = 0
     *    transfer = 0
     *
     * Since several of the list fields are already set, this call
     * effectively sets them to the values they already have. This is a
     * bit peculiar but not otherwise harmful.
     *
     * For reasons that are not known and are probably historical, the
     * procedure does not initialize the params or screen_params fields.
     * In the unlikely event that these fields are ever contain pointers,
     * we initialize them explicitly here. Wse, params, and scrren_params
     * probably should not occur in the device halftone at all; they are
     * themselves historical artifacts.
     */
    code = gx_ht_alloc_ht_order( &new_order,
                                 new_order.width,
                                 new_order.height,
                                 new_order.num_levels,
                                 new_order.num_bits,
                                 new_order.shift,
                                 new_order.procs,
                                 mem );
    if (code < 0)
        return code;
    memset(&new_order.params, 0, sizeof(new_order.params));
    memset(&new_order.screen_params, 0, sizeof(new_order.screen_params));

    /* fill in the levels and bit_data arrays */
    memcpy(new_order.levels, data, levels_size);
    data += levels_size;
    memcpy(new_order.bit_data, data, bits_size);
    data += bits_size;

    /* process the transfer function */
    code = gx_ht_read_tf(&new_order.transfer, data, data_lim - data, mem);
    if (code < 0) {
        gx_ht_order_release(&new_order, mem, false);
        return code;
    }
    data += code;

    /*
     * Check to see if the order is in ROM. Since it is possible (if not
     * particularly likely) that the command list writer and renderer do
     * not have the same set of ROM-based halftones, the full halftone
     * order is transmitted and compared against the set ROM set provided
     * by the renderer. If there is a match, the transmitted version is
     * discarded and the ROM version used.
     *
     * It is not clear which, if any or the currently used devices
     * provide a ROM-based halftone order set.
     */
    for (i = 0; phtrp[i] != 0; i++) {
        const gx_device_halftone_resource_t *const *    pphtr = phtrp[i]();
        const gx_device_halftone_resource_t *           phtr;

        while ((phtr = *pphtr++) != 0) {
            /*
             * This test does not check for strict equality of the order,
             * nor is strict equality necessary. The ROM data will replace
             * just the levels and bit_data arrays of the transmitted
             * order, so only these must be the same. We don't even care
             * if the ROM's levels and bit_data arrays are larger; we
             * will never check values beyond the range required by the
             * current order.
             */
            if ( phtr->num_levels * sizeof(phtr->levels[0]) >= levels_size &&
                 phtr->Width * phtr->Height * phtr->elt_size >= bits_size  &&
                 memcmp(phtr->levels, new_order.levels, levels_size) == 0  &&
                 memcmp(phtr->bit_data, new_order.bit_data, bits_size) == 0  ) {
                /* the casts below are required to discard const qualifiers */
                gs_free_object(mem, new_order.bit_data, "gx_ht_read_component");
                new_order.bit_data = (void *)phtr->bit_data;
                gs_free_object(mem, new_order.levels, "gx_ht_read_component");
                new_order.levels = (uint *)phtr->levels;
                goto done;
            }
        }
    }

  done:
    /* everything OK, save the order and return the # of bytes read */
    pcomp->corder = new_order;
    pcomp->cname = 0;
    return data - data0;
}

/*
 * Serialize a halftone. The essential step is the serialization of the
 * halftone orders; beyond this only the halftone type must be
 * transmitted.
 *
 * Returns:
 *
 *    0, with *psize set the the amount of space required, if successful
 *
 *    gs_error_rangecheck, with *psize set to the size required, if the
 *        original *psize was not large enough
 *
 *    some other error code, with *psize unchange, in the event of an
 *        error other than lack of space
 */
int
gx_ht_write(
    const gx_device_halftone *  pdht,
    const gx_device *           dev,
    byte *                      data,
    uint *                      psize )
{
    int                         num_dev_comps;
    int                         i, code;
    uint                        req_size = 2, used_size = 2;
                                /* 1 for halftone type, 1 for num_dev_comps */

    /*
     * With the introduction of color models, there should never be a
     * halftone that includes just one component. Enforce this
     * restriction, even though it is not present in much of the rest
     * of the code.
     *
     * NB: the pdht->order field is ignored by this code.
     */
    if (pdht == 0 || pdht->components == 0)
        return_error(gs_error_unregistered); /* Must not happen. */
    num_dev_comps = pdht->num_dev_comp;

    /*
     * The following fields do not need to be transmitted:
     *
     *  order       Ignored by this code (see above).
     *
     *  rc, id      Recreated by the allocation code on the renderer.
     *
     *  lcm_width,  Can be recreated by the de-serialization code on the
     *  lcm_height  the renderer. Since halftones are transmitted
     *              infrequently (for normal jobs), the time required
     *              for re-calculation is not significant.
     *
     * Hence, the only fields that must be serialized are the type,and
     * the number of components.  (The number of components for the halftone
     * may not match the device's if we are compositing with a process color
     * model which does not match the output device.
     *
     * Several halftone components may be identical, but there is
     * currently no simple way to determine this. Halftones are normally
     * transmitted only once per page, so it is not clear that use of
     * such information would significantly reduce command list size.
     */

    /* calculate the required data space */
    for ( i = 0, code = gs_error_rangecheck;
          i < num_dev_comps && code == gs_error_rangecheck;
          i++) {
        uint     tmp_size = 0;

        /* sanity check */
        if (i != pdht->components[i].comp_number)
            return_error(gs_error_unregistered); /* Must not happen. */

        code = gx_ht_write_component( &pdht->components[i],
                                      data,
                                      &tmp_size );
        req_size += tmp_size;
    }
    if (code < 0 && code != gs_error_rangecheck)
        return code;
    else if (*psize < req_size) {
        *psize = req_size;
        return 0;
    }
    req_size = *psize;

    /* the halftone type is known to fit in a byte */
    *data++ = (byte)pdht->type;
    /* the number of components is known to fit in a byte */
    *data++ = (byte)num_dev_comps;

    /* serialize the halftone components */
    for (i = 0, code = 0; i < num_dev_comps && code == 0; i++) {
        uint    tmp_size = req_size - used_size;

        code = gx_ht_write_component( &pdht->components[i],
                                      data,
                                      &tmp_size );
        used_size += tmp_size;
        data += tmp_size;
    }

    if (code < 0) {
        if (code == gs_error_rangecheck)
            code = gs_error_unknownerror;
        return code;
    }

    *psize = used_size;
    return 0;
}

/*
 * Reconstruct a halftone from its serial representation, and install it
 * as the current halftone. The buffer provided is expected to be large
 * enough to hold the entire halftone.
 *
 * The reading and installation phases are combined in this routine so as
 * to avoid unnecessarily allocating a device halftone and its component
 * array, just to release them immediately after installation is complete.
 * There is also not much reason to reconstuct a halftone except to make
 * it the current halftone.
 *
 * Returns the number of bytes read, or <0 in the event of an error.
 */
int
gx_ht_read_and_install(
    gs_imager_state *       pis,
    const gx_device *       dev,
    const byte *            data,
    uint                    size,
    gs_memory_t *           mem )
{
    gx_ht_order_component   components[GX_DEVICE_COLOR_MAX_COMPONENTS];
    gx_ht_order_component   components_save[GX_DEVICE_COLOR_MAX_COMPONENTS];
    const byte *            data0 = data;
    gx_device_halftone      dht;
    int                     num_dev_comps;
    int                     i, code;

    /* fill in some fixed fields */
    memset(&dht.order, 0, sizeof(dht.order));
    memset(&dht.rc, 0, sizeof(dht.rc));
    dht.id = gs_no_id;      /* updated during installation */
    dht.components = components;
    dht.lcm_width = 1;      /* recalculated during installation */
    dht.lcm_height = 1;

    /* clear pointers in the components array in case we need to abort */
    memset(components, 0, sizeof(components));

    /* get the halftone type */
    if (size < 2)
        return_error(gs_error_rangecheck);
    dht.type = (gs_halftone_type)(*data++);
    num_dev_comps = dht.num_dev_comp = dht.num_comp = *data++;
    size -= 2;

    /* process the component orders */
    for (i = 0, code = 0; i < num_dev_comps && code >= 0; i++) {
        components[i].comp_number = i;
        code = gx_ht_read_component(&components[i], data, size, mem);
        if (code >= 0) {
            size -= code;
            data += code;
        }
    }

    /* if everything is OK, install the halftone */
    if (code >= 0) {
        /* save since the 'install' copies the order, but then clears the source order	*/
        for (i = 0; i < num_dev_comps; i++)
            components_save[i] = components[i];
        code = gx_imager_dev_ht_install(pis, &dht, dht.type, dev);
        for (i = 0; i < num_dev_comps; i++)
            gx_ht_order_release(&components_save[i].corder, mem, false);
    }

    /*
     * If installation failed, discard the allocated elements. We can't
     * use the gx_device_halftone_release procedure, as the components
     * array is on the stack rather than in the heap.
     */
    if (code < 0) {
        for (i = 0; i < num_dev_comps; i++)
            gx_ht_order_release(&components[i].corder, mem, false);
    }

    return code < 0 ? code : data - data0;
}