Coverage Report

Created: 2025-06-24 07:01

/src/ghostpdl/psi/zmedia2.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2001-2023 Artifex Software, Inc.
2
   All Rights Reserved.
3
4
   This software is provided AS-IS with no warranty, either express or
5
   implied.
6
7
   This software is distributed under license and may not be copied,
8
   modified or distributed except as expressly authorized under the terms
9
   of the license contained in the file LICENSE in this distribution.
10
11
   Refer to licensing information at http://www.artifex.com or contact
12
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
13
   CA 94129, USA, for further information.
14
*/
15
16
17
/* Media matching for setpagedevice */
18
#include "math_.h"
19
#include "memory_.h"
20
#include "ghost.h"
21
#include "gsmatrix.h"
22
#include "oper.h"
23
#include "idict.h"
24
#include "idparam.h"
25
#include "iname.h"
26
#include "store.h"
27
28
/* <pagedict> <attrdict> <policydict> <keys> .matchmedia <key> true */
29
/* <pagedict> <attrdict> <policydict> <keys> .matchmedia false */
30
/* <pagedict> null <policydict> <keys> .matchmedia null true */
31
static int zmatch_page_size(const gs_memory_t *mem,
32
                             const ref * pvreq, const ref * pvmed,
33
                             int policy, int orient, bool roll,
34
                             float *best_mismatch, gs_matrix * pmat,
35
                             gs_point * pmsize);
