cf2hints.c revision 727dee178a392d20eb050d0c446f2fcc29058fa1
1/***************************************************************************/
2/*                                                                         */
3/*  cf2hints.c                                                             */
4/*                                                                         */
5/*    Adobe's code for handling CFF hints (body).                          */
6/*                                                                         */
7/*  Copyright 2007-2013 Adobe Systems Incorporated.                        */
8/*                                                                         */
9/*  This software, and all works of authorship, whether in source or       */
10/*  object code form as indicated by the copyright notice(s) included      */
11/*  herein (collectively, the "Work") is made available, and may only be   */
12/*  used, modified, and distributed under the FreeType Project License,    */
13/*  LICENSE.TXT.  Additionally, subject to the terms and conditions of the */
14/*  FreeType Project License, each contributor to the Work hereby grants   */
15/*  to any individual or legal entity exercising permissions granted by    */
16/*  the FreeType Project License and this section (hereafter, "You" or     */
17/*  "Your") a perpetual, worldwide, non-exclusive, no-charge,              */
18/*  royalty-free, irrevocable (except as stated in this section) patent    */
19/*  license to make, have made, use, offer to sell, sell, import, and      */
20/*  otherwise transfer the Work, where such license applies only to those  */
21/*  patent claims licensable by such contributor that are necessarily      */
22/*  infringed by their contribution(s) alone or by combination of their    */
23/*  contribution(s) with the Work to which such contribution(s) was        */
24/*  submitted.  If You institute patent litigation against any entity      */
25/*  (including a cross-claim or counterclaim in a lawsuit) alleging that   */
26/*  the Work or a contribution incorporated within the Work constitutes    */
27/*  direct or contributory patent infringement, then any patent licenses   */
28/*  granted to You under this License for that Work shall terminate as of  */
29/*  the date such litigation is filed.                                     */
30/*                                                                         */
31/*  By using, modifying, or distributing the Work you indicate that you    */
32/*  have read and understood the terms and conditions of the               */
33/*  FreeType Project License as well as those provided in this section,    */
34/*  and you accept them fully.                                             */
35/*                                                                         */
36/***************************************************************************/
37
38
39#include "cf2ft.h"
40#include FT_INTERNAL_DEBUG_H
41
42#include "cf2glue.h"
43#include "cf2font.h"
44#include "cf2hints.h"
45#include "cf2intrp.h"
46
47
48  /*************************************************************************/
49  /*                                                                       */
50  /* The macro FT_COMPONENT is used in trace mode.  It is an implicit      */
51  /* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log  */
52  /* messages during execution.                                            */
53  /*                                                                       */
54#undef  FT_COMPONENT
55#define FT_COMPONENT  trace_cf2hints
56
57
58  typedef struct  CF2_HintMoveRec_
59  {
60    size_t     j;          /* index of upper hint map edge   */
61    CF2_Fixed  moveUp;     /* adjustment to optimum position */
62
63  } CF2_HintMoveRec, *CF2_HintMove;
64
65
66  /* Compute angular momentum for winding order detection.  It is called */
67  /* for all lines and curves, but not necessarily in element order.     */
68  static CF2_Int
69  cf2_getWindingMomentum( CF2_Fixed  x1,
70                          CF2_Fixed  y1,
71                          CF2_Fixed  x2,
72                          CF2_Fixed  y2 )
73  {
74    /* cross product of pt1 position from origin with pt2 position from  */
75    /* pt1; we reduce the precision so that the result fits into 32 bits */
76
77    return ( x1 >> 16 ) * ( ( y2 - y1 ) >> 16 ) -
78           ( y1 >> 16 ) * ( ( x2 - x1 ) >> 16 );
79  }
80
81
82  /*
83   * Construct from a StemHint; this is used as a parameter to
84   * `cf2_blues_capture'.
85   * `hintOrigin' is the character space displacement of a seac accent.
86   * Adjust stem hint for darkening here.
87   *
88   */
89  static void
90  cf2_hint_init( CF2_Hint            hint,
91                 const CF2_ArrStack  stemHintArray,
92                 size_t              indexStemHint,
93                 const CF2_Font      font,
94                 CF2_Fixed           hintOrigin,
95                 CF2_Fixed           scale,
96                 FT_Bool             bottom )
97  {
98    CF2_Fixed               width;
99    const CF2_StemHintRec*  stemHint;
100
101
102    FT_ZERO( hint );
103
104    stemHint = (const CF2_StemHintRec*)cf2_arrstack_getPointer(
105                                         stemHintArray,
106                                         indexStemHint );
107
108    width = stemHint->max - stemHint->min;
109
110    if ( width == cf2_intToFixed( -21 ) )
111    {
112      /* ghost bottom */
113
114      if ( bottom )
115      {
116        hint->csCoord = stemHint->max;
117        hint->flags   = CF2_GhostBottom;
118      }
119      else
120        hint->flags = 0;
121    }
122
123    else if ( width == cf2_intToFixed( -20 ) )
124    {
125      /* ghost top */
126
127      if ( bottom )
128        hint->flags = 0;
129      else
130      {
131        hint->csCoord = stemHint->min;
132        hint->flags   = CF2_GhostTop;
133      }
134    }
135
136    else if ( width < 0 )
137    {
138      /* inverted pair */
139
140      /*
141       * Hints with negative widths were produced by an early version of a
142       * non-Adobe font tool.  The Type 2 spec allows edge (ghost) hints
143       * with negative widths, but says
144       *
145       *   All other negative widths have undefined meaning.
146       *
147       * CoolType has a silent workaround that negates the hint width; for
148       * permissive mode, we do the same here.
149       *
150       * Note: Such fonts cannot use ghost hints, but should otherwise work.
151       * Note: Some poor hints in our faux fonts can produce negative
152       *       widths at some blends.  For example, see a light weight of
153       *       `u' in ASerifMM.
154       *
155       */
156      if ( bottom )
157      {
158        hint->csCoord = stemHint->max;
159        hint->flags   = CF2_PairBottom;
160      }
161      else
162      {
163        hint->csCoord = stemHint->min;
164        hint->flags   = CF2_PairTop;
165      }
166    }
167
168    else
169    {
170      /* normal pair */
171
172      if ( bottom )
173      {
174        hint->csCoord = stemHint->min;
175        hint->flags   = CF2_PairBottom;
176      }
177      else
178      {
179        hint->csCoord = stemHint->max;
180        hint->flags   = CF2_PairTop;
181      }
182    }
183
184    /* Now that ghost hints have been detected, adjust this edge for      */
185    /* darkening.  Bottoms are not changed; tops are incremented by twice */
186    /* `darkenY'.                                                         */
187    if ( cf2_hint_isTop( hint ) )
188      hint->csCoord += 2 * font->darkenY;
189
190    hint->csCoord += hintOrigin;
191    hint->scale    = scale;
192    hint->index    = indexStemHint;   /* index in original stem hint array */
193
194    /* if original stem hint has been used, use the same position */
195    if ( hint->flags != 0 && stemHint->used )
196    {
197      if ( cf2_hint_isTop( hint ) )
198        hint->dsCoord = stemHint->maxDS;
199      else
200        hint->dsCoord = stemHint->minDS;
201
202      cf2_hint_lock( hint );
203    }
204    else
205      hint->dsCoord = FT_MulFix( hint->csCoord, scale );
206  }
207
208
209  /* initialize an invalid hint map element */
210  static void
211  cf2_hint_initZero( CF2_Hint  hint )
212  {
213    FT_ZERO( hint );
214  }
215
216
217  FT_LOCAL_DEF( FT_Bool )
218  cf2_hint_isValid( const CF2_Hint  hint )
219  {
220    return hint->flags != 0;
221  }
222
223
224  static FT_Bool
225  cf2_hint_isPair( const CF2_Hint  hint )
226  {
227    return ( hint->flags                      &
228             ( CF2_PairBottom | CF2_PairTop ) ) != 0;
229  }
230
231
232  static FT_Bool
233  cf2_hint_isPairTop( const CF2_Hint  hint )
234  {
235    return ( hint->flags & CF2_PairTop ) != 0;
236  }
237
238
239  FT_LOCAL_DEF( FT_Bool )
240  cf2_hint_isTop( const CF2_Hint  hint )
241  {
242    return ( hint->flags                    &
243             ( CF2_PairTop | CF2_GhostTop ) ) != 0;
244  }
245
246
247  FT_LOCAL_DEF( FT_Bool )
248  cf2_hint_isBottom( const CF2_Hint  hint )
249  {
250    return ( hint->flags                          &
251             ( CF2_PairBottom | CF2_GhostBottom ) ) != 0;
252  }
253
254
255  static FT_Bool
256  cf2_hint_isLocked( const CF2_Hint  hint )
257  {
258    return ( hint->flags & CF2_Locked ) != 0;
259  }
260
261
262  static FT_Bool
263  cf2_hint_isSynthetic( const CF2_Hint  hint )
264  {
265    return ( hint->flags & CF2_Synthetic ) != 0;
266  }
267
268
269  FT_LOCAL_DEF( void )
270  cf2_hint_lock( CF2_Hint  hint )
271  {
272    hint->flags |= CF2_Locked;
273  }
274
275
276  FT_LOCAL_DEF( void )
277  cf2_hintmap_init( CF2_HintMap   hintmap,
278                    CF2_Font      font,
279                    CF2_HintMap   initialMap,
280                    CF2_ArrStack  hintMoves,
281                    CF2_Fixed     scale )
282  {
283    FT_ZERO( hintmap );
284
285    /* copy parameters from font instance */
286    hintmap->hinted         = font->hinted;
287    hintmap->scale          = scale;
288    hintmap->font           = font;
289    hintmap->initialHintMap = initialMap;
290    /* will clear in `cf2_hintmap_adjustHints' */
291    hintmap->hintMoves      = hintMoves;
292  }
293
294
295  static FT_Bool
296  cf2_hintmap_isValid( const CF2_HintMap  hintmap )
297  {
298    return hintmap->isValid;
299  }
300
301
302  /* transform character space coordinate to device space using hint map */
303  static CF2_Fixed
304  cf2_hintmap_map( CF2_HintMap  hintmap,
305                   CF2_Fixed    csCoord )
306  {
307    FT_ASSERT( hintmap->isValid );  /* must call Build before Map */
308    FT_ASSERT( hintmap->lastIndex < CF2_MAX_HINT_EDGES );
309
310    if ( hintmap->count == 0 || ! hintmap->hinted )
311    {
312      /* there are no hints; use uniform scale and zero offset */
313      return FT_MulFix( csCoord, hintmap->scale );
314    }
315    else
316    {
317      /* start linear search from last hit */
318      CF2_UInt  i = hintmap->lastIndex;
319
320
321      /* search up */
322      while ( i < hintmap->count - 1                  &&
323              csCoord >= hintmap->edge[i + 1].csCoord )
324        i += 1;
325
326      /* search down */
327      while ( i > 0 && csCoord < hintmap->edge[i].csCoord )
328        i -= 1;
329
330      hintmap->lastIndex = i;
331
332      if ( i == 0 && csCoord < hintmap->edge[0].csCoord )
333      {
334        /* special case for points below first edge: use uniform scale */
335        return FT_MulFix( csCoord - hintmap->edge[0].csCoord,
336                          hintmap->scale ) +
337                 hintmap->edge[0].dsCoord;
338      }
339      else
340      {
341        /*
342         * Note: entries with duplicate csCoord are allowed.
343         * Use edge[i], the highest entry where csCoord >= entry[i].csCoord
344         */
345        return FT_MulFix( csCoord - hintmap->edge[i].csCoord,
346                          hintmap->edge[i].scale ) +
347                 hintmap->edge[i].dsCoord;
348      }
349    }
350  }
351
352
353  /*
354   * This hinting policy moves a hint pair in device space so that one of
355   * its two edges is on a device pixel boundary (its fractional part is
356   * zero).  `cf2_hintmap_insertHint' guarantees no overlap in CS
357   * space.  Ensure here that there is no overlap in DS.
358   *
359   * In the first pass, edges are adjusted relative to adjacent hints.
360   * Those that are below have already been adjusted.  Those that are
361   * above have not yet been adjusted.  If a hint above blocks an
362   * adjustment to an optimal position, we will try again in a second
363   * pass.  The second pass is top-down.
364   *
365   */
366
367  static void
368  cf2_hintmap_adjustHints( CF2_HintMap  hintmap )
369  {
370    size_t  i, j;
371
372
373    cf2_arrstack_clear( hintmap->hintMoves );      /* working storage */
374
375    /*
376     * First pass is bottom-up (font hint order) without look-ahead.
377     * Locked edges are already adjusted.
378     * Unlocked edges begin with dsCoord from `initialHintMap'.
379     * Save edges that are not optimally adjusted in `hintMoves' array,
380     * and process them in second pass.
381     */
382
383    for ( i = 0; i < hintmap->count; i++ )
384    {
385      FT_Bool  isPair = cf2_hint_isPair( &hintmap->edge[i] );
386
387
388      /* index of upper edge (same value for ghost hint) */
389      j = isPair ? i + 1 : i;
390
391      FT_ASSERT( j < hintmap->count );
392      FT_ASSERT( cf2_hint_isValid( &hintmap->edge[i] ) );
393      FT_ASSERT( cf2_hint_isValid( &hintmap->edge[j] ) );
394      FT_ASSERT( cf2_hint_isLocked( &hintmap->edge[i] ) ==
395                   cf2_hint_isLocked( &hintmap->edge[j] ) );
396
397      if ( !cf2_hint_isLocked( &hintmap->edge[i] ) )
398      {
399        /* hint edge is not locked, we can adjust it */
400        CF2_Fixed  fracDown = cf2_fixedFraction( hintmap->edge[i].dsCoord );
401        CF2_Fixed  fracUp   = cf2_fixedFraction( hintmap->edge[j].dsCoord );
402
403        /* calculate all four possibilities; moves down are negative */
404        CF2_Fixed  downMoveDown = 0 - fracDown;
405        CF2_Fixed  upMoveDown   = 0 - fracUp;
406        CF2_Fixed  downMoveUp   = fracDown == 0
407                                    ? 0
408                                    : cf2_intToFixed( 1 ) - fracDown;
409        CF2_Fixed  upMoveUp     = fracUp == 0
410                                    ? 0
411                                    : cf2_intToFixed( 1 ) - fracUp;
412
413        /* smallest move up */
414        CF2_Fixed  moveUp   = FT_MIN( downMoveUp, upMoveUp );
415        /* smallest move down */
416        CF2_Fixed  moveDown = FT_MAX( downMoveDown, upMoveDown );
417
418        /* final amount to move edge or edge pair */
419        CF2_Fixed  move;
420
421        CF2_Fixed  downMinCounter = CF2_MIN_COUNTER;
422        CF2_Fixed  upMinCounter   = CF2_MIN_COUNTER;
423        FT_Bool    saveEdge       = FALSE;
424
425
426        /* minimum counter constraint doesn't apply when adjacent edges */
427        /* are synthetic                                                */
428        /* TODO: doesn't seem a big effect; for now, reduce the code    */
429#if 0
430        if ( i == 0                                        ||
431             cf2_hint_isSynthetic( &hintmap->edge[i - 1] ) )
432          downMinCounter = 0;
433
434        if ( j >= hintmap->count - 1                       ||
435             cf2_hint_isSynthetic( &hintmap->edge[j + 1] ) )
436          upMinCounter = 0;
437#endif
438
439        /* is there room to move up?                                    */
440        /* there is if we are at top of array or the next edge is at or */
441        /* beyond proposed move up?                                     */
442        if ( j >= hintmap->count - 1                            ||
443             hintmap->edge[j + 1].dsCoord >=
444               hintmap->edge[j].dsCoord + moveUp + upMinCounter )
445        {
446          /* there is room to move up; is there also room to move down? */
447          if ( i == 0                                                 ||
448               hintmap->edge[i - 1].dsCoord <=
449                 hintmap->edge[i].dsCoord + moveDown - downMinCounter )
450          {
451            /* move smaller absolute amount */
452            move = ( -moveDown < moveUp ) ? moveDown : moveUp;  /* optimum */
453          }
454          else
455            move = moveUp;
456        }
457        else
458        {
459          /* is there room to move down? */
460          if ( i == 0                                                 ||
461               hintmap->edge[i - 1].dsCoord <=
462                 hintmap->edge[i].dsCoord + moveDown - downMinCounter )
463          {
464            move     = moveDown;
465            saveEdge = moveUp < -moveDown;  /* true if non-optimum move */
466          }
467          else
468          {
469            /* no room to move either way without overlapping or reducing */
470            /* the counter too much                                       */
471            move     = 0;
472            saveEdge = TRUE;
473          }
474        }
475
476        /* Identify non-moves and moves down that aren't optimal, and save */
477        /* them for second pass.                                           */
478        /* Do this only if there is an unlocked edge above (which could    */
479        /* possibly move).                                                 */
480        if ( saveEdge                                    &&
481             j < hintmap->count - 1                      &&
482             !cf2_hint_isLocked( &hintmap->edge[j + 1] ) )
483        {
484          CF2_HintMoveRec  savedMove;
485
486
487          savedMove.j      = j;
488          /* desired adjustment in second pass */
489          savedMove.moveUp = moveUp - move;
490
491          cf2_arrstack_push( hintmap->hintMoves, &savedMove );
492        }
493
494        /* move the edge(s) */
495        hintmap->edge[i].dsCoord += move;
496        if ( isPair )
497          hintmap->edge[j].dsCoord += move;
498      }
499
500      /* assert there are no overlaps in device space */
501      FT_ASSERT( i == 0                                                   ||
502                 hintmap->edge[i - 1].dsCoord <= hintmap->edge[i].dsCoord );
503      FT_ASSERT( i < j                                                ||
504                 hintmap->edge[i].dsCoord <= hintmap->edge[j].dsCoord );
505
506      /* adjust the scales, avoiding divide by zero */
507      if ( i > 0 )
508      {
509        if ( hintmap->edge[i].csCoord != hintmap->edge[i - 1].csCoord )
510          hintmap->edge[i - 1].scale =
511            FT_DivFix(
512              hintmap->edge[i].dsCoord - hintmap->edge[i - 1].dsCoord,
513              hintmap->edge[i].csCoord - hintmap->edge[i - 1].csCoord );
514      }
515
516      if ( isPair )
517      {
518        if ( hintmap->edge[j].csCoord != hintmap->edge[j - 1].csCoord )
519          hintmap->edge[j - 1].scale =
520            FT_DivFix(
521              hintmap->edge[j].dsCoord - hintmap->edge[j - 1].dsCoord,
522              hintmap->edge[j].csCoord - hintmap->edge[j - 1].csCoord );
523
524        i += 1;     /* skip upper edge on next loop */
525      }
526    }
527
528    /* second pass tries to move non-optimal hints up, in case there is */
529    /* room now                                                         */
530    for ( i = cf2_arrstack_size( hintmap->hintMoves ); i > 0; i-- )
531    {
532      CF2_HintMove  hintMove = (CF2_HintMove)
533                      cf2_arrstack_getPointer( hintmap->hintMoves, i - 1 );
534
535
536      j = hintMove->j;
537
538      /* this was tested before the push, above */
539      FT_ASSERT( j < hintmap->count - 1 );
540
541      /* is there room to move up? */
542      if ( hintmap->edge[j + 1].dsCoord >=
543             hintmap->edge[j].dsCoord + hintMove->moveUp + CF2_MIN_COUNTER )
544      {
545        /* there is more room now, move edge up */
546        hintmap->edge[j].dsCoord += hintMove->moveUp;
547
548        if ( cf2_hint_isPair( &hintmap->edge[j] ) )
549        {
550          FT_ASSERT( j > 0 );
551          hintmap->edge[j - 1].dsCoord += hintMove->moveUp;
552        }
553      }
554    }
555  }
556
557
558  /* insert hint edges into map, sorted by csCoord */
559  static void
560  cf2_hintmap_insertHint( CF2_HintMap  hintmap,
561                          CF2_Hint     bottomHintEdge,
562                          CF2_Hint     topHintEdge )
563  {
564    CF2_UInt  indexInsert;
565
566    /* set default values, then check for edge hints */
567    FT_Bool   isPair         = TRUE;
568    CF2_Hint  firstHintEdge  = bottomHintEdge;
569    CF2_Hint  secondHintEdge = topHintEdge;
570
571
572    /* one or none of the input params may be invalid when dealing with */
573    /* edge hints; at least one edge must be valid                      */
574    FT_ASSERT( cf2_hint_isValid( bottomHintEdge ) ||
575               cf2_hint_isValid( topHintEdge )    );
576
577    /* determine how many and which edges to insert */
578    if ( !cf2_hint_isValid( bottomHintEdge ) )
579    {
580      /* insert only the top edge */
581      firstHintEdge = topHintEdge;
582      isPair        = FALSE;
583    }
584    else if ( !cf2_hint_isValid( topHintEdge ) )
585    {
586      /* insert only the bottom edge */
587      isPair = FALSE;
588    }
589
590    /* paired edges must be in proper order */
591    FT_ASSERT( !isPair                                         ||
592               topHintEdge->csCoord >= bottomHintEdge->csCoord );
593
594    /* linear search to find index value of insertion point */
595    indexInsert = 0;
596    for ( ; indexInsert < hintmap->count; indexInsert++ )
597    {
598      if ( hintmap->edge[indexInsert].csCoord > firstHintEdge->csCoord )
599        break;
600    }
601
602    /*
603     * Discard any hints that overlap in character space.  Most often,
604     * this is while building the initial map, but in theory, it can also
605     * occur because of darkening.
606     *
607     */
608    if ( indexInsert < hintmap->count )
609    {
610      /* we are inserting before an existing edge:              */
611      /* verify that a new pair does not straddle the next edge */
612      if ( isPair                                                       &&
613           hintmap->edge[indexInsert].csCoord < secondHintEdge->csCoord )
614        return; /* ignore overlapping stem hint */
615
616      /* verify that we are not inserting between paired edges */
617      if ( cf2_hint_isPairTop( &hintmap->edge[indexInsert] ) )
618        return; /* ignore overlapping stem hint */
619    }
620
621    /* recompute device space locations using initial hint map */
622    if ( cf2_hintmap_isValid( hintmap->initialHintMap ) &&
623         !cf2_hint_isLocked( firstHintEdge )            )
624    {
625      if ( isPair )
626      {
627        /* Use hint map to position the center of stem, and nominal scale */
628        /* to position the two edges.  This preserves the stem width.     */
629        CF2_Fixed  midpoint  = cf2_hintmap_map(
630                                 hintmap->initialHintMap,
631                                 ( secondHintEdge->csCoord +
632                                   firstHintEdge->csCoord ) / 2 );
633        CF2_Fixed  halfWidth = FT_MulFix(
634                                 ( secondHintEdge->csCoord -
635                                   firstHintEdge->csCoord ) / 2,
636                                 hintmap->scale );
637
638
639        firstHintEdge->dsCoord  = midpoint - halfWidth;
640        secondHintEdge->dsCoord = midpoint + halfWidth;
641      }
642      else
643        firstHintEdge->dsCoord = cf2_hintmap_map( hintmap->initialHintMap,
644                                                  firstHintEdge->csCoord );
645    }
646
647    /* discard any hints that overlap in device space; this can occur */
648    /* because locked hints have been moved to align with blue zones  */
649    if ( indexInsert > 0 )
650    {
651      /* we are inserting after an existing edge */
652      if ( firstHintEdge->dsCoord < hintmap->edge[indexInsert - 1].dsCoord )
653        return;
654    }
655
656    if ( indexInsert < hintmap->count )
657    {
658      /* we are inserting before an existing edge */
659      if ( isPair )
660      {
661        if ( secondHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord )
662          return;
663      }
664      else
665      {
666        if ( firstHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord )
667          return;
668      }
669    }
670
671    /* make room to insert */
672    {
673      CF2_Int  iSrc = hintmap->count - 1;
674      CF2_Int  iDst = isPair ? hintmap->count + 1 : hintmap->count;
675
676      CF2_Int  count = hintmap->count - indexInsert;
677
678
679      if ( iDst >= CF2_MAX_HINT_EDGES )
680      {
681        FT_TRACE4(( "cf2_hintmap_insertHint: too many hintmaps\n" ));
682        return;
683      }
684
685      while ( count-- )
686        hintmap->edge[iDst--] = hintmap->edge[iSrc--];
687
688      /* insert first edge */
689      hintmap->edge[indexInsert] = *firstHintEdge;         /* copy struct */
690      hintmap->count += 1;
691
692      if ( isPair )
693      {
694        /* insert second edge */
695        hintmap->edge[indexInsert + 1] = *secondHintEdge;  /* copy struct */
696        hintmap->count                += 1;
697      }
698    }
699
700    return;
701  }
702
703
704  /*
705   * Build a map from hints and mask.
706   *
707   * This function may recur one level if `hintmap->initialHintMap' is not yet
708   * valid.
709   * If `initialMap' is true, simply build initial map.
710   *
711   * Synthetic hints are used in two ways.  A hint at zero is inserted, if
712   * needed, in the initial hint map, to prevent translations from
713   * propagating across the origin.  If synthetic em box hints are enabled
714   * for ideographic dictionaries, then they are inserted in all hint
715   * maps, including the initial one.
716   *
717   */
718  FT_LOCAL_DEF( void )
719  cf2_hintmap_build( CF2_HintMap   hintmap,
720                     CF2_ArrStack  hStemHintArray,
721                     CF2_ArrStack  vStemHintArray,
722                     CF2_HintMask  hintMask,
723                     CF2_Fixed     hintOrigin,
724                     FT_Bool       initialMap )
725  {
726    FT_Byte*  maskPtr;
727
728    CF2_Font         font = hintmap->font;
729    CF2_HintMaskRec  tempHintMask;
730
731    size_t   bitCount, i;
732    FT_Byte  maskByte;
733
734
735    /* check whether initial map is constructed */
736    if ( !initialMap && !cf2_hintmap_isValid( hintmap->initialHintMap ) )
737    {
738      /* make recursive call with initialHintMap and temporary mask; */
739      /* temporary mask will get all bits set, below */
740      cf2_hintmask_init( &tempHintMask, hintMask->error );
741      cf2_hintmap_build( hintmap->initialHintMap,
742                         hStemHintArray,
743                         vStemHintArray,
744                         &tempHintMask,
745                         hintOrigin,
746                         TRUE );
747    }
748
749    if ( !cf2_hintmask_isValid( hintMask ) )
750    {
751      /* without a hint mask, assume all hints are active */
752      cf2_hintmask_setAll( hintMask,
753                           cf2_arrstack_size( hStemHintArray ) +
754                             cf2_arrstack_size( vStemHintArray ) );
755    }
756
757    /* begin by clearing the map */
758    hintmap->count     = 0;
759    hintmap->lastIndex = 0;
760
761    /* make a copy of the hint mask so we can modify it */
762    tempHintMask = *hintMask;
763    maskPtr      = cf2_hintmask_getMaskPtr( &tempHintMask );
764
765    /* use the hStem hints only, which are first in the mask */
766    /* TODO: compare this to cffhintmaskGetBitCount */
767    bitCount = cf2_arrstack_size( hStemHintArray );
768
769    /* synthetic embox hints get highest priority */
770    if ( font->blues.doEmBoxHints )
771    {
772      CF2_HintRec  dummy;
773
774
775      cf2_hint_initZero( &dummy );   /* invalid hint map element */
776
777      /* ghost bottom */
778      cf2_hintmap_insertHint( hintmap,
779                              &font->blues.emBoxBottomEdge,
780                              &dummy );
781      /* ghost top */
782      cf2_hintmap_insertHint( hintmap,
783                              &dummy,
784                              &font->blues.emBoxTopEdge );
785    }
786
787    /* insert hints captured by a blue zone or already locked (higher */
788    /* priority)                                                      */
789    for ( i = 0, maskByte = 0x80; i < bitCount; i++ )
790    {
791      if ( maskByte & *maskPtr )
792      {
793        /* expand StemHint into two `CF2_Hint' elements */
794        CF2_HintRec  bottomHintEdge, topHintEdge;
795
796
797        cf2_hint_init( &bottomHintEdge,
798                       hStemHintArray,
799                       i,
800                       font,
801                       hintOrigin,
802                       hintmap->scale,
803                       TRUE /* bottom */ );
804        cf2_hint_init( &topHintEdge,
805                       hStemHintArray,
806                       i,
807                       font,
808                       hintOrigin,
809                       hintmap->scale,
810                       FALSE /* top */ );
811
812        if ( cf2_hint_isLocked( &bottomHintEdge ) ||
813             cf2_hint_isLocked( &topHintEdge )    ||
814             cf2_blues_capture( &font->blues,
815                                &bottomHintEdge,
816                                &topHintEdge )   )
817        {
818          /* insert captured hint into map */
819          cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge );
820
821          *maskPtr &= ~maskByte;      /* turn off the bit for this hint */
822        }
823      }
824
825      if ( ( i & 7 ) == 7 )
826      {
827        /* move to next mask byte */
828        maskPtr++;
829        maskByte = 0x80;
830      }
831      else
832        maskByte >>= 1;
833    }
834
835    /* initial hint map includes only captured hints plus maybe one at 0 */
836
837    /*
838     * TODO: There is a problem here because we are trying to build a
839     *       single hint map containing all captured hints.  It is
840     *       possible for there to be conflicts between captured hints,
841     *       either because of darkening or because the hints are in
842     *       separate hint zones (we are ignoring hint zones for the
843     *       initial map).  An example of the latter is MinionPro-Regular
844     *       v2.030 glyph 883 (Greek Capital Alpha with Psili) at 15ppem.
845     *       A stem hint for the psili conflicts with the top edge hint
846     *       for the base character.  The stem hint gets priority because
847     *       of its sort order.  In glyph 884 (Greek Capital Alpha with
848     *       Psili and Oxia), the top of the base character gets a stem
849     *       hint, and the psili does not.  This creates different initial
850     *       maps for the two glyphs resulting in different renderings of
851     *       the base character.  Will probably defer this either as not
852     *       worth the cost or as a font bug.  I don't think there is any
853     *       good reason for an accent to be captured by an alignment
854     *       zone.  -darnold 2/12/10
855     */
856
857    if ( initialMap )
858    {
859      /* Apply a heuristic that inserts a point for (0,0), unless it's     */
860      /* already covered by a mapping.  This locks the baseline for glyphs */
861      /* that have no baseline hints.                                      */
862
863      if ( hintmap->count == 0                           ||
864           hintmap->edge[0].csCoord > 0                  ||
865           hintmap->edge[hintmap->count - 1].csCoord < 0 )
866      {
867        /* all edges are above 0 or all edges are below 0; */
868        /* construct a locked edge hint at 0               */
869
870        CF2_HintRec  edge, invalid;
871
872
873        cf2_hint_initZero( &edge );
874
875        edge.flags = CF2_GhostBottom |
876                     CF2_Locked      |
877                     CF2_Synthetic;
878        edge.scale = hintmap->scale;
879
880        cf2_hint_initZero( &invalid );
881        cf2_hintmap_insertHint( hintmap, &edge, &invalid );
882      }
883    }
884    else
885    {
886      /* insert remaining hints */
887
888      maskPtr = cf2_hintmask_getMaskPtr( &tempHintMask );
889
890      for ( i = 0, maskByte = 0x80; i < bitCount; i++ )
891      {
892        if ( maskByte & *maskPtr )
893        {
894          CF2_HintRec  bottomHintEdge, topHintEdge;
895
896
897          cf2_hint_init( &bottomHintEdge,
898                         hStemHintArray,
899                         i,
900                         font,
901                         hintOrigin,
902                         hintmap->scale,
903                         TRUE /* bottom */ );
904          cf2_hint_init( &topHintEdge,
905                         hStemHintArray,
906                         i,
907                         font,
908                         hintOrigin,
909                         hintmap->scale,
910                         FALSE /* top */ );
911
912          cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge );
913        }
914
915        if ( ( i & 7 ) == 7 )
916        {
917          /* move to next mask byte */
918          maskPtr++;
919          maskByte = 0x80;
920        }
921        else
922          maskByte >>= 1;
923      }
924    }
925
926    /*
927     * Note: The following line is a convenient place to break when
928     *       debugging hinting.  Examine `hintmap->edge' for the list of
929     *       enabled hints, then step over the call to see the effect of
930     *       adjustment.  We stop here first on the recursive call that
931     *       creates the initial map, and then on each counter group and
932     *       hint zone.
933     */
934
935    /* adjust positions of hint edges that are not locked to blue zones */
936    cf2_hintmap_adjustHints( hintmap );
937
938    /* save the position of all hints that were used in this hint map; */
939    /* if we use them again, we'll locate them in the same position    */
940    if ( !initialMap )
941    {
942      for ( i = 0; i < hintmap->count; i++ )
943      {
944        if ( !cf2_hint_isSynthetic( &hintmap->edge[i] ) )
945        {
946          /* Note: include both valid and invalid edges            */
947          /* Note: top and bottom edges are copied back separately */
948          CF2_StemHint  stemhint = (CF2_StemHint)
949                          cf2_arrstack_getPointer( hStemHintArray,
950                                                   hintmap->edge[i].index );
951
952
953          if ( cf2_hint_isTop( &hintmap->edge[i] ) )
954            stemhint->maxDS = hintmap->edge[i].dsCoord;
955          else
956            stemhint->minDS = hintmap->edge[i].dsCoord;
957
958          stemhint->used = TRUE;
959        }
960      }
961    }
962
963    /* hint map is ready to use */
964    hintmap->isValid = TRUE;
965
966    /* remember this mask has been used */
967    cf2_hintmask_setNew( hintMask, FALSE );
968  }
969
970
971  FT_LOCAL_DEF( void )
972  cf2_glyphpath_init( CF2_GlyphPath         glyphpath,
973                      CF2_Font              font,
974                      CF2_OutlineCallbacks  callbacks,
975                      CF2_Fixed             scaleY,
976                      /* CF2_Fixed  hShift, */
977                      CF2_ArrStack          hStemHintArray,
978                      CF2_ArrStack          vStemHintArray,
979                      CF2_HintMask          hintMask,
980                      CF2_Fixed             hintOriginY,
981                      const CF2_Blues       blues,
982                      const FT_Vector*      fractionalTranslation )
983  {
984    FT_ZERO( glyphpath );
985
986    glyphpath->font      = font;
987    glyphpath->callbacks = callbacks;
988
989    cf2_arrstack_init( &glyphpath->hintMoves,
990                       font->memory,
991                       &font->error,
992                       sizeof ( CF2_HintMoveRec ) );
993
994    cf2_hintmap_init( &glyphpath->initialHintMap,
995                      font,
996                      &glyphpath->initialHintMap,
997                      &glyphpath->hintMoves,
998                      scaleY );
999    cf2_hintmap_init( &glyphpath->firstHintMap,
1000                      font,
1001                      &glyphpath->initialHintMap,
1002                      &glyphpath->hintMoves,
1003                      scaleY );
1004    cf2_hintmap_init( &glyphpath->hintMap,
1005                      font,
1006                      &glyphpath->initialHintMap,
1007                      &glyphpath->hintMoves,
1008                      scaleY );
1009
1010    glyphpath->scaleX = font->innerTransform.a;
1011    glyphpath->scaleC = font->innerTransform.c;
1012    glyphpath->scaleY = font->innerTransform.d;
1013
1014    glyphpath->fractionalTranslation = *fractionalTranslation;
1015
1016#if 0
1017    glyphpath->hShift = hShift;       /* for fauxing */
1018#endif
1019
1020    glyphpath->hStemHintArray = hStemHintArray;
1021    glyphpath->vStemHintArray = vStemHintArray;
1022    glyphpath->hintMask       = hintMask;      /* ptr to current mask */
1023    glyphpath->hintOriginY    = hintOriginY;
1024    glyphpath->blues          = blues;
1025    glyphpath->darken         = font->darkened; /* TODO: should we make copies? */
1026    glyphpath->xOffset        = font->darkenX;
1027    glyphpath->yOffset        = font->darkenY;
1028    glyphpath->miterLimit     = 2 * FT_MAX(
1029                                     cf2_fixedAbs( glyphpath->xOffset ),
1030                                     cf2_fixedAbs( glyphpath->yOffset ) );
1031
1032    /* .1 character space unit */
1033    glyphpath->snapThreshold = cf2_floatToFixed( 0.1f );
1034
1035    glyphpath->moveIsPending = TRUE;
1036    glyphpath->pathIsOpen    = FALSE;
1037    glyphpath->elemIsQueued  = FALSE;
1038  }
1039
1040
1041  FT_LOCAL_DEF( void )
1042  cf2_glyphpath_finalize( CF2_GlyphPath  glyphpath )
1043  {
1044    cf2_arrstack_finalize( &glyphpath->hintMoves );
1045  }
1046
1047
1048  /*
1049   * Hint point in y-direction and apply outerTransform.
1050   * Input `current' hint map (which is actually delayed by one element).
1051   * Input x,y point in Character Space.
1052   * Output x,y point in Device Space, including translation.
1053   */
1054  static void
1055  cf2_glyphpath_hintPoint( CF2_GlyphPath  glyphpath,
1056                           CF2_HintMap    hintmap,
1057                           FT_Vector*     ppt,
1058                           CF2_Fixed      x,
1059                           CF2_Fixed      y )
1060  {
1061    FT_Vector  pt;   /* hinted point in upright DS */
1062
1063
1064    pt.x = FT_MulFix( glyphpath->scaleX, x ) +
1065             FT_MulFix( glyphpath->scaleC, y );
1066    pt.y = cf2_hintmap_map( hintmap, y );
1067
1068    ppt->x = FT_MulFix( glyphpath->font->outerTransform.a, pt.x )   +
1069               FT_MulFix( glyphpath->font->outerTransform.c, pt.y ) +
1070               glyphpath->fractionalTranslation.x;
1071    ppt->y = FT_MulFix( glyphpath->font->outerTransform.b, pt.x )   +
1072               FT_MulFix( glyphpath->font->outerTransform.d, pt.y ) +
1073               glyphpath->fractionalTranslation.y;
1074  }
1075
1076
1077  /*
1078   * From two line segments, (u1,u2) and (v1,v2), compute a point of
1079   * intersection on the corresponding lines.
1080   * Return false if no intersection is found, or if the intersection is
1081   * too far away from the ends of the line segments, u2 and v1.
1082   *
1083   */
1084  static FT_Bool
1085  cf2_glyphpath_computeIntersection( CF2_GlyphPath     glyphpath,
1086                                     const FT_Vector*  u1,
1087                                     const FT_Vector*  u2,
1088                                     const FT_Vector*  v1,
1089                                     const FT_Vector*  v2,
1090                                     FT_Vector*        intersection )
1091  {
1092    /*
1093     * Let `u' be a zero-based vector from the first segment, `v' from the
1094     * second segment.
1095     * Let `w 'be the zero-based vector from `u1' to `v1'.
1096     * `perp' is the `perpendicular dot product'; see
1097     * http://mathworld.wolfram.com/PerpDotProduct.html.
1098     * `s' is the parameter for the parametric line for the first segment
1099     * (`u').
1100     *
1101     * See notation in
1102     * http://softsurfer.com/Archive/algorithm_0104/algorithm_0104B.htm.
1103     * Calculations are done in 16.16, but must handle the squaring of
1104     * line lengths in character space.  We scale all vectors by 1/32 to
1105     * avoid overflow.  This allows values up to 4095 to be squared.  The
1106     * scale factor cancels in the divide.
1107     *
1108     * TODO: the scale factor could be computed from UnitsPerEm.
1109     *
1110     */
1111
1112#define cf2_perp( a, b )                                    \
1113          ( FT_MulFix( a.x, b.y ) - FT_MulFix( a.y, b.x ) )
1114
1115  /* round and divide by 32 */
1116#define CF2_CS_SCALE( x )         \
1117          ( ( (x) + 0x10 ) >> 5 )
1118
1119    FT_Vector  u, v, w;      /* scaled vectors */
1120    CF2_Fixed  denominator, s;
1121
1122
1123    u.x = CF2_CS_SCALE( u2->x - u1->x );
1124    u.y = CF2_CS_SCALE( u2->y - u1->y );
1125    v.x = CF2_CS_SCALE( v2->x - v1->x );
1126    v.y = CF2_CS_SCALE( v2->y - v1->y );
1127    w.x = CF2_CS_SCALE( v1->x - u1->x );
1128    w.y = CF2_CS_SCALE( v1->y - u1->y );
1129
1130    denominator = cf2_perp( u, v );
1131
1132    if ( denominator == 0 )
1133      return FALSE;           /* parallel or coincident lines */
1134
1135    s = FT_DivFix( cf2_perp( w, v ), denominator );
1136
1137    intersection->x = u1->x + FT_MulFix( s, u2->x - u1->x );
1138    intersection->y = u1->y + FT_MulFix( s, u2->y - u1->y );
1139
1140    /*
1141     * Special case snapping for horizontal and vertical lines.
1142     * This cleans up intersections and reduces problems with winding
1143     * order detection.
1144     * Sample case is sbc cd KozGoPr6N-Medium.otf 20 16685.
1145     * Note: these calculations are in character space.
1146     *
1147     */
1148
1149    if ( u1->x == u2->x                                                     &&
1150         cf2_fixedAbs( intersection->x - u1->x ) < glyphpath->snapThreshold )
1151      intersection->x = u1->x;
1152    if ( u1->y == u2->y                                                     &&
1153         cf2_fixedAbs( intersection->y - u1->y ) < glyphpath->snapThreshold )
1154      intersection->y = u1->y;
1155
1156    if ( v1->x == v2->x                                                     &&
1157         cf2_fixedAbs( intersection->x - v1->x ) < glyphpath->snapThreshold )
1158      intersection->x = v1->x;
1159    if ( v1->y == v2->y                                                     &&
1160         cf2_fixedAbs( intersection->y - v1->y ) < glyphpath->snapThreshold )
1161      intersection->y = v1->y;
1162
1163    /* limit the intersection distance from midpoint of u2 and v1 */
1164    if ( cf2_fixedAbs( intersection->x - ( u2->x + v1->x ) / 2 ) >
1165           glyphpath->miterLimit                                   ||
1166         cf2_fixedAbs( intersection->y - ( u2->y + v1->y ) / 2 ) >
1167           glyphpath->miterLimit                                   )
1168      return FALSE;
1169
1170    return TRUE;
1171  }
1172
1173
1174  /*
1175   * Push the cached element (glyphpath->prevElem*) to the outline
1176   * consumer.  When a darkening offset is used, the end point of the
1177   * cached element may be adjusted to an intersection point or it may be
1178   * connected by a line to the current element.  This calculation must
1179   * use a HintMap that was valid at the time the element was saved.  For
1180   * the first point in a subpath, that is a saved HintMap.  For most
1181   * elements, it just means the caller has delayed building a HintMap
1182   * from the current HintMask.
1183   *
1184   * Transform each point with outerTransform and call the outline
1185   * callbacks.  This is a general 3x3 transform:
1186   *
1187   *   x' = a*x + c*y + tx, y' = b*x + d*y + ty
1188   *
1189   * but it uses 4 elements from CF2_Font and the translation part
1190   * from CF2_GlyphPath.
1191   *
1192   */
1193  static void
1194  cf2_glyphpath_pushPrevElem( CF2_GlyphPath  glyphpath,
1195                              CF2_HintMap    hintmap,
1196                              FT_Vector*     nextP0,
1197                              FT_Vector      nextP1,
1198                              FT_Bool        close )
1199  {
1200    CF2_CallbackParamsRec  params;
1201
1202    FT_Vector*  prevP0;
1203    FT_Vector*  prevP1;
1204
1205    FT_Vector  intersection    = { 0, 0 };
1206    FT_Bool    useIntersection = FALSE;
1207
1208
1209    FT_ASSERT( glyphpath->prevElemOp == CF2_PathOpLineTo ||
1210               glyphpath->prevElemOp == CF2_PathOpCubeTo );
1211
1212    if ( glyphpath->prevElemOp == CF2_PathOpLineTo )
1213    {
1214      prevP0 = &glyphpath->prevElemP0;
1215      prevP1 = &glyphpath->prevElemP1;
1216    }
1217    else
1218    {
1219      prevP0 = &glyphpath->prevElemP2;
1220      prevP1 = &glyphpath->prevElemP3;
1221    }
1222
1223    /* optimization: if previous and next elements are offset by the same */
1224    /* amount, then there will be no gap, and no need to compute an       */
1225    /* intersection.                                                      */
1226    if ( prevP1->x != nextP0->x || prevP1->y != nextP0->y )
1227    {
1228      /* previous element does not join next element:             */
1229      /* adjust end point of previous element to the intersection */
1230      useIntersection = cf2_glyphpath_computeIntersection( glyphpath,
1231                                                           prevP0,
1232                                                           prevP1,
1233                                                           nextP0,
1234                                                           &nextP1,
1235                                                           &intersection );
1236      if ( useIntersection )
1237      {
1238        /* modify the last point of the cached element (either line or */
1239        /* curve)                                                      */
1240        *prevP1 = intersection;
1241      }
1242    }
1243
1244    params.pt0 = glyphpath->currentDS;
1245
1246    switch( glyphpath->prevElemOp )
1247    {
1248    case CF2_PathOpLineTo:
1249      params.op = CF2_PathOpLineTo;
1250
1251      /* note: pt2 and pt3 are unused */
1252      cf2_glyphpath_hintPoint( glyphpath,
1253                               hintmap,
1254                               &params.pt1,
1255                               glyphpath->prevElemP1.x,
1256                               glyphpath->prevElemP1.y );
1257
1258      glyphpath->callbacks->lineTo( glyphpath->callbacks, &params );
1259
1260      glyphpath->currentDS = params.pt1;
1261
1262      break;
1263
1264    case CF2_PathOpCubeTo:
1265      params.op = CF2_PathOpCubeTo;
1266
1267      /* TODO: should we intersect the interior joins (p1-p2 and p2-p3)? */
1268      cf2_glyphpath_hintPoint( glyphpath,
1269                               hintmap,
1270                               &params.pt1,
1271                               glyphpath->prevElemP1.x,
1272                               glyphpath->prevElemP1.y );
1273      cf2_glyphpath_hintPoint( glyphpath,
1274                               hintmap,
1275                               &params.pt2,
1276                               glyphpath->prevElemP2.x,
1277                               glyphpath->prevElemP2.y );
1278      cf2_glyphpath_hintPoint( glyphpath,
1279                               hintmap,
1280                               &params.pt3,
1281                               glyphpath->prevElemP3.x,
1282                               glyphpath->prevElemP3.y );
1283
1284      glyphpath->callbacks->cubeTo( glyphpath->callbacks, &params );
1285
1286      glyphpath->currentDS = params.pt3;
1287
1288      break;
1289    }
1290
1291    if ( !useIntersection || close )
1292    {
1293      /* insert connecting line between end of previous element and start */
1294      /* of current one                                                   */
1295      /* note: at the end of a subpath, we might do both, so use `nextP0' */
1296      /* before we change it, below                                       */
1297
1298      cf2_glyphpath_hintPoint( glyphpath,
1299                               hintmap,
1300                               &params.pt1,
1301                               nextP0->x,
1302                               nextP0->y );
1303
1304      if ( params.pt1.x != glyphpath->currentDS.x ||
1305           params.pt1.y != glyphpath->currentDS.y )
1306      {
1307        /* length is nonzero */
1308        params.op  = CF2_PathOpLineTo;
1309        params.pt0 = glyphpath->currentDS;
1310
1311        /* note: pt2 and pt3 are unused */
1312        glyphpath->callbacks->lineTo( glyphpath->callbacks, &params );
1313
1314        glyphpath->currentDS = params.pt1;
1315      }
1316    }
1317
1318    if ( useIntersection )
1319    {
1320      /* return intersection point to caller */
1321      *nextP0 = intersection;
1322    }
1323  }
1324
1325
1326  /* push a MoveTo element based on current point and offset of current */
1327  /* element                                                            */
1328  static void
1329  cf2_glyphpath_pushMove( CF2_GlyphPath  glyphpath,
1330                          FT_Vector      start )
1331  {
1332    CF2_CallbackParamsRec  params;
1333
1334
1335    params.op  = CF2_PathOpMoveTo;
1336    params.pt0 = glyphpath->currentDS;
1337
1338    /* Test if move has really happened yet; it would have called */
1339    /* `cf2_hintmap_build' to set `isValid'.                   */
1340    if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) )
1341    {
1342      /* we are here iff first subpath is missing a moveto operator: */
1343      /* synthesize first moveTo to finish initialization of hintMap */
1344      cf2_glyphpath_moveTo( glyphpath,
1345                            glyphpath->start.x,
1346                            glyphpath->start.y );
1347    }
1348
1349    cf2_glyphpath_hintPoint( glyphpath,
1350                             &glyphpath->hintMap,
1351                             &params.pt1,
1352                             start.x,
1353                             start.y );
1354
1355    /* note: pt2 and pt3 are unused */
1356    glyphpath->callbacks->moveTo( glyphpath->callbacks, &params );
1357
1358    glyphpath->currentDS    = params.pt1;
1359    glyphpath->offsetStart0 = start;
1360  }
1361
1362
1363  /*
1364   * All coordinates are in character space.
1365   * On input, (x1, y1) and (x2, y2) give line segment.
1366   * On output, (x, y) give offset vector.
1367   * We use a piecewise approximation to trig functions.
1368   *
1369   * TODO: Offset true perpendicular and proper length
1370   *       supply the y-translation for hinting here, too,
1371   *       that adds yOffset unconditionally to *y.
1372   */
1373  static void
1374  cf2_glyphpath_computeOffset( CF2_GlyphPath  glyphpath,
1375                               CF2_Fixed      x1,
1376                               CF2_Fixed      y1,
1377                               CF2_Fixed      x2,
1378                               CF2_Fixed      y2,
1379                               CF2_Fixed*     x,
1380                               CF2_Fixed*     y )
1381  {
1382    CF2_Fixed  dx = x2 - x1;
1383    CF2_Fixed  dy = y2 - y1;
1384
1385
1386    /* note: negative offsets don't work here; negate deltas to change */
1387    /* quadrants, below                                                */
1388    if ( glyphpath->font->reverseWinding )
1389    {
1390      dx = -dx;
1391      dy = -dy;
1392    }
1393
1394    *x = *y = 0;
1395
1396    if ( !glyphpath->darken )
1397        return;
1398
1399    /* add momentum for this path element */
1400    glyphpath->callbacks->windingMomentum +=
1401      cf2_getWindingMomentum( x1, y1, x2, y2 );
1402
1403    /* note: allow mixed integer and fixed multiplication here */
1404    if ( dx >= 0 )
1405    {
1406      if ( dy >= 0 )
1407      {
1408        /* first quadrant, +x +y */
1409
1410        if ( dx > 2 * dy )
1411        {
1412          /* +x */
1413          *x = 0;
1414          *y = 0;
1415        }
1416        else if ( dy > 2 * dx )
1417        {
1418          /* +y */
1419          *x = glyphpath->xOffset;
1420          *y = glyphpath->yOffset;
1421        }
1422        else
1423        {
1424          /* +x +y */
1425          *x = FT_MulFix( cf2_floatToFixed( 0.7 ),
1426                          glyphpath->xOffset );
1427          *y = FT_MulFix( cf2_floatToFixed( 1.0 - 0.7 ),
1428                          glyphpath->yOffset );
1429        }
1430      }
1431      else
1432      {
1433        /* fourth quadrant, +x -y */
1434
1435        if ( dx > -2 * dy )
1436        {
1437          /* +x */
1438          *x = 0;
1439          *y = 0;
1440        }
1441        else if ( -dy > 2 * dx )
1442        {
1443          /* -y */
1444          *x = -glyphpath->xOffset;
1445          *y = glyphpath->yOffset;
1446        }
1447        else
1448        {
1449          /* +x -y */
1450          *x = FT_MulFix( cf2_floatToFixed( -0.7 ),
1451                          glyphpath->xOffset );
1452          *y = FT_MulFix( cf2_floatToFixed( 1.0 - 0.7 ),
1453                          glyphpath->yOffset );
1454        }
1455      }
1456    }
1457    else
1458    {
1459      if ( dy >= 0 )
1460      {
1461        /* second quadrant, -x +y */
1462
1463        if ( -dx > 2 * dy )
1464        {
1465          /* -x */
1466          *x = 0;
1467          *y = 2 * glyphpath->yOffset;
1468        }
1469        else if ( dy > -2 * dx )
1470        {
1471          /* +y */
1472          *x = glyphpath->xOffset;
1473          *y = glyphpath->yOffset;
1474        }
1475        else
1476        {
1477          /* -x +y */
1478          *x = FT_MulFix( cf2_floatToFixed( 0.7 ),
1479                          glyphpath->xOffset );
1480          *y = FT_MulFix( cf2_floatToFixed( 1.0 + 0.7 ),
1481                          glyphpath->yOffset );
1482        }
1483      }
1484      else
1485      {
1486        /* third quadrant, -x -y */
1487
1488        if ( -dx > -2 * dy )
1489        {
1490          /* -x */
1491          *x = 0;
1492          *y = 2 * glyphpath->yOffset;
1493        }
1494        else if ( -dy > -2 * dx )
1495        {
1496          /* -y */
1497          *x = -glyphpath->xOffset;
1498          *y = glyphpath->xOffset;
1499        }
1500        else
1501        {
1502          /* -x -y */
1503          *x = FT_MulFix( cf2_floatToFixed( -0.7 ),
1504                          glyphpath->xOffset );
1505          *y = FT_MulFix( cf2_floatToFixed( 1.0 + 0.7 ),
1506                          glyphpath->yOffset );
1507        }
1508      }
1509    }
1510  }
1511
1512
1513  FT_LOCAL_DEF( void )
1514  cf2_glyphpath_moveTo( CF2_GlyphPath  glyphpath,
1515                        CF2_Fixed      x,
1516                        CF2_Fixed      y )
1517  {
1518    cf2_glyphpath_closeOpenPath( glyphpath );
1519
1520    /* save the parameters of the move for later, when we'll know how to */
1521    /* offset it;                                                        */
1522    /* also save last move point */
1523    glyphpath->currentCS.x = glyphpath->start.x = x;
1524    glyphpath->currentCS.y = glyphpath->start.y = y;
1525
1526    glyphpath->moveIsPending = TRUE;
1527
1528    /* ensure we have a valid map with current mask */
1529    if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) ||
1530         cf2_hintmask_isNew( glyphpath->hintMask )   )
1531      cf2_hintmap_build( &glyphpath->hintMap,
1532                         glyphpath->hStemHintArray,
1533                         glyphpath->vStemHintArray,
1534                         glyphpath->hintMask,
1535                         glyphpath->hintOriginY,
1536                         FALSE );
1537
1538    /* save a copy of current HintMap to use when drawing initial point */
1539    glyphpath->firstHintMap = glyphpath->hintMap;     /* structure copy */
1540  }
1541
1542
1543  FT_LOCAL_DEF( void )
1544  cf2_glyphpath_lineTo( CF2_GlyphPath  glyphpath,
1545                        CF2_Fixed      x,
1546                        CF2_Fixed      y )
1547  {
1548    CF2_Fixed  xOffset, yOffset;
1549    FT_Vector  P0, P1;
1550
1551
1552    /* can't compute offset of zero length line, so ignore them */
1553    if ( glyphpath->currentCS.x == x && glyphpath->currentCS.y == y )
1554      return;
1555
1556    cf2_glyphpath_computeOffset( glyphpath,
1557                                 glyphpath->currentCS.x,
1558                                 glyphpath->currentCS.y,
1559                                 x,
1560                                 y,
1561                                 &xOffset,
1562                                 &yOffset );
1563
1564    /* construct offset points */
1565    P0.x = glyphpath->currentCS.x + xOffset;
1566    P0.y = glyphpath->currentCS.y + yOffset;
1567    P1.x = x + xOffset;
1568    P1.y = y + yOffset;
1569
1570    if ( glyphpath->moveIsPending )
1571    {
1572      /* emit offset 1st point as MoveTo */
1573      cf2_glyphpath_pushMove( glyphpath, P0 );
1574
1575      glyphpath->moveIsPending = FALSE;  /* adjust state machine */
1576      glyphpath->pathIsOpen    = TRUE;
1577
1578      glyphpath->offsetStart1 = P1;              /* record second point */
1579    }
1580
1581    if ( glyphpath->elemIsQueued )
1582    {
1583      FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) );
1584
1585      cf2_glyphpath_pushPrevElem( glyphpath,
1586                                  &glyphpath->hintMap,
1587                                  &P0,
1588                                  P1,
1589                                  FALSE );
1590    }
1591
1592    /* queue the current element with offset points */
1593    glyphpath->elemIsQueued = TRUE;
1594    glyphpath->prevElemOp   = CF2_PathOpLineTo;
1595    glyphpath->prevElemP0   = P0;
1596    glyphpath->prevElemP1   = P1;
1597
1598    /* update current map */
1599    if ( cf2_hintmask_isNew( glyphpath->hintMask ) )
1600      cf2_hintmap_build( &glyphpath->hintMap,
1601                         glyphpath->hStemHintArray,
1602                         glyphpath->vStemHintArray,
1603                         glyphpath->hintMask,
1604                         glyphpath->hintOriginY,
1605                         FALSE );
1606
1607    glyphpath->currentCS.x = x;     /* pre-offset current point */
1608    glyphpath->currentCS.y = y;
1609  }
1610
1611
1612  FT_LOCAL_DEF( void )
1613  cf2_glyphpath_curveTo( CF2_GlyphPath  glyphpath,
1614                         CF2_Fixed      x1,
1615                         CF2_Fixed      y1,
1616                         CF2_Fixed      x2,
1617                         CF2_Fixed      y2,
1618                         CF2_Fixed      x3,
1619                         CF2_Fixed      y3 )
1620  {
1621    CF2_Fixed  xOffset1, yOffset1, xOffset3, yOffset3;
1622    FT_Vector  P0, P1, P2, P3;
1623
1624
1625    /* TODO: ignore zero length portions of curve?? */
1626    cf2_glyphpath_computeOffset( glyphpath,
1627                                 glyphpath->currentCS.x,
1628                                 glyphpath->currentCS.y,
1629                                 x1,
1630                                 y1,
1631                                 &xOffset1,
1632                                 &yOffset1 );
1633    cf2_glyphpath_computeOffset( glyphpath,
1634                                 x2,
1635                                 y2,
1636                                 x3,
1637                                 y3,
1638                                 &xOffset3,
1639                                 &yOffset3 );
1640
1641    /* add momentum from the middle segment */
1642    glyphpath->callbacks->windingMomentum +=
1643      cf2_getWindingMomentum( x1, y1, x2, y2 );
1644
1645    /* construct offset points */
1646    P0.x = glyphpath->currentCS.x + xOffset1;
1647    P0.y = glyphpath->currentCS.y + yOffset1;
1648    P1.x = x1 + xOffset1;
1649    P1.y = y1 + yOffset1;
1650    /* note: preserve angle of final segment by using offset3 at both ends */
1651    P2.x = x2 + xOffset3;
1652    P2.y = y2 + yOffset3;
1653    P3.x = x3 + xOffset3;
1654    P3.y = y3 + yOffset3;
1655
1656    if ( glyphpath->moveIsPending )
1657    {
1658      /* emit offset 1st point as MoveTo */
1659      cf2_glyphpath_pushMove( glyphpath, P0 );
1660
1661      glyphpath->moveIsPending = FALSE;
1662      glyphpath->pathIsOpen    = TRUE;
1663
1664      glyphpath->offsetStart1 = P1;              /* record second point */
1665    }
1666
1667    if ( glyphpath->elemIsQueued )
1668    {
1669      FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) );
1670
1671      cf2_glyphpath_pushPrevElem( glyphpath,
1672                                  &glyphpath->hintMap,
1673                                  &P0,
1674                                  P1,
1675                                  FALSE );
1676    }
1677
1678    /* queue the current element with offset points */
1679    glyphpath->elemIsQueued = TRUE;
1680    glyphpath->prevElemOp   = CF2_PathOpCubeTo;
1681    glyphpath->prevElemP0   = P0;
1682    glyphpath->prevElemP1   = P1;
1683    glyphpath->prevElemP2   = P2;
1684    glyphpath->prevElemP3   = P3;
1685
1686    /* update current map */
1687    if ( cf2_hintmask_isNew( glyphpath->hintMask ) )
1688      cf2_hintmap_build( &glyphpath->hintMap,
1689                         glyphpath->hStemHintArray,
1690                         glyphpath->vStemHintArray,
1691                         glyphpath->hintMask,
1692                         glyphpath->hintOriginY,
1693                         FALSE );
1694
1695    glyphpath->currentCS.x = x3;       /* pre-offset current point */
1696    glyphpath->currentCS.y = y3;
1697  }
1698
1699
1700  FT_LOCAL_DEF( void )
1701  cf2_glyphpath_closeOpenPath( CF2_GlyphPath  glyphpath )
1702  {
1703    if ( glyphpath->pathIsOpen )
1704    {
1705      FT_ASSERT( cf2_hintmap_isValid( &glyphpath->firstHintMap ) );
1706
1707      /* since we need to apply an offset to the implicit lineto, we make */
1708      /* it explicit here                                                 */
1709      cf2_glyphpath_lineTo( glyphpath,
1710                            glyphpath->start.x,
1711                            glyphpath->start.y );
1712
1713      /* Draw previous element (the explicit LineTo we just created,      */
1714      /* above) and connect it to the start point, but with the offset we */
1715      /* saved from the first element.                                    */
1716      /* Use the saved HintMap, too. */
1717      FT_ASSERT( glyphpath->elemIsQueued );
1718
1719      cf2_glyphpath_pushPrevElem( glyphpath,
1720                                  &glyphpath->firstHintMap,
1721                                  &glyphpath->offsetStart0,
1722                                  glyphpath->offsetStart1,
1723                                  TRUE );
1724
1725      /* reset state machine */
1726      glyphpath->moveIsPending = TRUE;
1727      glyphpath->pathIsOpen    = FALSE;
1728      glyphpath->elemIsQueued  = FALSE;
1729    }
1730  }
1731
1732
1733/* END */
1734