1 /* Copyright (C) 1990, 1995, 1997, 1998, 1999 artofcode LLC. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify it
4 under the terms of the GNU General Public License as published by the
5 Free Software Foundation; either version 2 of the License, or (at your
6 option) any later version.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program; if not, write to the Free Software Foundation, Inc.,
15 59 Temple Place, Suite 330, Boston, MA, 02111-1307.
16
17 */
18
19 /*$Id: gxhint2.c,v 1.2.6.1.2.1 2003/01/17 00:49:03 giles Exp $ */
20 /* Character level hints for Type 1 fonts. */
21 #include "memory_.h"
22 #include "gx.h"
23 #include "gserrors.h"
24 #include "gxarith.h"
25 #include "gxfixed.h"
26 #include "gxmatrix.h"
27 #include "gxfont.h"
28 #include "gxfont1.h"
29 #include "gxtype1.h"
30
31 /* Define the tolerance for testing whether a point is in a zone, */
32 /* in device pixels. (Maybe this should be variable?) */
33 #define STEM_TOLERANCE float2fixed(0.05)
34
35 /* Forward references */
36
37 private stem_hint *type1_stem(P4(const gs_type1_state *, stem_hint_table *, fixed, fixed));
38 private fixed find_snap(P3(fixed, const stem_snap_table *, const pixel_scale *));
39 private alignment_zone *find_zone(P3(gs_type1_state *, fixed, fixed));
40
41 /* Reset the stem hints. */
42 void
reset_stem_hints(gs_type1_state * pcis)43 reset_stem_hints(gs_type1_state * pcis)
44 {
45 pcis->hstem_hints.count = pcis->hstem_hints.replaced_count = 0;
46 pcis->vstem_hints.count = pcis->vstem_hints.replaced_count = 0;
47 update_stem_hints(pcis);
48 }
49
50 /* Prepare to replace the stem hints. */
51 private void
save_replaced_hints(stem_hint_table * psht)52 save_replaced_hints(stem_hint_table * psht)
53 {
54 int rep_count = min(psht->replaced_count + psht->count, max_stems);
55
56 memmove(&psht->data[max_stems - rep_count], &psht->data[0],
57 psht->count * sizeof(psht->data[0]));
58 psht->replaced_count = rep_count;
59 psht->count = psht->current = 0;
60 }
61 void
type1_replace_stem_hints(gs_type1_state * pcis)62 type1_replace_stem_hints(gs_type1_state * pcis)
63 {
64 if_debug2('y', "[y]saving hints: %d hstem, %d vstem\n",
65 pcis->hstem_hints.count, pcis->vstem_hints.count);
66 save_replaced_hints(&pcis->hstem_hints);
67 save_replaced_hints(&pcis->vstem_hints);
68 if_debug2('y', "[y]total saved hints: %d hstem, %d vstem\n",
69 pcis->hstem_hints.replaced_count,
70 pcis->vstem_hints.replaced_count);
71 }
72
73 /* Update the internal stem hint pointers after moving or copying the state. */
74 void
update_stem_hints(gs_type1_state * pcis)75 update_stem_hints(gs_type1_state * pcis)
76 {
77 pcis->hstem_hints.current = 0;
78 pcis->vstem_hints.current = 0;
79 }
80
81 /* ------ Add hints ------ */
82
83 #undef c_fixed
84 #define c_fixed(d, c) m_fixed(d, c, pcis->fc, max_coeff_bits)
85
86 #define if_debug_print_add_stem(chr, msg, psht, psh, c, dc, v, dv)\
87 if_debug10(chr, "%s %d/%d: %g,%g -> %g(%g)%g ; d = %g,%g\n",\
88 msg, (int)((psh) - &(psht)->data[0]), (psht)->count,\
89 fixed2float(c), fixed2float(dc),\
90 fixed2float(v), fixed2float(dv), fixed2float((v) + (dv)),\
91 fixed2float((psh)->dv0), fixed2float((psh)->dv1))
92
93 /* Compute and store the adjusted stem coordinates. */
94 private void
store_stem_deltas(const stem_hint_table * psht,stem_hint * psh,const pixel_scale * psp,fixed v,fixed dv,fixed adj_dv)95 store_stem_deltas(const stem_hint_table * psht, stem_hint * psh,
96 const pixel_scale * psp, fixed v, fixed dv, fixed adj_dv)
97 {
98 /*
99 * We want to align the stem so its edges fall on pixel boundaries
100 * (possibly "big pixel" boundaries if we are oversampling),
101 * but if hint replacement has occurred, we must shift edges in a
102 * consistent way. This is a real nuisance, but I don't see how
103 * to avoid it; if we don't do it, we get bizarre anomalies like
104 * disappearing stems.
105 */
106 const stem_hint *psh0 = 0;
107 const stem_hint *psh1 = 0;
108 int i;
109
110 /*
111 * If we ever had a hint with the same edge(s), align this one
112 * the same way.
113 */
114 for (i = max_stems - psht->replaced_count; i < max_stems; ++i) {
115 const stem_hint *ph = &psht->data[i];
116
117 if (ph == psh)
118 continue;
119 if (ph->v0 == psh->v0)
120 psh0 = ph;
121 if (ph->v1 == psh->v1)
122 psh1 = ph;
123 }
124 for (i = 0; i < psht->count; ++i) {
125 const stem_hint *ph = &psht->data[i];
126
127 if (ph == psh)
128 continue;
129 if (ph->v0 == psh->v0)
130 psh0 = ph;
131 if (ph->v1 == psh->v1)
132 psh1 = ph;
133 }
134 if (psh0 != 0) {
135 psh->dv0 = psh0->dv0;
136 if (psh1 != 0) { /* Both edges are determined. */
137 psh->dv1 = psh1->dv1;
138 } else { /* Only the lower edge is determined. */
139 psh->dv1 = psh->dv0 + adj_dv - dv;
140 }
141 } else if (psh1 != 0) { /* Only the upper edge is determined. */
142 psh->dv1 = psh1->dv1;
143 psh->dv0 = psh->dv1 + adj_dv - dv;
144 } else { /* Neither edge is determined. */
145 fixed diff2_dv = arith_rshift_1(adj_dv - dv);
146 fixed edge = v - diff2_dv;
147 fixed diff_v = scaled_rounded(edge, psp) - edge;
148
149 psh->dv0 = diff_v - diff2_dv;
150 psh->dv1 = diff_v + diff2_dv;
151 }
152 }
153
154 /*
155 * The Type 1 font format uses negative stem widths to indicate edge hints.
156 * We need to convert these into zero-width stem hints.
157 */
158 private void
detect_edge_hint(fixed * xy,fixed * dxy)159 detect_edge_hint(fixed *xy, fixed *dxy)
160 {
161 if (*dxy == -21) {
162 /* Bottom edge hint. */
163 *xy -= 21, *dxy = 0;
164 } else if (*dxy == -20) {
165 /* Top edge hint. */
166 *dxy = 0;
167 }
168 }
169
170 /* Add a horizontal stem hint. */
171 void
type1_do_hstem(gs_type1_state * pcis,fixed y,fixed dy,const gs_matrix_fixed * pmat)172 type1_do_hstem(gs_type1_state * pcis, fixed y, fixed dy,
173 const gs_matrix_fixed * pmat)
174 {
175 stem_hint *psh;
176 alignment_zone *pz;
177 const pixel_scale *psp;
178 fixed v, dv, adj_dv;
179 fixed vtop, vbot;
180
181 if (!pcis->fh.use_y_hints || !pmat->txy_fixed_valid)
182 return;
183 detect_edge_hint(&y, &dy);
184 y += pcis->lsb.y + pcis->adxy.y;
185 if (pcis->fh.axes_swapped) {
186 psp = &pcis->scale.x;
187 v = pcis->vs_offset.x + c_fixed(y, yx) + pmat->tx_fixed;
188 dv = c_fixed(dy, yx);
189 } else {
190 psp = &pcis->scale.y;
191 v = pcis->vs_offset.y + c_fixed(y, yy) + pmat->ty_fixed;
192 dv = c_fixed(dy, yy);
193 }
194 if (dy < 0)
195 vbot = v + dv, vtop = v;
196 else
197 vbot = v, vtop = v + dv;
198 if (dv < 0)
199 v += dv, dv = -dv;
200 psh = type1_stem(pcis, &pcis->hstem_hints, v, dv);
201 if (psh == 0)
202 return;
203 adj_dv = find_snap(dv, &pcis->fh.snap_h, psp);
204 pz = find_zone(pcis, vbot, vtop);
205 if (pz != 0) { /* Use the alignment zone to align the outer stem edge. */
206 int inverted =
207 (pcis->fh.axes_swapped ? pcis->fh.x_inverted : pcis->fh.y_inverted);
208 int adjust_v1 =
209 (inverted ? !pz->is_top_zone : pz->is_top_zone);
210 fixed flat_v = pz->flat;
211 fixed overshoot =
212 (pz->is_top_zone ? vtop - flat_v : flat_v - vbot);
213 fixed pos_over =
214 (inverted ? -overshoot : overshoot);
215 fixed ddv = adj_dv - dv;
216 fixed shift = scaled_rounded(flat_v, psp) - flat_v;
217
218 if (pos_over > 0) {
219 if (pos_over < pcis->fh.blue_shift || pcis->fh.suppress_overshoot) { /* Character is small, suppress overshoot. */
220 if_debug0('y', "[y]suppress overshoot\n");
221 if (pz->is_top_zone)
222 shift -= overshoot;
223 else
224 shift += overshoot;
225 } else if (pos_over < psp->unit) { /* Enforce overshoot. */
226 if_debug0('y', "[y]enforce overshoot\n");
227 if (overshoot < 0)
228 overshoot = -psp->unit - overshoot;
229 else
230 overshoot = psp->unit - overshoot;
231 if (pz->is_top_zone)
232 shift += overshoot;
233 else
234 shift -= overshoot;
235 }
236 }
237 if (adjust_v1)
238 psh->dv1 = shift, psh->dv0 = shift - ddv;
239 else
240 psh->dv0 = shift, psh->dv1 = shift + ddv;
241 if_debug2('y', "[y]flat_v = %g, overshoot = %g for:\n",
242 fixed2float(flat_v), fixed2float(overshoot));
243 } else { /* Align the stem so its edges fall on pixel boundaries. */
244 store_stem_deltas(&pcis->hstem_hints, psh, psp, v, dv, adj_dv);
245 }
246 if_debug_print_add_stem('y', "[y]hstem", &pcis->hstem_hints, psh,
247 y, dy, v, dv);
248 }
249
250 /* Add a vertical stem hint. */
251 void
type1_do_vstem(gs_type1_state * pcis,fixed x,fixed dx,const gs_matrix_fixed * pmat)252 type1_do_vstem(gs_type1_state * pcis, fixed x, fixed dx,
253 const gs_matrix_fixed * pmat)
254 {
255 stem_hint *psh;
256 const pixel_scale *psp;
257 fixed v, dv, adj_dv;
258
259 if (!pcis->fh.use_x_hints)
260 return;
261 detect_edge_hint(&x, &dx);
262 x += pcis->lsb.x + pcis->adxy.x;
263 if (pcis->fh.axes_swapped) {
264 psp = &pcis->scale.y;
265 v = pcis->vs_offset.y + c_fixed(x, xy) + pmat->ty_fixed;
266 dv = c_fixed(dx, xy);
267 } else {
268 psp = &pcis->scale.x;
269 v = pcis->vs_offset.x + c_fixed(x, xx) + pmat->tx_fixed;
270 dv = c_fixed(dx, xx);
271 }
272 if (dv < 0)
273 v += dv, dv = -dv;
274 psh = type1_stem(pcis, &pcis->vstem_hints, v, dv);
275 if (psh == 0)
276 return;
277 adj_dv = find_snap(dv, &pcis->fh.snap_v, psp);
278 if (pcis->pfont->data.ForceBold && adj_dv < psp->unit)
279 adj_dv = psp->unit;
280 /* Align the stem so its edges fall on pixel boundaries. */
281 store_stem_deltas(&pcis->vstem_hints, psh, psp, v, dv, adj_dv);
282 if_debug_print_add_stem('y', "[y]vstem", &pcis->vstem_hints, psh,
283 x, dx, v, dv);
284 }
285
286 /* Adjust the character center for a vstem3. */
287 /****** NEEDS UPDATING FOR SCALE ******/
288 void
type1_do_center_vstem(gs_type1_state * pcis,fixed x0,fixed dx,const gs_matrix_fixed * pmat)289 type1_do_center_vstem(gs_type1_state * pcis, fixed x0, fixed dx,
290 const gs_matrix_fixed * pmat)
291 {
292 fixed x1 = x0 + dx;
293 gs_fixed_point pt0, pt1, width;
294 fixed center, int_width;
295 fixed *psxy;
296
297 if (gs_point_transform2fixed(pmat, fixed2float(x0), 0.0, &pt0) < 0 ||
298 gs_point_transform2fixed(pmat, fixed2float(x1), 0.0, &pt1) < 0
299 ) { /* Punt. */
300 return;
301 }
302 width.x = pt0.x - pt1.x;
303 if (width.x < 0)
304 width.x = -width.x;
305 width.y = pt0.y - pt1.y;
306 if (width.y < 0)
307 width.y = -width.y;
308 if (width.y < float2fixed(0.05)) { /* Vertical on device */
309 center = arith_rshift_1(pt0.x + pt1.x);
310 int_width = fixed_rounded(width.x);
311 psxy = &pcis->vs_offset.x;
312 } else { /* Horizontal on device */
313 center = arith_rshift_1(pt0.y + pt1.y);
314 int_width = fixed_rounded(width.y);
315 psxy = &pcis->vs_offset.y;
316 }
317 if (int_width == fixed_0 || (int_width & fixed_1) == 0) { /* Odd width, center stem over pixel. */
318 *psxy = fixed_floor(center) + fixed_half - center;
319 } else { /* Even width, center stem between pixels. */
320 *psxy = fixed_rounded(center) - center;
321 }
322 /* We can't fix up the current point here, */
323 /* but we can fix up everything else. */
324 /****** TO BE COMPLETED ******/
325 }
326
327 /* Add a stem hint, keeping the table sorted. */
328 /* We know that d >= 0. */
329 /* Return the stem hint pointer, or 0 if the table is full. */
330 private stem_hint *
type1_stem(const gs_type1_state * pcis,stem_hint_table * psht,fixed v0,fixed d)331 type1_stem(const gs_type1_state * pcis, stem_hint_table * psht,
332 fixed v0, fixed d)
333 {
334 stem_hint *bot = &psht->data[0];
335 stem_hint *top = bot + psht->count;
336
337 if (psht->count >= max_stems)
338 return 0;
339 while (top > bot && v0 < top[-1].v0) {
340 *top = top[-1];
341 top--;
342 }
343 /* Add a little fuzz for insideness testing. */
344 top->v0 = v0 - STEM_TOLERANCE;
345 top->v1 = v0 + d + STEM_TOLERANCE;
346 top->index = pcis->hstem_hints.count + pcis->vstem_hints.count;
347 top->active = true;
348 psht->count++;
349 return top;
350 }
351
352 /* Compute the adjusted width of a stem. */
353 /* The value returned is always a multiple of scale.unit. */
354 private fixed
find_snap(fixed dv,const stem_snap_table * psst,const pixel_scale * pps)355 find_snap(fixed dv, const stem_snap_table * psst, const pixel_scale * pps)
356 { /* We aren't sure why a maximum difference of pps->half */
357 /* works better than pps->unit, but it does. */
358 #define max_snap_distance (pps->half)
359 fixed best = max_snap_distance;
360 fixed adj_dv;
361 int i;
362
363 for (i = 0; i < psst->count; i++) {
364 fixed diff = psst->data[i] - dv;
365
366 if (any_abs(diff) < any_abs(best)) {
367 if_debug3('Y', "[Y]possibly snap %g to [%d]%g\n",
368 fixed2float(dv), i,
369 fixed2float(psst->data[i]));
370 best = diff;
371 }
372 }
373 adj_dv = scaled_rounded((any_abs(best) < max_snap_distance ?
374 dv + best : dv),
375 pps);
376 if (adj_dv == 0)
377 adj_dv = pps->unit;
378 #ifdef DEBUG
379 if (adj_dv == dv)
380 if_debug1('Y', "[Y]no snap %g\n", fixed2float(dv));
381 else
382 if_debug2('Y', "[Y]snap %g to %g\n",
383 fixed2float(dv), fixed2float(adj_dv));
384 #endif
385 return adj_dv;
386 #undef max_snap_distance
387 }
388
389 /* Find the applicable alignment zone for a stem, if any. */
390 /* vbot and vtop are the bottom and top of the stem, */
391 /* but without interchanging if the y axis is inverted. */
392 private alignment_zone *
find_zone(gs_type1_state * pcis,fixed vbot,fixed vtop)393 find_zone(gs_type1_state * pcis, fixed vbot, fixed vtop)
394 {
395 alignment_zone *pz;
396
397 for (pz = &pcis->fh.a_zones[pcis->fh.a_zone_count];
398 --pz >= &pcis->fh.a_zones[0];
399 ) {
400 fixed v = (pz->is_top_zone ? vtop : vbot);
401
402 if (v >= pz->v0 && v <= pz->v1) {
403 if_debug2('Y', "[Y]stem crosses %s-zone %d\n",
404 (pz->is_top_zone ? "top" : "bottom"),
405 (int)(pz - &pcis->fh.a_zones[0]));
406 return pz;
407 }
408 }
409 return 0;
410 }
411