36
typedef struct match_record_s {
37
    ref best_key, match_key;
38
    uint priority, no_match_priority;
39
} match_record_t;
40
static void
41
reset_match(match_record_t *match)
42
7.10M
{
43
7.10M
    make_null(&match->best_key);
44
7.10M
    make_null(&match->match_key);
45
7.10M
    match->priority = match->no_match_priority;
46
7.10M
}
47
static int
48
zmatchmedia(i_ctx_t *i_ctx_p)
49
1.57M
{
50
1.57M
    os_ptr op = osp;
51
1.57M
    os_ptr preq = op - 3;
52
1.57M
    os_ptr pattr = op - 2;
53
1.57M
    os_ptr ppol = op - 1;
54
1.57M
    os_ptr pkeys = op;    /* *const */
55
1.57M
    int policy_default;
56
1.57M
    float best_mismatch = (float)max_long; /* adhoc */
57
1.57M
    float mepos_penalty;
58
1.57M
    float mbest = best_mismatch;
59
1.57M
    match_record_t match;
60
1.57M
    ref no_priority;
61
1.57M
    ref *ppriority;
62
1.57M
    int mepos, orient;
63
1.57M
    bool roll;
64
1.57M
    int code;
65
1.57M
    int ai;
66
1.57M
    struct mkd_ {
67
1.57M
        ref key, dict;
68
1.57M
    } aelt;
69
1.57M
    if (r_has_type(pattr, t_null)) {
70
0
        check_op(4);
71
0
        make_null(op - 3);
72
0
        make_true(op - 2);
73
0
        pop(2);
74
0
        return 0;
75
0
    }
76
1.57M
    check_type(*preq, t_dictionary);
77
1.57M
    check_dict_read(*preq);
78
1.57M
    check_type(*pattr, t_dictionary);
79
1.57M
    check_dict_read(*pattr);
80
1.57M
    check_type(*ppol, t_dictionary);
81
1.57M
    check_dict_read(*ppol);
82
1.57M
    check_array(*pkeys);
83
1.57M
    check_read(*pkeys);
84
1.57M
    switch (code = dict_int_null_param(preq, "MediaPosition", 0, 0x7fff,
85
1.57M
                                       0, &mepos)) {
86
0
        default:
87
0
            return code;
88
0
        case 2:
89
1.57M
        case 1:
90
1.57M
            mepos = -1;
91
1.57M
        case 0:;
92
1.57M
    }
93
1.57M
    switch (code = dict_int_null_param(preq, "Orientation", 0, 3,
94
1.57M
                                       0, &orient)) {
95
0
        default:
96
0
            return code;
97
0
        case 2:
98
1.57M
        case 1:
99
1.57M
            orient = -1;
100
1.57M
        case 0:;
101
1.57M
    }
102
1.57M
    code = dict_bool_param(preq, "RollFedMedia", false, &roll);
103
1.57M
    if (code < 0)
104
0
        return code;
105
1.57M
    code = dict_int_param(ppol, "PolicyNotFound", 0, 7, 0,
106
1.57M
                          &policy_default);
107
1.57M
    if (code < 0)
108
0
        return code;
109
1.57M
    if (dict_find_string(pattr, "Priority", &ppriority) > 0) {
110
0
        check_array_only(*ppriority);
111
0
        check_read(*ppriority);
112
1.57M
    } else {
113
1.57M
        make_empty_array(&no_priority, a_readonly);
114
1.57M
        ppriority = &no_priority;
115
1.57M
    }
116
1.57M
    match.no_match_priority = r_size(ppriority);
117
1.57M
    reset_match(&match);
118
1.57M
    for (ai = dict_first(pattr);
119
62.3M
         (ai = dict_next(pattr, ai, (ref * /*[2]*/)&aelt)) >= 0;
120
60.8M
         ) {
121
60.8M
        if (r_has_type(&aelt.dict, t_dictionary) &&
122
60.8M
            r_has_attr(dict_access_ref(&aelt.dict), a_read) &&
123
60.8M
            r_has_type(&aelt.key, t_integer)
124
60.8M
            ) {
125
60.8M
            bool match_all;
126
60.8M
            uint ki, pi;
127
128
60.8M
            code = dict_bool_param(&aelt.dict, "MatchAll", false,
129
60.8M
                                   &match_all);
130
60.8M
            if (code < 0)
131
0
                return code;
132
121M
            for (ki = 0; ki < r_size(pkeys); ki++) {
133
60.8M
                ref key;
134
60.8M
                ref kstr;
135
60.8M
                ref *prvalue;
136
60.8M
                ref *pmvalue;
137
60.8M
                ref *ppvalue;
138
60.8M
                int policy;
139
140
60.8M
                array_get(imemory, pkeys, ki, &key);
141
60.8M
                if (dict_find(&aelt.dict, &key, &pmvalue) <= 0)
142
789k
                    continue;
143
60.0M
                if (dict_find(preq, &key, &prvalue) <= 0 ||
144
60.0M
                    r_has_type(prvalue, t_null)
145
60.0M
                    ) {
146
0
                    if (match_all)
147
0
                        goto no;
148
0
                    else
149
0
                        continue;
150
0
                }
151
                /* Look for the Policies entry for this key. */
152
60.0M
                if (dict_find(ppol, &key, &ppvalue) > 0) {
153
60.0M
                    check_type_only(*ppvalue, t_integer);
154
60.0M
                    policy = ppvalue->value.intval;
155
60.0M
                } else
156
0
                    policy = policy_default;
157
        /*
158
         * Match a requested attribute value with the attribute value in the
159
         * description of a medium.  For all attributes except PageSize,
160
         * matching means equality.  PageSize is special; see match_page_size
161
         * below.
162
         */
163
60.0M
                if (r_has_type(&key, t_name) &&
164
60.0M
                    (name_string_ref(imemory, &key, &kstr),
165
60.0M
                     r_size(&kstr) == 8 &&
166
60.0M
                     !memcmp(kstr.value.bytes, "PageSize", 8))
167
60.0M
                    ) {
168
60.0M
                    gs_matrix ignore_mat;
169
60.0M
                    gs_point ignore_msize;
170
171
60.0M
                    if (zmatch_page_size(imemory, prvalue, pmvalue,
172
60.0M
                                         policy, orient, roll,
173
60.0M
                                         &best_mismatch,
174
60.0M
                                         &ignore_mat,
175
60.0M
                                         &ignore_msize)
176
60.0M
                        <= 0)
177
5.09k
                        goto no;
178
60.0M
                } else if (!obj_eq(imemory, prvalue, pmvalue))
179
0
                    goto no;
180
60.0M
            }
181
182
60.7M
            mepos_penalty = (mepos < 0 || aelt.key.value.intval == mepos) ?
183
60.7M
                0 : .001;
184
185
            /* We have a match. Save the match in case no better match is found */
186
60.7M
            if (r_has_type(&match.match_key, t_null))
187
1.57M
                match.match_key = aelt.key;
188
            /*
189
             * If it is a better match than the current best it supersedes it
190
             * regardless of priority. If the match is the same, then update
191
             * to the current only if the key value is lower.
192
             */
193
60.7M
            if (best_mismatch + mepos_penalty <= mbest) {
194
60.7M
                if (best_mismatch + mepos_penalty < mbest  ||
195
60.7M
                    (r_has_type(&match.match_key, t_integer) &&
196
60.0M
                     match.match_key.value.intval > aelt.key.value.intval)) {
197
5.52M
                    reset_match(&match);
198
5.52M
                    match.match_key = aelt.key;
199
5.52M
                    mbest = best_mismatch + mepos_penalty;
200
5.52M
                }
201
60.7M
            }
202
            /* In case of a tie, see if the new match has priority. */
203
60.7M
            for (pi = match.priority; pi > 0;) {
204
0
                ref pri;
205
206
0
                pi--;
207
0
                array_get(imemory, ppriority, pi, &pri);
208
0
                if (obj_eq(imemory, &aelt.key, &pri)) { /* Yes, higher priority. */
209
0
                    match.best_key = aelt.key;
210
0
                    match.priority = pi;
211
0
                    break;
212
0
                }
213
0
            }
214
60.8M
no:;
215
60.8M
        }
216
60.8M
    }
217
1.57M
    if (r_has_type(&match.match_key, t_null)) {
218
67
        make_false(op - 3);
219
67
        pop(3);
220
1.57M
    } else {
221
1.57M
        if (r_has_type(&match.best_key, t_null))
222
1.57M
            op[-3] = match.match_key;
223
0
        else
224
0
            op[-3] = match.best_key;
225
1.57M
        make_true(op - 2);
226
1.57M
        pop(2);
227
1.57M
    }
228
1.57M
    return 0;
229
1.57M
}
230
231
/* [<req_x> <req_y>] [<med_x0> <med_y0> (<med_x1> <med_y1> | )]
232
 *     <policy> <orient|null> <roll> <matrix|null> .matchpagesize
233
 *   <matrix|null> <med_x> <med_y> true   -or-  false
234
 */
