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