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
|
/* Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999 Aladdin Enterprises. 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 the license contained in the file LICENSE in this distribution.
For more information about licensing, please refer to
http://www.ghostscript.com/licensing/. For information on
commercial licensing, go to http://www.artifex.com/licensing/ or
contact Artifex Software, Inc., 101 Lucas Valley Road #110,
San Rafael, CA 94903, U.S.A., +1(415)492-9861.
*/
/* $Id$ */
/* Apply hints for Type 1 fonts. */
#include "gx.h"
#include "gserrors.h"
#include "gxarith.h"
#include "gxfixed.h"
#include "gxmatrix.h"
#include "gxfont.h"
#include "gxfont1.h"
#include "gxtype1.h"
#include "gzpath.h"
/* ------ Path hints ------ */
#ifdef KEEP_OLD_HINTER
/* Forward references */
private void
apply_hstem_hints(gs_type1_state *, int, gs_fixed_point *),
apply_vstem_hints(gs_type1_state *, int, gs_fixed_point *);
/*
* Apply hints along a newly added tail of a subpath.
* Path segments require hints as follows:
* Nearly vertical line: vstem hints at both ends.
* Nearly horizontal line: hstem hints at both ends.
* Curve with nearly vertical/horizontal start/end:
* vstem/hstem hints at start/end.
* We also must take care to handle the implicit closing line for
* subpaths that aren't explicitly closed.
*
* Note that "upper" and "lower" refer to device coordinates, which are
* what we use throughout the Type 1 code; however, "horizontal" and
* "vertical" refer to the character space coordinate system.
*/
#define HINT_VERT_LOWER 1
#define HINT_VERT_UPPER 2 /* must be > lower */
#define HINT_VERT (HINT_VERT_LOWER | HINT_VERT_UPPER)
#define HINT_HORZ_LOWER 4
#define HINT_HORZ_UPPER 8 /* must be > lower */
#define HINT_HORZ (HINT_HORZ_LOWER | HINT_HORZ_UPPER)
private inline bool
nearly_axial(fixed dmajor, fixed dminor)
{
return (dminor <= dmajor >> 4);
}
/*
* Determine which types of hints, if any, are applicable to a given
* line segment.
*/
private int
line_hints(const gs_type1_state * pcis, const gs_fixed_point * p0,
const gs_fixed_point * p1)
{
fixed dx = p1->x - p0->x;
fixed dy = p1->y - p0->y;
fixed adx, ady;
bool xi = pcis->fh.x_inverted, yi = pcis->fh.y_inverted;
int hints;
/*
* To figure out which side of the stem we are on, we assume that the
* inside of the filled area is always to the left of the edge, i.e.,
* edges moving in -X or +Y in character space are on the "upper" side
* of the stem, while edges moving by +X or -Y are on the "lower" side.
* (See section 3.5 of the Adobe Type 1 Font Format book.)
*/
/*
* Map the deltas back into character space. This is essentially an
* inverse-distance-transform with the combined matrix, but we don't
* bother to undo the scaling, since it only matters for the axiality
* test and we don't care about situations where X and Y scaling are
* radically different.
*/
if (xi)
dx = -dx;
if (yi)
dy = -dy;
if (pcis->fh.axes_swapped) {
fixed t = dx;
int ti = xi;
dx = dy, xi = yi;
dy = t, yi = ti;
}
adx = any_abs(dx);
ady = any_abs(dy);
/*
* Note that since upper/lower refer to device space, we must
* interchange them if the corresponding axis is inverted.
*/
if (dy != 0 && nearly_axial(ady, adx)) {
hints = (dy > 0 ? HINT_VERT_UPPER : HINT_VERT_LOWER);
if (xi)
hints ^= (HINT_VERT_LOWER | HINT_VERT_UPPER);
} else if (dx != 0 && nearly_axial(adx, ady)) {
hints = (dx < 0 ? HINT_HORZ_UPPER : HINT_HORZ_LOWER);
if (yi)
hints ^= (HINT_HORZ_LOWER | HINT_HORZ_UPPER);
} else
hints = 0;
if_debug7('y', "[y]hint from 0x%lx(%1.4f,%1.4f) to 0x%lx(%1.4f,%1.4f) = %d\n",
(ulong) p0, fixed2float(p0->x), fixed2float(p0->y),
(ulong) p1, fixed2float(p1->x), fixed2float(p1->y),
hints);
return hints;
}
/* Apply hints at a point. Optionally return the amount of adjustment. */
private void
apply_hints_at(gs_type1_state * pcis, int hints, gs_fixed_point * ppt,
gs_fixed_point * pdiff)
{
fixed px = ppt->x, py = ppt->y;
if_debug4('y', "[y]applying hints %d to 0x%lx(%1.4f,%1.4f) ...\n",
hints, (ulong) ppt, fixed2float(px), fixed2float(py));
if ((hints & HINT_VERT) != 0 &&
(pcis->vstem_hints.count & pcis->dotsection_flag) != 0
)
apply_vstem_hints(pcis, (hints & HINT_VERT_UPPER) -
(hints & HINT_VERT_LOWER), ppt);
if ((hints & HINT_HORZ) != 0 &&
(pcis->hstem_hints.count & pcis->dotsection_flag) != 0
)
apply_hstem_hints(pcis, (hints & HINT_HORZ_UPPER) -
(hints & HINT_HORZ_LOWER), ppt);
if (pdiff != NULL)
pdiff->x = ppt->x - px,
pdiff->y = ppt->y - py;
/* Here is where we would round *ppt to the nearest quarter-pixel */
/* if we wanted to. */
if_debug2('y', "[y] ... => (%1.4f,%1.4f)\n",
fixed2float(ppt->x), fixed2float(ppt->y));
}
/* Add a hint delta to a point. */
#ifndef DEBUG
inline
#endif
private void
add_hint_diff(gs_fixed_point * ppt, gs_fixed_point delta)
{
if_debug7('y', "[y]adding diff (%1.4f,%1.4f) to 0x%lx(%1.4f,%1.4f) => (%1.4f,%1.4f)\n",
fixed2float(delta.x), fixed2float(delta.y), (ulong) ppt,
fixed2float(ppt->x), fixed2float(ppt->y),
fixed2float(ppt->x + delta.x), fixed2float(ppt->y + delta.y));
ppt->x += delta.x;
ppt->y += delta.y;
}
/* Test whether a line is null. */
inline private bool
line_is_null(gs_fixed_point p0, gs_fixed_point p1)
{
return (any_abs(p1.x - p0.x) + any_abs(p1.y - p0.y) < fixed_epsilon * 4);
}
/*
* Adjust the other control points of a curve proportionately when moving
* one end. The Boolean argument indicates whether the point being
* adjusted is the one nearer the point that was moved.
*/
private fixed
scale_delta(fixed diff, fixed dv, fixed lv, bool nearer)
{
if (dv == 0)
return 0;
/*
* fixed_mult_quo requires non-negative 2nd and 3rd arguments,
* and also 2nd argument < 3rd argument.
* If it weren't for that, we would just use it directly.
*
* lv = 0 is implausible, but we have to allow for it.
*/
if (lv == 0)
return (nearer ? diff : fixed_0);
if (lv < 0)
lv = -lv, dv = -dv;
if (dv < 0)
dv = -dv, diff = -diff;
/*
* If dv > lv, there has been some kind of anomaly similar to
* the lv = 0 case.
*/
if (dv >= lv)
return (nearer ? diff : fixed_0);
else
return fixed_mult_quo(diff, dv, lv);
}
private void
adjust_curve_start(curve_segment * pcseg, const gs_fixed_point * pdiff)
{
fixed dx = pdiff->x, dy = pdiff->y;
fixed end_x = pcseg->pt.x, end_y = pcseg->pt.y;
const segment *prev = pcseg->prev;
fixed lx = end_x - (prev->pt.x - dx), ly = end_y - (prev->pt.y - dy);
gs_fixed_point delta;
delta.x = scale_delta(dx, end_x - pcseg->p1.x, lx, true);
delta.y = scale_delta(dy, end_y - pcseg->p1.y, ly, true);
add_hint_diff(&pcseg->p1, delta);
delta.x = scale_delta(dx, end_x - pcseg->p2.x, lx, false);
delta.y = scale_delta(dy, end_y - pcseg->p2.y, ly, false);
add_hint_diff(&pcseg->p2, delta);
}
private void
adjust_curve_end(curve_segment * pcseg, const gs_fixed_point * pdiff)
{
fixed dx = pdiff->x, dy = pdiff->y;
const segment *prev = pcseg->prev;
fixed start_x = prev->pt.x, start_y = prev->pt.y;
fixed lx = pcseg->pt.x - dx - start_x, ly = pcseg->pt.y - dy - start_y;
gs_fixed_point delta;
delta.x = scale_delta(dx, pcseg->p1.x - start_x, lx, false);
delta.y = scale_delta(dy, pcseg->p1.y - start_y, ly, false);
add_hint_diff(&pcseg->p1, delta);
delta.x = scale_delta(dx, pcseg->p2.x - start_x, lx, true);
delta.y = scale_delta(dy, pcseg->p2.y - start_y, ly, true);
add_hint_diff(&pcseg->p2, delta);
}
/*
* Propagate a final wraparound hint back through any null line segments
* to a possible curve. pseg_last.pt has already been adjusted.
*/
private void
apply_final_hint(segment * pseg_last, const gs_fixed_point * pdiff)
{
segment *pseg;
for (pseg = pseg_last;; pseg = pseg->prev) {
segment *prev = pseg->prev;
switch (pseg->type) {
case s_curve:
adjust_curve_end(((curve_segment *) pseg), pdiff);
return;
case s_line:
case s_line_close:
if (!line_is_null(prev->pt, pseg->pt))
return;
add_hint_diff(&prev->pt, *pdiff);
break;
default: /* s_start */
return;
}
}
}
/*
* Handle the end of the subpath wrapping around to the start. This is
* ugly, messy code that we should be able to improve, but I neither see how
* to do it nor understand how the IBM Type 1 rasterizer can produce such
* good results without doing anything like this.
*
* This is a separate procedure only for readability: it is only called
* from one place in the next procedure.
*/
private void
apply_wrapped_hints(gs_type1_state * pcis, subpath * psub, segment * pseg,
int hints, gs_fixed_point * pdiff)
{
/* Some fonts don't use closepath when they should.... */
fixed ctemp;
bool closed =
(pseg->type == s_line_close ||
((ctemp = pseg->pt.x - psub->pt.x,
any_abs(ctemp) < float2fixed(0.1)) &&
(ctemp = pseg->pt.y - psub->pt.y,
any_abs(ctemp) < float2fixed(0.1))));
segment *const pfirst = psub->next;
int hints_first = pcis->hints_initial;
if (closed) {
/*
* Apply the union of the hints at both the end (pseg) and the start
* (psub) of the subpath. Note that we have already applied hints
* at the end, and hints_first at the start. However, because of
* hint replacement, the points might differ even if hints ==
* hints_first. In this case, the initial hints take priority,
* because the initial segment was laid down first.
*/
int do_x, do_y;
gs_fixed_point diff2;
if_debug2('y', "[y]closing closed, hints=%d, hints_first=%d\n",
hints, hints_first);
if (pcis->fh.axes_swapped)
do_x = HINT_HORZ, do_y = HINT_VERT;
else
do_x = HINT_VERT, do_y = HINT_HORZ;
{
/* Apply hints_first - hints to the end. */
int hints_end = hints_first & ~hints;
diff2.x =
(hints_end & do_x ?
psub->pt.x - pcis->unmoved_start.x : 0);
diff2.y =
(hints_end & do_y ?
psub->pt.y - pcis->unmoved_start.y : 0);
}
{
/* Apply hints - hints_first to the start. */
int hints_start = hints & ~hints_first;
pdiff->x =
(hints_start & do_x ?
pseg->pt.x - pcis->unmoved_end.x : 0);
pdiff->y =
(hints_start & do_y ?
pseg->pt.y - pcis->unmoved_end.y : 0);
}
add_hint_diff(&pseg->pt, diff2);
apply_final_hint(pseg, &diff2);
add_hint_diff(&psub->pt, *pdiff);
/*
* Now align the initial and final points, to deal with hint
* replacement.
*/
diff2.x = psub->pt.x - pseg->pt.x;
diff2.y = psub->pt.y - pseg->pt.y;
if (diff2.x || diff2.y) {
/* Force the points to coincide. */
pseg->pt = psub->pt;
apply_final_hint(pseg, &diff2);
}
} else {
int hints_close =
line_hints(pcis, &pcis->unmoved_end, &pcis->unmoved_start);
hints_close &= ~(hints | hints_first);
if_debug3('y', "[y]closing open, hints=%d, hints_close=%d, hints_first=%d\n",
hints, hints_close, hints_first);
if (hints_close) {
apply_hints_at(pcis, hints_close, &pseg->pt, pdiff);
apply_final_hint(pseg, pdiff);
apply_hints_at(pcis, hints_close, &psub->pt, pdiff);
} else
pdiff->x = pdiff->y = 0;
}
if (pfirst->type == s_curve)
adjust_curve_start((curve_segment *) pfirst, pdiff);
}
/*
* Apply hints along a subpath. If closing is true, consider the subpath
* closed; if not, we may add more to the subpath later. In the latter case,
* don't do anything if the subpath is closed, because we already applied
* the hints.
*/
void
type1_do_apply_path_hints(gs_type1_state * pcis, bool closing, gx_path * ppath)
{
segment *pseg = pcis->hint_next;
segment *pnext;
subpath *const psub = ppath->current_subpath;
/*
* hints holds the set of hints that have already been applied (if
* applicable) to pseg->pt, and hence should not be applied again.
*/
int hints = pcis->hints_pending;
gs_fixed_point diff;
/*
* Since unknown OtherSubrs call apply_path_hints before returning
* to the client, and since OtherSubrs may be invoked before the
* [h]sbw is seen, it's possible that init_done < 0, i.e., the path
* and hint members of the state haven't been set up yet. In this
* case, we know there are no relevant hints.
*/
if (pcis->init_done < 0 || !(pcis->fh.use_x_hints | pcis->fh.use_y_hints))
return;
if (pseg == 0) {
/* Start at the beginning of the subpath. */
if (psub == 0)
return;
if (psub->is_closed && !closing)
return;
pseg = (segment *) psub;
if (pseg->next == 0)
return;
hints = 0;
pcis->unmoved_start = psub->pt;
pcis->unmoved_end = psub->pt;
} else
hints = pcis->hints_pending;
diff.x = diff.y = 0;
for (; (pnext = pseg->next) != 0; pseg = pnext) {
int hints_next;
/*
* Apply hints to the end of the previous segment (pseg)
* and the beginning of this one (pnext).
*/
gs_fixed_point dseg;
switch (pnext->type) {
case s_curve:{
curve_segment *const pnext_curve = (curve_segment *) pnext;
int hints_first =
line_hints(pcis, &pcis->unmoved_end,
&pnext_curve->p1) & ~hints;
gs_fixed_point diff2;
if (pseg == (segment *) psub)
pcis->hints_initial = hints_first;
if (hints_first)
apply_hints_at(pcis, hints_first, &pseg->pt, &dseg);
else
dseg.x = dseg.y = 0;
diff2.x = pseg->pt.x - pcis->unmoved_end.x;
diff2.y = pseg->pt.y - pcis->unmoved_end.y;
hints_next = line_hints(pcis, &pnext_curve->p2, &pnext->pt);
adjust_curve_start(pnext_curve, &diff2);
if (hints_next) {
apply_hints_at(pcis, hints_next, &pnext_curve->p2, &diff);
pcis->unmoved_end = pnext->pt;
add_hint_diff(&pnext->pt, diff);
} else
pcis->unmoved_end = pnext->pt;
break;
}
case s_line_close:
/* Undo any initial hints propagated to the end. */
pnext->pt = pcis->unmoved_start;
default: /* s_line, s_line_close */
if (line_is_null(pnext->pt, pcis->unmoved_end)) {
/* This is a null line, just move it. */
hints_next = hints;
dseg.x = dseg.y = 0; /* don't move p2 again */
} else {
hints_next =
line_hints(pcis, &pcis->unmoved_end, &pnext->pt);
if (hints_next & ~hints)
apply_hints_at(pcis, hints_next & ~hints,
&pseg->pt, &dseg);
else
dseg.x = dseg.y = 0;
}
if (pseg == (segment *) psub)
pcis->hints_initial = hints_next;
pcis->unmoved_end = pnext->pt;
if (hints_next)
apply_hints_at(pcis, hints_next, &pnext->pt, NULL);
}
if (pseg->type == s_curve)
adjust_curve_end((curve_segment *) pseg, &dseg);
hints = hints_next;
}
if (closing) {
apply_wrapped_hints(pcis, psub, pseg, hints, &diff);
pcis->hint_next = 0;
pcis->hints_pending = 0;
} else {
pcis->hint_next = pseg;
pcis->hints_pending = hints;
}
}
void
type1_apply_path_hints(gs_type1_state * pcis, bool closing, gx_path * ppath)
{
if (ppath->segments != 0)
type1_do_apply_path_hints(pcis, closing, ppath);
else {
/* We compute glyph bbox without hinting */
}
}
/* ------ Individual hints ------ */
private const stem_hint *search_hints(stem_hint_table *, fixed);
/*
* Adjust a point according to the relevant hints.
* dx or dy is > 0 for the upper edge, < 0 for the lower.
* The caller is responsible for checking use_hstem_hints or use_vstem_hints
* and not calling the find_xxx_hints routine if this is false.
* Note that if use_x/y_hints is false, no entries ever get made
* in the stem hint tables, so these routines will not get called.
*/
private void
apply_vstem_hints(gs_type1_state * pcis, int dy, gs_fixed_point * ppt)
{
fixed *pv = (pcis->fh.axes_swapped ? &ppt->y : &ppt->x);
const stem_hint *ph = search_hints(&pcis->vstem_hints, *pv);
if (ph != 0) {
if_debug3('Y', "[Y]use vstem %d: %1.4f (%s)",
(int)(ph - &pcis->vstem_hints.data[0]),
fixed2float(*pv),
(dy == 0 ? "?!" : dy > 0 ? "upper" : "lower"));
#ifdef DEBUG
if (dy == 0) {
lprintf("dy == 0 in apply_vstem_hints!\n");
return;
}
#endif
*pv += (dy > 0 ? ph->dv1 : ph->dv0);
if_debug1('Y', " -> %1.4f\n", fixed2float(*pv));
}
}
private void
apply_hstem_hints(gs_type1_state * pcis, int dx, gs_fixed_point * ppt)
{
fixed *pv = (pcis->fh.axes_swapped ? &ppt->x : &ppt->y);
const stem_hint *ph = search_hints(&pcis->hstem_hints, *pv);
if (ph != 0) {
if_debug3('Y', "[Y]use hstem %d: %1.4f (%s)",
(int)(ph - &pcis->hstem_hints.data[0]),
fixed2float(*pv),
(dx == 0 ? "?!" : dx > 0 ? "upper" : "lower"));
#ifdef DEBUG
if (dx == 0) {
lprintf("dx == 0 in apply_vstem_hints!\n");
return;
}
#endif
*pv += (dx > 0 ? ph->dv1 : ph->dv0);
if_debug1('Y', " -> %1.4f\n", fixed2float(*pv));
}
}
/* Search one hint table for an adjustment. */
private const stem_hint *
search_hints(stem_hint_table * psht, fixed v)
{
const stem_hint *table = &psht->data[0];
const stem_hint *ph = table + psht->current;
if (v >= ph->v0 && v <= ph->v1 && ph->active)
return ph;
/* We don't bother with binary or even up/down search, */
/* because there won't be very many hints. */
for (ph = &table[psht->count]; --ph >= table;)
if (v >= ph->v0 && v <= ph->v1 && ph->active) {
psht->current = ph - table;
return ph;
}
return 0;
}
#endif
|