235
static int
236
zmatchpagesize(i_ctx_t *i_ctx_p)
237
1.67M
{
238
1.67M
    os_ptr op = osp;
239
1.67M
    gs_matrix mat;
240
1.67M
    float ignore_mismatch = (float)max_long;
241
1.67M
    gs_point media_size;
242
1.67M
    int orient;
243
1.67M
    bool roll;
244
1.67M
    int code;
245
246
1.67M
    check_type(op[-3], t_integer);
247
1.67M
    if (r_has_type(op - 2, t_null))
248
1.67M
        orient = -1;
249
0
    else {
250
0
        check_int_leu(op[-2], 3);
251
0
        orient = (int)op[-2].value.intval;
252
0
    }
253
1.67M
    check_type(op[-1], t_boolean);
254
1.67M
    roll = op[-1].value.boolval;
255
1.67M
    code = zmatch_page_size(imemory,
256
1.67M
                            op - 5, op - 4, (int)op[-3].value.intval,
257
1.67M
                            orient, roll,
258
1.67M
                            &ignore_mismatch, &mat, &media_size);
259
1.67M
    switch (code) {
260
67
        default:
261
67
            return code;
262
0
        case 0:
263
0
            make_false(op - 5);
264
0
            pop(5);
265
0
            break;
266
1.67M
        case 1:
267
1.67M
            code = write_matrix(op, &mat);
268
1.67M
            if (code < 0 && !r_has_type(op, t_null))
269
0
                return code;
270
1.67M
            op[-5] = *op;
271
1.67M
            make_real(op - 4, media_size.x);
272
1.67M
            make_real(op - 3, media_size.y);
273
1.67M
            make_true(op - 2);
274
1.67M
            pop(2);
275
1.67M
            break;
276
1.67M
    }
277
1.67M
    return 0;
278
1.67M
}
279
/* Match the PageSize.  See below for details. */
280
static int
281
match_page_size(const gs_point * request,
282
                             const gs_rect * medium,
283
                             int policy, int orient, bool roll,
284
                             float *best_mismatch, gs_matrix * pmat,
285
                             gs_point * pmsize);
