home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-387-Vol-3of3.iso / g / gs252src.zip / GS252 / GXHINT1.C < prev    next >
C/C++ Source or Header  |  1992-09-15  |  16KB  |  475 lines

  1. /* Copyright (C) 1990, 1992 Aladdin Enterprises.  All rights reserved.
  2.    Distributed by Free Software Foundation, Inc.
  3.  
  4. This file is part of Ghostscript.
  5.  
  6. Ghostscript is distributed in the hope that it will be useful, but
  7. WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
  8. to anyone for the consequences of using it or for whether it serves any
  9. particular purpose or works at all, unless he says so in writing.  Refer
  10. to the Ghostscript General Public License for full details.
  11.  
  12. Everyone is granted permission to copy, modify and redistribute
  13. Ghostscript, but only under the conditions described in the Ghostscript
  14. General Public License.  A copy of this license is supposed to have been
  15. given to you along with Ghostscript so you can know your rights and
  16. responsibilities.  It should be in a file named COPYING.  Among other
  17. things, the copyright notice and this notice must be preserved on all
  18. copies.  */
  19.  
  20. /* gxhint1.c */
  21. /* Adobe Type 1 font hint routines */
  22. #include "gx.h"
  23. #include "gserrors.h"
  24. #include "gxarith.h"
  25. #include "gxfixed.h"
  26. #include "gxmatrix.h"
  27. #include "gzstate.h"
  28. #include "gzdevice.h"            /* for gxchar */
  29. #include "gxdevmem.h"            /* ditto */
  30. #include "gxchar.h"
  31. #include "gxfont.h"
  32. #include "gxtype1.h"
  33. #include "gxop1.h"
  34.  
  35. /* Define the tolerance for testing whether a point is in a zone, */
  36. /* in device pixels.  (Maybe this should be variable??) */
  37. #define stem_tolerance float2fixed(0.05)
  38.  
  39. /* ------ Initialization ------ */
  40.  
  41. typedef zone_table(1) a_zone_table;
  42. private alignment_zone *near
  43.   compute_zones(P6(const gs_matrix_fixed *, const font_hints *,
  44.     const a_zone_table *, const a_zone_table *, alignment_zone *, int));
  45. private void near
  46.   transform_zone(P4(const gs_matrix_fixed *, const font_hints *,
  47.     const float *, alignment_zone *));
  48.  
  49. /* Compute the font-level hints from the font and the matrix. */
  50. /* We should cache this with the font/matrix pair.... */
  51. void
  52. compute_font_hints(font_hints *pfh, const gs_matrix_fixed *pmat,
  53.   const gs_type1_data *pdata)
  54. {    int h_count = 0, v_count = 0;
  55.     alignment_zone *zp = &pfh->a_zones[0];
  56.     /* Figure out which hints, if any, to use, */
  57.     /* and the orientation of the axes. */
  58.     pfh->axes_swapped = pfh->x_inverted = pfh->y_inverted = 0;
  59.     pfh->use_x_hints = pfh->use_y_hints = 0;
  60.     if ( is_fzero(pmat->xy) )
  61.         pfh->y_inverted = is_fneg(pmat->yy),
  62.         pfh->use_y_hints = 1;
  63.     else if ( is_fzero(pmat->xx) )
  64.         pfh->y_inverted = is_fneg(pmat->xy),
  65.         pfh->axes_swapped = 1,
  66.         pfh->use_y_hints = 1;
  67.     if ( is_fzero(pmat->yx) )
  68.         pfh->x_inverted = is_fneg(pmat->xx),
  69.         pfh->use_x_hints = 1;
  70.     else if ( is_fzero(pmat->yy) )
  71.         pfh->x_inverted = is_fneg(pmat->yx),
  72.         pfh->axes_swapped = 1,
  73.         pfh->use_x_hints = 1;
  74.     if_debug6('1', "[1]ctm=[%g %g %g %g %g %g]\n",
  75.           pmat->xx, pmat->xy, pmat->yx, pmat->yy,
  76.           pmat->tx, pmat->ty);
  77.     if_debug5('1', "[1]swapped=%d, x/y_hints=%d,%d, x/y_inverted=%d,%d\n",
  78.           pfh->axes_swapped, pfh->use_x_hints, pfh->use_y_hints,
  79.           pfh->x_inverted, pfh->y_inverted);
  80.     /* Transform the actual hints. */
  81.     if ( pfh->use_x_hints )
  82.     {    gs_fixed_point hw;
  83.         fixed *hp = (pfh->axes_swapped ? &hw.y : &hw.x);
  84.         int i;
  85.         if ( pdata->StdHW.count )
  86.         {    gs_distance_transform2fixed(pmat,
  87.                 (float)pdata->StdHW.data[0], 0.0, &hw);
  88.             pfh->snap_h.data[0] = any_abs(*hp);
  89.             if_debug1('1', "[1]snap_h[0]=%g\n",
  90.                   fixed2float(pfh->snap_h.data[0]));
  91.             h_count = 1;
  92.         }
  93.         for ( i = 0; i < pdata->StemSnapH.count; i++ )
  94.         {    gs_distance_transform2fixed(pmat,
  95.                 (float)pdata->StemSnapH.data[i], 0.0, &hw);
  96.             pfh->snap_h.data[h_count++] = any_abs(*hp);
  97.             if_debug2('1', "[1]snap_h[%d]=%g\n", h_count - 1,
  98.                   fixed2float(pfh->snap_h.data[h_count - 1]));
  99.         }
  100.     }
  101.     if ( pfh->use_y_hints )
  102.     {    gs_fixed_point vw;
  103.         fixed *vp = (pfh->axes_swapped ? &vw.x : &vw.y);
  104.         int i;
  105.         /* Convert blue parameters to device pixels. */
  106.         gs_distance_transform2fixed(pmat, 0.0,
  107.                         (float)pdata->BlueFuzz, &vw);
  108.         pfh->blue_fuzz = any_abs(*vp);
  109.         gs_distance_transform2fixed(pmat, 0.0, 1.0, &vw);
  110.         pfh->suppress_overshoot = any_abs(*vp) < pdata->BlueScale;
  111.         gs_distance_transform2fixed(pmat, 0.0, pdata->BlueShift, &vw);
  112.         pfh->blue_shift = any_abs(*vp);
  113.         /* Tweak up blue_shift if it is less than half a pixel. */
  114.         /* See the discussion of BlueShift in section 5.7 of */
  115.         /* "Adobe Type 1 Font Format." */
  116.         if ( pfh->blue_shift < fixed_half )
  117.             pfh->blue_shift = fixed_half;
  118.         if_debug5('1', "[1]blue_fuzz=%d->%g, blue_shift=%g->%g, sup_ov=%d\n",
  119.               pdata->BlueFuzz, fixed2float(pfh->blue_fuzz),
  120.               pdata->BlueShift, fixed2float(pfh->blue_shift),
  121.               pfh->suppress_overshoot);
  122.         zp = compute_zones(pmat, pfh,
  123.                    (const a_zone_table *)&pdata->BlueValues,
  124.                    (const a_zone_table *)&pdata->FamilyBlues,
  125.                    zp, 1);
  126.         zp = compute_zones(pmat, pfh,
  127.                    (const a_zone_table *)&pdata->OtherBlues,
  128.                    (const a_zone_table *)&pdata->FamilyOtherBlues,
  129.                    zp, max_OtherBlues);
  130.         if ( pdata->StdVW.count )
  131.         {    gs_distance_transform2fixed(pmat, 0.0,
  132.                 (float)pdata->StdVW.data[0], &vw);
  133.             pfh->snap_v.data[0] = any_abs(*vp);
  134.             if_debug1('1', "[1]snap_v[0]=%g\n",
  135.                   fixed2float(pfh->snap_v.data[0]));
  136.             v_count = 1;
  137.         }
  138.         for ( i = 0; i < pdata->StemSnapV.count; i++ )
  139.         {    gs_distance_transform2fixed(pmat, 0.0,
  140.                 (float)pdata->StemSnapV.data[i], &vw);
  141.             pfh->snap_v.data[v_count++] = any_abs(*vp);
  142.             if_debug2('1', "[1]snap_v[%d]=%g\n", v_count - 1,
  143.                   fixed2float(pfh->snap_v.data[v_count - 1]));
  144.         }
  145.     }
  146.     pfh->snap_h.count = h_count;
  147.     pfh->snap_v.count = v_count;
  148.     pfh->a_zone_count = zp - &pfh->a_zones[0];
  149. }
  150.  
  151. /* Compute the alignment zones for one set of 'blue' values. */
  152. private alignment_zone *near
  153. compute_zones(const gs_matrix_fixed *pmat, const font_hints *pfh,
  154.   const a_zone_table *blues, const a_zone_table *family_blues,
  155.   alignment_zone *zp, int bottom_count)
  156. {    int i;
  157.     fixed fuzz = pfh->blue_fuzz;
  158.     int inverted =
  159.         (pfh->axes_swapped ? pfh->x_inverted : pfh->y_inverted);
  160.     for ( i = 0; i < blues->count; i += 2, zp++ )
  161.     {    const float *vp = &blues->data[i];
  162.         zp->is_top_zone = i >> 1 >= bottom_count;
  163.         transform_zone(pmat, pfh, vp, zp);
  164.         if_debug5('1', "[1]blues[%d]=%g,%g -> %g,%g\n",
  165.               i >> 1, vp[0], vp[1],
  166.               fixed2float(zp->v0), fixed2float(zp->v1));
  167.         if ( i < family_blues->count )
  168.         {    /* Check whether family blues should supersede. */
  169.             alignment_zone fz;
  170.             const float *fvp = &family_blues->data[i];
  171.             fixed diff;
  172.             transform_zone(pmat, pfh, fvp, &fz);
  173.             if_debug5('1', "[1]f_blues[%d]=%g,%g -> %g,%g\n",
  174.                   i >> 1, fvp[0], fvp[1],
  175.                   fixed2float(fz.v0), fixed2float(fz.v1));
  176.             diff = (zp->v1 - zp->v0) - (fz.v1 - fz.v0);
  177.             if ( diff > -fixed_1 && diff < fixed_1 )
  178.                 zp->v0 = fz.v0, zp->v1 = fz.v1;
  179.         }
  180.         /* Compute the flat position, and add the fuzz. */
  181.         if ( (inverted ? zp->is_top_zone : !zp->is_top_zone) )
  182.             zp->flat = zp->v1, zp->v0 -= fuzz;
  183.         else
  184.             zp->flat = zp->v0, zp->v1 += fuzz;
  185.     }
  186.     return zp;
  187. }
  188.  
  189. /* Transform a single alignment zone to device coordinates, */
  190. /* taking axis swapping into account. */
  191. private void near
  192. transform_zone(const gs_matrix_fixed *pmat, const font_hints *pfh,
  193.   const float *vp, alignment_zone *zp)
  194. {    gs_fixed_point p0, p1;
  195.     fixed v0, v1;
  196.     gs_point_transform2fixed(pmat, 0.0, vp[0], &p0);
  197.     gs_point_transform2fixed(pmat, 0.0, vp[1], &p1);
  198.     if ( pfh->axes_swapped ) v0 = p0.x, v1 = p1.x;
  199.     else v0 = p0.y, v1 = p1.y;
  200.     if ( v0 <= v1 ) zp->v0 = v0, zp->v1 = v1;
  201.     else zp->v0 = v1, zp->v1 = v0;
  202. }
  203.  
  204. /* Reset the stem hints. */
  205. void
  206. reset_stem_hints(register gs_type1_state *pis)
  207. {    pis->hstem_hints.count = 0;
  208.     pis->hstem_hints.current = &pis->hstem_hints.data[0];
  209.     pis->vstem_hints.count = 0;
  210.     pis->vstem_hints.current = &pis->vstem_hints.data[0];
  211. }
  212.  
  213. /* ------ Add hints ------ */
  214.  
  215. private stem_hint *near type1_stem(P3(stem_hint_table *, fixed, fixed));
  216. private fixed near find_snap(P2(fixed, const stem_snap_table *));
  217. private alignment_zone *near
  218. find_zone(P3(gs_type1_state *, fixed, fixed));
  219.  
  220. #define c_fixed(d, c) m_fixed(fixed2int_var(d), c, pis->fc, max_coeff_bits)
  221.  
  222. /* Add a horizontal stem hint. */
  223. void
  224. type1_hstem(register gs_type1_state *pis, fixed y, fixed dy)
  225. {    stem_hint *psh;
  226.     alignment_zone *pz;
  227.     fixed v, dv, adj_dv;
  228.     fixed vtop, vbot;
  229.     fixed center, diff_v, diff2_dv;
  230.     if ( !pis->fh.use_y_hints ) return;
  231.     y += pis->lsb.y;
  232.     if ( pis->fh.axes_swapped )
  233.         v = pis->vs_offset.x + c_fixed(y, yx) +
  234.             pis->pgs->ctm.tx_fixed,
  235.         dv = c_fixed(dy, yx);
  236.     else
  237.         v = pis->vs_offset.y + c_fixed(y, yy) +
  238.             pis->pgs->ctm.ty_fixed,
  239.         dv = c_fixed(dy, yy);
  240.     if ( dy < 0 )
  241.         vbot = v + dv, vtop = v;
  242.     else
  243.         vbot = v, vtop = v + dv;
  244.     if ( dv < 0 ) v += dv, dv = -dv;
  245.     psh = type1_stem(&pis->hstem_hints, v, dv);
  246.     if ( psh == 0 ) return;
  247.     adj_dv = find_snap(dv, &pis->fh.snap_h);
  248.     pz = find_zone(pis, vbot, vtop);
  249.     if ( pz != 0 )
  250.     {    /* Use the alignment zone to align the outer stem edge. */
  251.         int inverted =
  252.           (pis->fh.axes_swapped ? pis->fh.x_inverted : pis->fh.y_inverted);
  253.         int adjust_v1 =
  254.           (inverted ? !pz->is_top_zone : pz->is_top_zone);
  255.         fixed flat_v = pz->flat;
  256.         fixed overshoot =
  257.             (pz->is_top_zone ? vtop - flat_v : flat_v - vbot);
  258.         fixed pos_over =
  259.             (inverted ? -overshoot : overshoot);
  260.         fixed ddv = adj_dv - dv;
  261.         fixed shift = fixed_rounded(flat_v) - flat_v;
  262.         if ( pos_over > 0 )
  263.         { if ( pos_over < pis->fh.blue_shift || pis->fh.suppress_overshoot )
  264.           {    /* Character is small, suppress overshoot. */
  265.             if_debug0('1', "[1]suppress overshoot\n");
  266.             if ( pz->is_top_zone )
  267.                 shift -= overshoot;
  268.             else
  269.                 shift += overshoot;
  270.           }
  271.           else if ( pos_over < fixed_1 )
  272.           {    /* Enforce overshoot. */
  273.             if_debug0('1', "[1]enforce overshoot\n");
  274.             if ( overshoot < 0 )
  275.                 overshoot = -fixed_1 - overshoot;
  276.             else
  277.                 overshoot = fixed_1 - overshoot;
  278.             if ( pz->is_top_zone )
  279.                 shift += overshoot;
  280.             else
  281.                 shift -= overshoot;
  282.           }
  283.         }
  284.         if ( adjust_v1 )
  285.             psh->dv1 = shift, psh->dv0 = shift - ddv;
  286.         else
  287.             psh->dv0 = shift, psh->dv1 = shift + ddv;
  288.         if_debug4('1', "[1]flat_v = %g, overshoot = %g, dv=%g,%g\n",
  289.               fixed2float(flat_v), fixed2float(overshoot),
  290.               fixed2float(psh->dv0), fixed2float(psh->dv1));
  291.         return;
  292.     }
  293.     /* Align the stem so its edges fall on pixel boundaries, */
  294.     /* moving the center as little as possible. */
  295.     center = v + arith_rshift_1(dv);
  296.     if ( adj_dv & fixed_1 )
  297.     {    /* Odd width, align center on half-pixel. */
  298.         center += fixed_half;
  299.     }
  300.     diff_v = fixed_rounded(center) - center;
  301.     diff2_dv = arith_rshift_1(adj_dv - dv);
  302.     psh->dv0 = diff_v - diff2_dv;
  303.     psh->dv1 = diff_v + diff2_dv;
  304.     if_debug6('1', "[1]hstem %g,%g -> %g,%g ; d = %g,%g\n",
  305.           fixed2float(y), fixed2float(dy),
  306.           fixed2float(v), fixed2float(dv),
  307.           fixed2float(psh->dv0), fixed2float(psh->dv1));
  308. }
  309.  
  310. /* Add a vertical stem hint. */
  311. void
  312. type1_vstem(register gs_type1_state *pis, fixed x, fixed dx)
  313. {    stem_hint *psh;
  314.     fixed v, dv, adj_dv;
  315.     fixed center, diff_v, diff2_dv;
  316.     if ( !pis->fh.use_x_hints ) return;
  317.     x += pis->lsb.x;
  318.     if ( pis->fh.axes_swapped )
  319.         v = pis->vs_offset.y + c_fixed(x, xy) +
  320.             pis->pgs->ctm.ty_fixed,
  321.         dv = c_fixed(dx, xy);
  322.     else
  323.         v = pis->vs_offset.x + c_fixed(x, xx) +
  324.             pis->pgs->ctm.tx_fixed,
  325.         dv = c_fixed(dx, xx);
  326.     if ( dv < 0 ) v += dv, dv = -dv;
  327.     psh = type1_stem(&pis->vstem_hints, v, dv);
  328.     if ( psh == 0 ) return;
  329.     adj_dv = find_snap(dv, &pis->fh.snap_v);
  330.     if ( pis->pdata->ForceBold && adj_dv < fixed_1 )
  331.         adj_dv = fixed_1;
  332.     /* Align the stem so its edges fall on pixel boundaries, */
  333.     /* moving the center as little as possible. */
  334.     center = v + arith_rshift_1(dv);
  335.     if ( adj_dv & fixed_1 )
  336.     {    /* Odd width, align center on half-pixel. */
  337.         center += fixed_half;
  338.     }
  339.     diff_v = fixed_rounded(center) - center;
  340.     diff2_dv = arith_rshift_1(adj_dv - dv);
  341.     psh->dv0 = diff_v - diff2_dv;
  342.     psh->dv1 = diff_v + diff2_dv;
  343.     if_debug6('1', "[1]vstem %g,%g -> %g,%g ; d = %g,%g\n",
  344.           fixed2float(x), fixed2float(dx),
  345.           fixed2float(v), fixed2float(dv),
  346.           fixed2float(psh->dv0), fixed2float(psh->dv1));
  347. }
  348.  
  349. /* Add a stem hint, keeping the table sorted. */
  350. /* Return the stem hint pointer, or 0 if the table is full. */
  351. private stem_hint *near
  352. type1_stem(stem_hint_table *psht, fixed v0, fixed d)
  353. {    stem_hint *bot = &psht->data[0];
  354.     stem_hint *top = bot + psht->count;
  355.     if ( psht->count >= max_stems ) return 0;
  356.     while ( top > bot && v0 < top[-1].v0 )
  357.        {    *top = top[-1];
  358.         top--;
  359.        }
  360.     /* Add a little fuzz for insideness testing. */
  361.     top->v0 = v0 - stem_tolerance;
  362.     top->v1 = v0 + d + stem_tolerance;
  363.     psht->count++;
  364.     return top;
  365. }
  366.  
  367. /* Compute the adjusted width of a stem. */
  368. private fixed near
  369. find_snap(fixed dv, const stem_snap_table *psst)
  370. {    fixed best = fixed_1 * 2;
  371.     fixed adj_dv;
  372.     int i;
  373.     for ( i = 0; i < psst->count; i++ )
  374.     {    fixed diff = psst->data[i] - dv;
  375.         if ( any_abs(diff) < any_abs(best) )
  376.         {    if_debug3('1', "[1]snap %g to [%d]%g\n",
  377.                   fixed2float(dv), i,
  378.                   fixed2float(psst->data[i]));
  379.             best = diff;
  380.         }
  381.     }
  382.     adj_dv = fixed_rounded(any_abs(best) <= fixed_1 ? dv + best : dv);
  383.     if ( adj_dv == 0 ) adj_dv = fixed_1;
  384.     if_debug2('1', "[1]snap %g to %g\n",
  385.           fixed2float(dv), fixed2float(adj_dv));
  386.     return adj_dv;
  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 *near
  393. find_zone(gs_type1_state *pis, fixed vbot, fixed vtop)
  394. {    alignment_zone *pz;
  395.     for ( pz = &pis->fh.a_zones[pis->fh.a_zone_count]; --pz >= &pis->fh.a_zones[0]; )
  396.     {    fixed v = (pz->is_top_zone ? vtop : vbot);
  397.         if ( v >= pz->v0 && v <= pz->v1 )
  398.         {    if_debug2('1', "[1]stem crosses %s-zone %d\n",
  399.                   (pz->is_top_zone ? "top" : "bottom"),
  400.                   (int)(pz - &pis->fh.a_zones[0]));
  401.             return pz;
  402.         }
  403.     }
  404.     return 0;
  405. }
  406.  
  407. /* ------ Apply hints ------ */
  408.  
  409. private stem_hint *near
  410. search_hints(P2(stem_hint_table *, fixed));
  411.  
  412. /* Adjust a point according to the relevant hints. */
  413. /* x and y are the current point in device space after moving; */
  414. /* dx and dy are the delta components in character space. */
  415. void
  416. find_stem_hints(gs_type1_state *pis, fixed x, fixed y,
  417.   fixed dx, fixed dy, gs_fixed_point *ppt)
  418. {    ppt->x = x, ppt->y = y;
  419.     if ( pis->in_dotsection ) return;
  420.     /* Note that if use_x/y_hints is false, */
  421.     /* no entries ever get made in the stem hint tables, */
  422.     /* so we don't have to check those flags here. */
  423.     /* Check the vertical stem hints. */
  424.     if ( pis->vstem_hints.count )
  425.     {    fixed *pv = (pis->fh.axes_swapped ? &ppt->y : &ppt->x);
  426.         stem_hint *ph = search_hints(&pis->vstem_hints, *pv);
  427.         if ( ph != 0 )
  428.         {    /* Decide which side of the stem we are on. */
  429.             /* If we're moving horizontally, just use the */
  430.             /* x coordinate; otherwise, assume outside */
  431.             /* edges move clockwise and inside edges move */
  432.             /* counter-clockwise.  (This algorithm was */
  433.             /* taken from the IBM X11R5 rasterizer; I'm not */
  434.             /* sure I believe it.) */
  435. #define adjust_stem(pv, dxy, ph, inverted)\
  436.   *pv +=\
  437.     ((/*dy == 0 ?*/ *pv < arith_rshift_1(ph->v0 +\
  438.       ph->v1) /*: pis->fh.y_inverted ? dy < 0 : dy > 0*/) ?\
  439.      ph->dv0 : ph->dv1)
  440.             if_debug2('1', "[1]use vstem %d: %g",
  441.                   (int)(ph - &pis->vstem_hints.data[0]),
  442.                   fixed2float(*pv));
  443.             adjust_stem(pv, dy, ph, pis->fh.y_inverted);
  444.             if_debug1('1', " -> %g\n", fixed2float(*pv));
  445.         }
  446.     }
  447.     /* Check the horizontal stem hints. */
  448.     if ( pis->hstem_hints.count )
  449.     {    fixed *pv = (pis->fh.axes_swapped ? &ppt->x : &ppt->y);
  450.         stem_hint *ph = search_hints(&pis->hstem_hints, *pv);
  451.         if ( ph != 0 )
  452.         {    if_debug2('1', "[1]use hstem %d: %g",
  453.                   (int)(ph - &pis->hstem_hints.data[0]),
  454.                   fixed2float(*pv));
  455.             adjust_stem(pv, dx, ph, pis->fh.x_inverted);
  456.             if_debug1('1', " -> %g\n", fixed2float(*pv));
  457.         }
  458.     }
  459.     return;
  460. #undef adjust_stem
  461. }
  462.  
  463. /* Search one hint table for an adjustment. */
  464. private stem_hint *near
  465. search_hints(stem_hint_table *psht, fixed v)
  466. {    stem_hint *ph = psht->current;
  467.     if ( v >= ph->v0 && v <= ph->v1 ) return ph;
  468.     /* We don't bother with binary or even up/down search, */
  469.     /* because there won't be very many hints. */
  470.     for ( ph = &psht->data[psht->count]; --ph >= &psht->data[0]; )
  471.       if ( v >= ph->v0 && v <= ph->v1 )
  472.         return (psht->current = ph);
  473.     return 0;
  474. }
  475.