1/****************************************************************************
2 *
3 * afloader.c
4 *
5 * Auto-fitter glyph loading routines (body).
6 *
7 * Copyright (C) 2003-2023 by
8 * David Turner, Robert Wilhelm, and Werner Lemberg.
9 *
10 * This file is part of the FreeType project, and may only be used,
11 * modified, and distributed under the terms of the FreeType project
12 * license, LICENSE.TXT. By continuing to use, modify, or distribute
13 * this file you indicate that you have read the license and
14 * understand and accept it fully.
15 *
16 */
17
18
19#include "afglobal.h"
20#include "afloader.h"
21#include "afhints.h"
22#include "aferrors.h"
23#include "afmodule.h"
24
25#include <freetype/internal/ftcalc.h>
26
27
28 /* Initialize glyph loader. */
29
30 FT_LOCAL_DEF( void )
31 af_loader_init( AF_Loader loader,
32 AF_GlyphHints hints )
33 {
34 FT_ZERO( loader );
35
36 loader->hints = hints;
37 }
38
39
40 /* Reset glyph loader and compute globals if necessary. */
41
42 FT_LOCAL_DEF( FT_Error )
43 af_loader_reset( AF_Loader loader,
44 AF_Module module,
45 FT_Face face )
46 {
47 FT_Error error = FT_Err_Ok;
48
49
50 loader->face = face;
51 loader->globals = (AF_FaceGlobals)face->autohint.data;
52
53 if ( !loader->globals )
54 {
55 error = af_face_globals_new( face, &loader->globals, module );
56 if ( !error )
57 {
58 face->autohint.data = (FT_Pointer)loader->globals;
59 face->autohint.finalizer = af_face_globals_free;
60 }
61 }
62
63 return error;
64 }
65
66
67 /* Finalize glyph loader. */
68
69 FT_LOCAL_DEF( void )
70 af_loader_done( AF_Loader loader )
71 {
72 loader->face = NULL;
73 loader->globals = NULL;
74 loader->hints = NULL;
75 }
76
77
78#define af_intToFixed( i ) \
79 ( (FT_Fixed)( (FT_UInt32)(i) << 16 ) )
80#define af_fixedToInt( x ) \
81 ( (FT_Short)( ( (FT_UInt32)(x) + 0x8000U ) >> 16 ) )
82#define af_floatToFixed( f ) \
83 ( (FT_Fixed)( (f) * 65536.0 + 0.5 ) )
84
85
86 static FT_Error
87 af_loader_embolden_glyph_in_slot( AF_Loader loader,
88 FT_Face face,
89 AF_StyleMetrics style_metrics )
90 {
91 FT_Error error = FT_Err_Ok;
92
93 FT_GlyphSlot slot = face->glyph;
94 AF_FaceGlobals globals = loader->globals;
95 AF_WritingSystemClass writing_system_class;
96
97 FT_Size_Metrics* size_metrics = &face->size->internal->autohint_metrics;
98
99 FT_Pos stdVW = 0;
100 FT_Pos stdHW = 0;
101
102 FT_Bool size_changed = size_metrics->x_ppem !=
103 globals->stem_darkening_for_ppem;
104
105 FT_Fixed em_size = af_intToFixed( face->units_per_EM );
106
107 FT_Matrix scale_down_matrix = { 0x10000L, 0, 0, 0x10000L };
108
109
110 /* Skip stem darkening for broken fonts. */
111 if ( !face->units_per_EM )
112 {
113 error = FT_ERR( Corrupted_Font_Header );
114 goto Exit;
115 }
116
117 /*
118 * We depend on the writing system (script analyzers) to supply
119 * standard widths for the script of the glyph we are looking at. If
120 * it can't deliver, stem darkening is disabled.
121 */
122 writing_system_class =
123 af_writing_system_classes[style_metrics->style_class->writing_system];
124
125 if ( writing_system_class->style_metrics_getstdw )
126 writing_system_class->style_metrics_getstdw( style_metrics,
127 &stdHW,
128 &stdVW );
129 else
130 {
131 error = FT_ERR( Unimplemented_Feature );
132 goto Exit;
133 }
134
135 if ( size_changed ||
136 ( stdVW > 0 && stdVW != globals->standard_vertical_width ) )
137 {
138 FT_Fixed darken_by_font_units_x, darken_x;
139
140
141 darken_by_font_units_x =
142 af_loader_compute_darkening( loader,
143 face,
144 stdVW ) ;
145 darken_x = FT_MulFix( darken_by_font_units_x,
146 size_metrics->x_scale );
147
148 globals->standard_vertical_width = stdVW;
149 globals->stem_darkening_for_ppem = size_metrics->x_ppem;
150 globals->darken_x = af_fixedToInt( darken_x );
151 }
152
153 if ( size_changed ||
154 ( stdHW > 0 && stdHW != globals->standard_horizontal_width ) )
155 {
156 FT_Fixed darken_by_font_units_y, darken_y;
157
158
159 darken_by_font_units_y =
160 af_loader_compute_darkening( loader,
161 face,
162 stdHW ) ;
163 darken_y = FT_MulFix( darken_by_font_units_y,
164 size_metrics->y_scale );
165
166 globals->standard_horizontal_width = stdHW;
167 globals->stem_darkening_for_ppem = size_metrics->x_ppem;
168 globals->darken_y = af_fixedToInt( darken_y );
169
170 /*
171 * Scale outlines down on the Y-axis to keep them inside their blue
172 * zones. The stronger the emboldening, the stronger the downscaling
173 * (plus heuristical padding to prevent outlines still falling out
174 * their zones due to rounding).
175 *
176 * Reason: `FT_Outline_Embolden' works by shifting the rightmost
177 * points of stems farther to the right, and topmost points farther
178 * up. This positions points on the Y-axis outside their
179 * pre-computed blue zones and leads to distortion when applying the
180 * hints in the code further below. Code outside this emboldening
181 * block doesn't know we are presenting it with modified outlines the
182 * analyzer didn't see!
183 *
184 * An unfortunate side effect of downscaling is that the emboldening
185 * effect is slightly decreased. The loss becomes more pronounced
186 * versus the CFF driver at smaller sizes, e.g., at 9ppem and below.
187 */
188 globals->scale_down_factor =
189 FT_DivFix( em_size - ( darken_by_font_units_y + af_intToFixed( 8 ) ),
190 em_size );
191 }
192
193 FT_Outline_EmboldenXY( &slot->outline,
194 globals->darken_x,
195 globals->darken_y );
196
197 scale_down_matrix.yy = globals->scale_down_factor;
198 FT_Outline_Transform( &slot->outline, &scale_down_matrix );
199
200 Exit:
201 return error;
202 }
203
204
205 /* Load the glyph at index into the current slot of a face and hint it. */
206
207 FT_LOCAL_DEF( FT_Error )
208 af_loader_load_glyph( AF_Loader loader,
209 AF_Module module,
210 FT_Face face,
211 FT_UInt glyph_index,
212 FT_Int32 load_flags )
213 {
214 FT_Error error;
215
216 FT_Size size = face->size;
217 FT_Size_Internal size_internal = size->internal;
218 FT_GlyphSlot slot = face->glyph;
219 FT_Slot_Internal slot_internal = slot->internal;
220 FT_GlyphLoader gloader = slot_internal->loader;
221
222 AF_GlyphHints hints = loader->hints;
223 AF_ScalerRec scaler;
224 AF_StyleMetrics style_metrics;
225 FT_UInt style_options = AF_STYLE_NONE_DFLT;
226 AF_StyleClass style_class;
227 AF_WritingSystemClass writing_system_class;
228
229
230 FT_ZERO( &scaler );
231
232 if ( !size_internal->autohint_metrics.x_scale ||
233 size_internal->autohint_mode != FT_LOAD_TARGET_MODE( load_flags ) )
234 {
235 /* switching between hinting modes usually means different scaling */
236 /* values; this later on enforces recomputation of everything */
237 /* related to the current size */
238
239 size_internal->autohint_mode = FT_LOAD_TARGET_MODE( load_flags );
240 size_internal->autohint_metrics = size->metrics;
241
242#ifdef AF_CONFIG_OPTION_TT_SIZE_METRICS
243 {
244 FT_Size_Metrics* size_metrics = &size_internal->autohint_metrics;
245
246
247 /* set metrics to integer values and adjust scaling accordingly; */
248 /* this is the same setup as with TrueType fonts, cf. function */
249 /* `tt_size_reset' in file `ttobjs.c' */
250 size_metrics->ascender = FT_PIX_ROUND(
251 FT_MulFix( face->ascender,
252 size_metrics->y_scale ) );
253 size_metrics->descender = FT_PIX_ROUND(
254 FT_MulFix( face->descender,
255 size_metrics->y_scale ) );
256 size_metrics->height = FT_PIX_ROUND(
257 FT_MulFix( face->height,
258 size_metrics->y_scale ) );
259
260 size_metrics->x_scale = FT_DivFix( size_metrics->x_ppem << 6,
261 face->units_per_EM );
262 size_metrics->y_scale = FT_DivFix( size_metrics->y_ppem << 6,
263 face->units_per_EM );
264 size_metrics->max_advance = FT_PIX_ROUND(
265 FT_MulFix( face->max_advance_width,
266 size_metrics->x_scale ) );
267 }
268#endif /* AF_CONFIG_OPTION_TT_SIZE_METRICS */
269 }
270
271 /*
272 * TODO: This code currently doesn't support fractional advance widths,
273 * i.e., placing hinted glyphs at anything other than integer
274 * x-positions. This is only relevant for the warper code, which
275 * scales and shifts glyphs to optimize blackness of stems (hinting on
276 * the x-axis by nature places things on pixel integers, hinting on the
277 * y-axis only, i.e., LIGHT mode, doesn't touch the x-axis). The delta
278 * values of the scaler would need to be adjusted.
279 */
280 scaler.face = face;
281 scaler.x_scale = size_internal->autohint_metrics.x_scale;
282 scaler.x_delta = 0;
283 scaler.y_scale = size_internal->autohint_metrics.y_scale;
284 scaler.y_delta = 0;
285
286 scaler.render_mode = FT_LOAD_TARGET_MODE( load_flags );
287 scaler.flags = 0;
288
289 /* note that the fallback style can't be changed anymore */
290 /* after the first call of `af_loader_load_glyph' */
291 error = af_loader_reset( loader, module, face );
292 if ( error )
293 goto Exit;
294
295 /*
296 * Glyphs (really code points) are assigned to scripts. Script
297 * analysis is done lazily: For each glyph that passes through here,
298 * the corresponding script analyzer is called, but returns immediately
299 * if it has been run already.
300 */
301 error = af_face_globals_get_metrics( loader->globals, glyph_index,
302 style_options, &style_metrics );
303 if ( error )
304 goto Exit;
305
306 style_class = style_metrics->style_class;
307 writing_system_class =
308 af_writing_system_classes[style_class->writing_system];
309
310 loader->metrics = style_metrics;
311
312 if ( writing_system_class->style_metrics_scale )
313 writing_system_class->style_metrics_scale( style_metrics, &scaler );
314 else
315 style_metrics->scaler = scaler;
316
317 if ( writing_system_class->style_hints_init )
318 {
319 error = writing_system_class->style_hints_init( hints,
320 style_metrics );
321 if ( error )
322 goto Exit;
323 }
324
325 /*
326 * Do the main work of `af_loader_load_glyph'. Note that we never have
327 * to deal with composite glyphs as those get loaded into
328 * FT_GLYPH_FORMAT_OUTLINE by the recursed `FT_Load_Glyph' function.
329 * In the rare cases where FT_LOAD_NO_RECURSE is set, it implies
330 * FT_LOAD_NO_SCALE and as such the auto-hinter is never called.
331 */
332 load_flags |= FT_LOAD_NO_SCALE |
333 FT_LOAD_IGNORE_TRANSFORM |
334 FT_LOAD_LINEAR_DESIGN;
335 load_flags &= ~FT_LOAD_RENDER;
336
337 error = FT_Load_Glyph( face, glyph_index, load_flags );
338 if ( error )
339 goto Exit;
340
341 /*
342 * Apply stem darkening (emboldening) here before hints are applied to
343 * the outline. Glyphs are scaled down proportionally to the
344 * emboldening so that curve points don't fall outside their
345 * precomputed blue zones.
346 *
347 * Any emboldening done by the font driver (e.g., the CFF driver)
348 * doesn't reach here because the autohinter loads the unprocessed
349 * glyphs in font units for analysis (functions `af_*_metrics_init_*')
350 * and then above to prepare it for the rasterizers by itself,
351 * independently of the font driver. So emboldening must be done here,
352 * within the autohinter.
353 *
354 * All glyphs to be autohinted pass through here one by one. The
355 * standard widths can therefore change from one glyph to the next,
356 * depending on what script a glyph is assigned to (each script has its
357 * own set of standard widths and other metrics). The darkening amount
358 * must therefore be recomputed for each size and
359 * `standard_{vertical,horizontal}_width' change.
360 *
361 * Ignore errors and carry on without emboldening.
362 *
363 */
364
365 /* stem darkening only works well in `light' mode */
366 if ( scaler.render_mode == FT_RENDER_MODE_LIGHT &&
367 ( !face->internal->no_stem_darkening ||
368 ( face->internal->no_stem_darkening < 0 &&
369 !module->no_stem_darkening ) ) )
370 af_loader_embolden_glyph_in_slot( loader, face, style_metrics );
371
372 loader->transformed = slot_internal->glyph_transformed;
373 if ( loader->transformed )
374 {
375 FT_Matrix inverse;
376
377
378 loader->trans_matrix = slot_internal->glyph_matrix;
379 loader->trans_delta = slot_internal->glyph_delta;
380
381 inverse = loader->trans_matrix;
382 if ( !FT_Matrix_Invert( &inverse ) )
383 FT_Vector_Transform( &loader->trans_delta, &inverse );
384 }
385
386 switch ( slot->format )
387 {
388 case FT_GLYPH_FORMAT_OUTLINE:
389 /* translate the loaded glyph when an internal transform is needed */
390 if ( loader->transformed )
391 FT_Outline_Translate( &slot->outline,
392 loader->trans_delta.x,
393 loader->trans_delta.y );
394
395 /* compute original horizontal phantom points */
396 /* (and ignore vertical ones) */
397 loader->pp1.x = hints->x_delta;
398 loader->pp1.y = hints->y_delta;
399 loader->pp2.x = FT_MulFix( slot->metrics.horiAdvance,
400 hints->x_scale ) + hints->x_delta;
401 loader->pp2.y = hints->y_delta;
402
403 /* be sure to check for spacing glyphs */
404 if ( slot->outline.n_points == 0 )
405 goto Hint_Metrics;
406
407 /* now load the slot image into the auto-outline */
408 /* and run the automatic hinting process */
409 if ( writing_system_class->style_hints_apply )
410 {
411 error = writing_system_class->style_hints_apply(
412 glyph_index,
413 hints,
414 &gloader->base.outline,
415 style_metrics );
416 if ( error )
417 goto Exit;
418 }
419
420 /* we now need to adjust the metrics according to the change in */
421 /* width/positioning that occurred during the hinting process */
422 if ( scaler.render_mode != FT_RENDER_MODE_LIGHT )
423 {
424 AF_AxisHints axis = &hints->axis[AF_DIMENSION_HORZ];
425
426
427 if ( axis->num_edges > 1 && AF_HINTS_DO_ADVANCE( hints ) )
428 {
429 AF_Edge edge1 = axis->edges; /* leftmost edge */
430 AF_Edge edge2 = edge1 +
431 axis->num_edges - 1; /* rightmost edge */
432
433 FT_Pos old_rsb = loader->pp2.x - edge2->opos;
434 /* loader->pp1.x is always zero at this point of time */
435 FT_Pos old_lsb = edge1->opos; /* - loader->pp1.x */
436 FT_Pos new_lsb = edge1->pos;
437
438 /* remember unhinted values to later account */
439 /* for rounding errors */
440 FT_Pos pp1x_uh = new_lsb - old_lsb;
441 FT_Pos pp2x_uh = edge2->pos + old_rsb;
442
443
444 /* prefer too much space over too little space */
445 /* for very small sizes */
446
447 if ( old_lsb < 24 )
448 pp1x_uh -= 8;
449
450 if ( old_rsb < 24 )
451 pp2x_uh += 8;
452
453 loader->pp1.x = FT_PIX_ROUND( pp1x_uh );
454 loader->pp2.x = FT_PIX_ROUND( pp2x_uh );
455
456 if ( loader->pp1.x >= new_lsb && old_lsb > 0 )
457 loader->pp1.x -= 64;
458
459 if ( loader->pp2.x <= edge2->pos && old_rsb > 0 )
460 loader->pp2.x += 64;
461
462 slot->lsb_delta = loader->pp1.x - pp1x_uh;
463 slot->rsb_delta = loader->pp2.x - pp2x_uh;
464 }
465 else
466 {
467 FT_Pos pp1x = loader->pp1.x;
468 FT_Pos pp2x = loader->pp2.x;
469
470
471 loader->pp1.x = FT_PIX_ROUND( pp1x );
472 loader->pp2.x = FT_PIX_ROUND( pp2x );
473
474 slot->lsb_delta = loader->pp1.x - pp1x;
475 slot->rsb_delta = loader->pp2.x - pp2x;
476 }
477 }
478 /* `light' mode uses integer advance widths */
479 /* but sets `lsb_delta' and `rsb_delta' */
480 else
481 {
482 FT_Pos pp1x = loader->pp1.x;
483 FT_Pos pp2x = loader->pp2.x;
484
485
486 loader->pp1.x = FT_PIX_ROUND( pp1x );
487 loader->pp2.x = FT_PIX_ROUND( pp2x );
488
489 slot->lsb_delta = loader->pp1.x - pp1x;
490 slot->rsb_delta = loader->pp2.x - pp2x;
491 }
492
493 break;
494
495 default:
496 /* we don't support other formats (yet?) */
497 error = FT_THROW( Unimplemented_Feature );
498 }
499
500 Hint_Metrics:
501 {
502 FT_BBox bbox;
503 FT_Vector vvector;
504
505
506 vvector.x = slot->metrics.vertBearingX - slot->metrics.horiBearingX;
507 vvector.y = slot->metrics.vertBearingY - slot->metrics.horiBearingY;
508 vvector.x = FT_MulFix( vvector.x, style_metrics->scaler.x_scale );
509 vvector.y = FT_MulFix( vvector.y, style_metrics->scaler.y_scale );
510
511 /* transform the hinted outline if needed */
512 if ( loader->transformed )
513 {
514 FT_Outline_Transform( &gloader->base.outline, &loader->trans_matrix );
515 FT_Vector_Transform( &vvector, &loader->trans_matrix );
516 }
517
518 /* we must translate our final outline by -pp1.x and compute */
519 /* the new metrics */
520 if ( loader->pp1.x )
521 FT_Outline_Translate( &gloader->base.outline, -loader->pp1.x, 0 );
522
523 FT_Outline_Get_CBox( &gloader->base.outline, &bbox );
524
525 bbox.xMin = FT_PIX_FLOOR( bbox.xMin );
526 bbox.yMin = FT_PIX_FLOOR( bbox.yMin );
527 bbox.xMax = FT_PIX_CEIL( bbox.xMax );
528 bbox.yMax = FT_PIX_CEIL( bbox.yMax );
529
530 slot->metrics.width = bbox.xMax - bbox.xMin;
531 slot->metrics.height = bbox.yMax - bbox.yMin;
532 slot->metrics.horiBearingX = bbox.xMin;
533 slot->metrics.horiBearingY = bbox.yMax;
534
535 slot->metrics.vertBearingX = FT_PIX_FLOOR( bbox.xMin + vvector.x );
536 slot->metrics.vertBearingY = FT_PIX_FLOOR( bbox.yMax + vvector.y );
537
538 /* for mono-width fonts (like Andale, Courier, etc.) we need */
539 /* to keep the original rounded advance width; ditto for */
540 /* digits if all have the same advance width */
541 if ( scaler.render_mode != FT_RENDER_MODE_LIGHT &&
542 ( FT_IS_FIXED_WIDTH( slot->face ) ||
543 ( af_face_globals_is_digit( loader->globals, glyph_index ) &&
544 style_metrics->digits_have_same_width ) ) )
545 {
546 slot->metrics.horiAdvance =
547 FT_MulFix( slot->metrics.horiAdvance,
548 style_metrics->scaler.x_scale );
549
550 /* Set delta values to 0. Otherwise code that uses them is */
551 /* going to ruin the fixed advance width. */
552 slot->lsb_delta = 0;
553 slot->rsb_delta = 0;
554 }
555 else
556 {
557 /* non-spacing glyphs must stay as-is */
558 if ( slot->metrics.horiAdvance )
559 slot->metrics.horiAdvance = loader->pp2.x - loader->pp1.x;
560 }
561
562 slot->metrics.vertAdvance = FT_MulFix( slot->metrics.vertAdvance,
563 style_metrics->scaler.y_scale );
564
565 slot->metrics.horiAdvance = FT_PIX_ROUND( slot->metrics.horiAdvance );
566 slot->metrics.vertAdvance = FT_PIX_ROUND( slot->metrics.vertAdvance );
567
568 slot->format = FT_GLYPH_FORMAT_OUTLINE;
569 }
570
571 Exit:
572 return error;
573 }
574
575
576 /*
577 * Compute amount of font units the face should be emboldened by, in
578 * analogy to the CFF driver's `cf2_computeDarkening' function. See there
579 * for details of the algorithm.
580 *
581 * XXX: Currently a crude adaption of the original algorithm. Do better?
582 */
583 FT_LOCAL_DEF( FT_Fixed )
584 af_loader_compute_darkening( AF_Loader loader,
585 FT_Face face,
586 FT_Pos standard_width )
587 {
588 AF_Module module = loader->globals->module;
589
590 FT_UShort units_per_EM;
591 FT_Fixed ppem, em_ratio;
592 FT_Fixed stem_width, stem_width_per_1000, scaled_stem, darken_amount;
593 FT_Int log_base_2;
594 FT_Int x1, y1, x2, y2, x3, y3, x4, y4;
595
596
597 ppem = FT_MAX( af_intToFixed( 4 ),
598 af_intToFixed( face->size->metrics.x_ppem ) );
599 units_per_EM = face->units_per_EM;
600
601 em_ratio = FT_DivFix( af_intToFixed( 1000 ),
602 af_intToFixed ( units_per_EM ) );
603 if ( em_ratio < af_floatToFixed( .01 ) )
604 {
605 /* If something goes wrong, don't embolden. */
606 return 0;
607 }
608
609 x1 = module->darken_params[0];
610 y1 = module->darken_params[1];
611 x2 = module->darken_params[2];
612 y2 = module->darken_params[3];
613 x3 = module->darken_params[4];
614 y3 = module->darken_params[5];
615 x4 = module->darken_params[6];
616 y4 = module->darken_params[7];
617
618 if ( standard_width <= 0 )
619 {
620 stem_width = af_intToFixed( 75 ); /* taken from cf2font.c */
621 stem_width_per_1000 = stem_width;
622 }
623 else
624 {
625 stem_width = af_intToFixed( standard_width );
626 stem_width_per_1000 = FT_MulFix( stem_width, em_ratio );
627 }
628
629 log_base_2 = FT_MSB( (FT_UInt32)stem_width_per_1000 ) +
630 FT_MSB( (FT_UInt32)ppem );
631
632 if ( log_base_2 >= 46 )
633 {
634 /* possible overflow */
635 scaled_stem = af_intToFixed( x4 );
636 }
637 else
638 scaled_stem = FT_MulFix( stem_width_per_1000, ppem );
639
640 /* now apply the darkening parameters */
641 if ( scaled_stem < af_intToFixed( x1 ) )
642 darken_amount = FT_DivFix( af_intToFixed( y1 ), ppem );
643
644 else if ( scaled_stem < af_intToFixed( x2 ) )
645 {
646 FT_Int xdelta = x2 - x1;
647 FT_Int ydelta = y2 - y1;
648 FT_Int x = stem_width_per_1000 -
649 FT_DivFix( af_intToFixed( x1 ), ppem );
650
651
652 if ( !xdelta )
653 goto Try_x3;
654
655 darken_amount = FT_MulDiv( x, ydelta, xdelta ) +
656 FT_DivFix( af_intToFixed( y1 ), ppem );
657 }
658
659 else if ( scaled_stem < af_intToFixed( x3 ) )
660 {
661 Try_x3:
662 {
663 FT_Int xdelta = x3 - x2;
664 FT_Int ydelta = y3 - y2;
665 FT_Int x = stem_width_per_1000 -
666 FT_DivFix( af_intToFixed( x2 ), ppem );
667
668
669 if ( !xdelta )
670 goto Try_x4;
671
672 darken_amount = FT_MulDiv( x, ydelta, xdelta ) +
673 FT_DivFix( af_intToFixed( y2 ), ppem );
674 }
675 }
676
677 else if ( scaled_stem < af_intToFixed( x4 ) )
678 {
679 Try_x4:
680 {
681 FT_Int xdelta = x4 - x3;
682 FT_Int ydelta = y4 - y3;
683 FT_Int x = stem_width_per_1000 -
684 FT_DivFix( af_intToFixed( x3 ), ppem );
685
686
687 if ( !xdelta )
688 goto Use_y4;
689
690 darken_amount = FT_MulDiv( x, ydelta, xdelta ) +
691 FT_DivFix( af_intToFixed( y3 ), ppem );
692 }
693 }
694
695 else
696 {
697 Use_y4:
698 darken_amount = FT_DivFix( af_intToFixed( y4 ), ppem );
699 }
700
701 /* Convert darken_amount from per 1000 em to true character space. */
702 return FT_DivFix( darken_amount, em_ratio );
703 }
704
705
706/* END */
707