286
static int
287
zmatch_page_size(const gs_memory_t *mem, const ref * pvreq, const ref * pvmed,
288
                 int policy, int orient, bool roll,
289
                 float *best_mismatch, gs_matrix * pmat, gs_point * pmsize)
290
61.6M
{
291
61.6M
    uint nr, nm;
292
61.6M
    int code;
293
61.6M
    ref rv[6];
294
295
    /* array_get checks array types and size. */
296
    /* This allows normal or packed arrays to be used */
297
61.6M
    if ((code = array_get(mem, pvreq, 1, &rv[1])) < 0)
298
0
        return_error(code);
299
61.6M
    nr = r_size(pvreq);
300
61.6M
    if ((code = array_get(mem, pvmed, 1, &rv[3])) < 0)
301
0
        return_error(code);
302
61.6M
    nm = r_size(pvmed);
303
61.6M
    if (!((nm == 2 || nm == 4) && (nr == 2 || nr == nm)))
304
0
        return_error(gs_error_rangecheck);
305
61.6M
    {
306
61.6M
        uint i;
307
61.6M
        double v[6];
308
61.6M
        int code;
309
310
61.6M
        array_get(mem, pvreq, 0, &rv[0]);
311
308M
        for (i = 0; i < 4; ++i)
312
246M
            array_get(mem,pvmed, i % nm, &rv[i + 2]);
313
61.6M
        if ((code = num_params(rv + 5, 6, v)) < 0)
314
0
            return code;
315
61.6M
        {
316
61.6M
            gs_point request;
317
61.6M
            gs_rect medium;
318
319
61.6M
            request.x = v[0], request.y = v[1];
320
61.6M
            medium.p.x = v[2], medium.p.y = v[3],
321
61.6M
                medium.q.x = v[4], medium.q.y = v[5];
322
61.6M
            return match_page_size(&request, &medium, policy, orient,
323
61.6M
                                   roll, best_mismatch, pmat, pmsize);
324
61.6M
        }
325
61.6M
    }
326
61.6M
}
327
/*
328
 * Match a requested PageSize with the PageSize of a medium.  The medium
329
 * may specify either a single value [mx my] or a range
330
 * [mxmin mymin mxmax mymax]; matching means equality or inclusion
331
 * to within a tolerance of 5, possibly swapping the requested X and Y.
332
 * Take the Policies value into account, keeping track of the discrepancy
333
 * if needed.  When a match is found, also return the matrix to be
334
 * concatenated after setting up the default matrix, and the actual
335
 * media size.
336
 *
337
 * NOTE: The algorithm here doesn't work properly for variable-size media
338
 * when the match isn't exact.  We'll fix it if we ever need to.
339
 */
340
static void make_adjustment_matrix(const gs_point * request,
341
                                    const gs_rect * medium,
342
                                    gs_matrix * pmat,
343
                                    bool scale, int rotate);
344
static int
345
match_page_size(const gs_point * request, const gs_rect * medium, int policy,
346
                int orient, bool roll, float *best_mismatch, gs_matrix * pmat,
347
                gs_point * pmsize)
348
61.6M
{
349
61.6M
    double rx = request->x, ry = request->y;
350
351
61.6M
    if ((rx <= 0) || (ry <= 0))
352
5.15k
        return_error(gs_error_rangecheck);
353
61.6M
    if (policy == 7) {
354
                /* (Adobe) hack: just impose requested values */
355
61.6M
        *best_mismatch = 0;
356
61.6M
        gs_make_identity(pmat);
357
61.6M
        *pmsize = *request;
358
61.6M
    } else {
359
0
        int fit_direct  = rx - medium->p.x >= -5 && rx - medium->q.x <= 5
360
0
                       && ry - medium->p.y >= -5 && ry - medium->q.y <= 5;
361
0
        int fit_rotated = rx - medium->p.y >= -5 && rx - medium->q.y <= 5
362
0
                       && ry - medium->p.x >= -5 && ry - medium->q.x <= 5;
363
364
        /* Fudge matches from a non-standard page size match (4 element array) */
365
        /* as worse than an exact match from a standard (2 element array), but */
366
        /* better than for a rotated match to a standard pagesize. This should */
367
        /* prevent rotation unless we have to (particularly for raster file    */
368
        /* formats like TIFF, JPEG, PNG, PCX, BMP, etc. and also should allow  */
369
        /* exact page size specification when there is a range PageSize entry. */
370
        /* As the comment in gs_setpd.ps says "Devices that care will provide  */
371
        /* a real InputAttributes dictionary (most without a range pagesize)   */
372
0
        if ( fit_direct && fit_rotated) {
373
0
            make_adjustment_matrix(request, medium, pmat, false, orient < 0 ? 0 : orient);
374
0
            if (medium->p.x < medium->q.x || medium->p.y < medium->q.y)
375
0
                *best_mismatch = (float)0.001;   /* fudge a match to a range as a small number */
376
0
            else  /* should be 0 for an exact match */
377
0
                *best_mismatch = fabs((rx - medium->p.x) * (medium->q.x - rx)) +
378
0
                                fabs((ry - medium->p.y) * (medium->q.y - ry));
379
0
        } else if ( fit_direct ) {
380
0
            int rotate = orient < 0 ? 0 : orient;
381
382
0
            make_adjustment_matrix(request, medium, pmat, false, (rotate + 1) & 2);
383
0
            *best_mismatch = fabs((medium->p.x - rx) * (medium->q.x - rx)) +
384
0
                                fabs((medium->p.y - ry) * (medium->q.y - ry)) +
385
0
                                    (pmat->xx == 0.0 || (rotate & 1) == 1 ? 0.01 : 0);  /* rotated */
386
0
        } else if ( fit_rotated ) {
387
0
            int rotate = (orient < 0 ? 1 : orient);
388
389
0
            make_adjustment_matrix(request, medium, pmat, false, rotate | 1);
390
0
            *best_mismatch = fabs((medium->p.y - rx) * (medium->q.y - rx)) +
391
0
                                fabs((medium->p.x - ry) * (medium->q.x - ry)) +
392
0
                                    (pmat->xx == 0.0 || (rotate & 1) == 1 ? 0.01 : 0);  /* rotated */
393
0
        } else {
394
0
            int rotate = 0;
395
0
            bool larger = 0;
396
0
            bool adjust = false;
397
0
            float mismatch = medium->q.x * medium->q.y - rx * ry;
398
399
0
            rotate = orient >= 0 ? orient : (rx < ry) ^ (medium->q.x < medium->q.y);
400
401
            /* If either the request or the media is square, there is no point in rotating */
402
0
            if (rx == ry || medium->q.x == medium->q.y)
403
0
                rotate = 0;
404
405
0
            larger = (policy == 13) ? 0 :
406
0
                    (rotate & 1 ? medium->q.y >= rx && medium->q.x >= ry :
407
0
                    medium->q.x >= rx && medium->q.y >= ry);
408
409
0
            switch (policy) {
410
0
                default:    /* exact match only */
411
0
                    return 0;
412
0
                case 3:   /* nearest match, adjust */
413
0
                case 13:        /* non-standard, nearest match, scale down OR up */
414
0
                    adjust = true;
415
                    /* fall through */
416
0
                case 5:   /* nearest match, don't adjust */
417
0
                    if (fabs(mismatch) >= fabs(*best_mismatch))
418
0
                        return 0;
419
0
                    break;
420
0
                case 4:   /* next larger match, adjust */
421
0
                    adjust = true;
422
                    /* fall through */
423
0
                case 6:   /* next larger match, don't adjust */
424
0
                    if (!larger || mismatch >= *best_mismatch)
425
0
                        return 0;
426
0
                    break;
427
0
            }
428
0
            if (adjust)
429
0
                make_adjustment_matrix(request, medium, pmat, !larger, rotate);
430
0
            else {
431
0
                gs_rect req_rect;
432
0
                if(rotate & 1) {
433
0
                    req_rect.p.x = ry;
434
0
                    req_rect.p.y = rx;
435
0
                } else {
436
0
                    req_rect.p.x = rx;
437
0
                    req_rect.p.y = ry;
438
0
                }
439
0
                req_rect.q = req_rect.p;
440
0
                make_adjustment_matrix(request, &req_rect, pmat, false, rotate);
441
0
            }
442
0
            *best_mismatch = fabs(mismatch);
443
0
        }
444
0
        if (pmat->xx == 0) { /* Swap request X and Y. */
445
0
            double temp = rx;
446
447
0
            rx = ry, ry = temp;
448
0
        }
449
0
#define ADJUST_INTO(req, mmin, mmax)\
450
0
      (req < mmin ? mmin : req > mmax ? mmax : req)
451
0
        pmsize->x = ADJUST_INTO(rx, medium->p.x, medium->q.x);
452
0
        pmsize->y = ADJUST_INTO(ry, medium->p.y, medium->q.y);
453
0
#undef ADJUST_INTO
454
0
    }
455
61.6M
    return 1;
456
61.6M
}
457
/*
458
 * Compute the adjustment matrix for scaling and/or rotating the page
459
 * to match the medium.  If the medium is completely flexible in a given
460
 * dimension (e.g., roll media in one dimension, or displays in both),
461
 * we must adjust its size in that dimension to match the request.
462
 * We recognize this by an unreasonably small medium->p.{x,y}.
463
 * The PageSize Policy 3 only scales down, so 'scale' will be false if
464
 * the medium is larger than the request. Policy 13 scales up OR down.
465
 */
466
static void
467
make_adjustment_matrix(const gs_point * request, const gs_rect * medium,
468
                       gs_matrix * pmat, bool scale, int rotate)
469
0
{
470
0
    double rx = request->x, ry = request->y;
471
0
    double mx = medium->q.x, my = medium->q.y;
472
473
    /* Rotate the request if necessary. */
474
0
    if (rotate & 1) {
475
0
        double temp = rx;
476
477
0
        rx = ry, ry = temp;
478
0
    }
479
    /* If 'medium' is flexible, adjust 'mx' and 'my' towards 'rx' and 'ry',
480
       respectively. Note that 'mx' and 'my' have just acquired the largest
481
       permissible value, medium->q. */
482
0
    if (medium->p.x < mx) { /* non-empty width range */
483
0
        if (rx < medium->p.x)
484
0
            mx = medium->p.x; /* use minimum of the range */
485
0
        else if (rx < mx)
486
0
            mx = rx;   /* fits */
487
                /* else leave mx == medium->q.x, i.e., the maximum */
488
0
    }
489
0
    if (medium->p.y < my) { /* non-empty height range */
490
0
        if (ry < medium->p.y)
491
0
            my = medium->p.y; /* use minimum of the range */
492
0
        else if (ry < my)
493
0
            my = ry;   /* fits */
494
            /* else leave my == medium->q.y, i.e., the maximum */
495
0
    }
496
497
    /* Translate to align the centers. */
498
0
    gs_make_translation(mx / 2, my / 2, pmat);
499
500
    /* Rotate if needed. */
501
0
    if (rotate)
502
0
        gs_matrix_rotate(pmat, 90.0 * rotate, pmat);
503
504
    /* Scale if needed. */
505
0
    if (scale) {
506
0
        double xfactor = mx / rx;
507
0
        double yfactor = my / ry;
508
0
        double factor = min(xfactor, yfactor);
509
510
0
        gs_matrix_scale(pmat, factor, factor, pmat);
511
0
    }
512
    /* Now translate the origin back, */
513
    /* using the original, unswapped request. */
514
0
    gs_matrix_translate(pmat, -request->x / 2, -request->y / 2, pmat);
515
0
}
516
#undef MIN_MEDIA_SIZE
517
518
/* ------ Initialization procedure ------ */
519
520
const op_def zmedia2_l2_op_defs[] =
521
{
522
    op_def_begin_level2(),
523
    {"4.matchmedia", zmatchmedia},
524
    {"6.matchpagesize", zmatchpagesize},
525
    op_def_end(0)
526
};