The R Project SVN R

Rev

Rev 69699 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/* PAUL MURRELL
   This is from the GNU plotutils libplot-2.3 distribution
   Several modifications have been made to use the R graphics engine
   for output.
   All references to HAVE_PROTOS removed
   All references to "plotter" replaced with references to "GEDevDesc"
*/

/* This file contains the internal method _falabel_hershey(), which plots a
   label using Hershey fonts.  Each character in a Hershey font is a
   sequence of pen motions, so this function simply calls fmoverel() and
   fcontrel() to `stroke' each character in the argument string.

   The width of the string in user units is returned.  The internal method
   _flabelwidth_hershey() is similar, but does not actually plot the label.
*/

/* PAUL MURRELL
   sys-defines.h not used
*/
/* #include "sys-defines.h" */

/* PAUL MURRELL
   extern.h renamed g_extern.h
*/
#include "g_extern.h"
#include "g_control.h"
#include "g_her_metr.h"

/* Shearing factor for oblique fonts, new_x = x + SHEAR * y  */

#define SHEAR (2.0/7.0)

/* Relative size of subscripts/superscripts (i.e. `indexical' size) */

#define SCRIPTSIZE (0.6)

/* Positioning of subscripts/superscripts */

#define SUBSCRIPT_DX 0.0
#define SUBSCRIPT_DY (-0.25)
#define SUPERSCRIPT_DX 0.0
#define SUPERSCRIPT_DY 0.4

/* Positioning of accents (in Hershey units).  UP_SHIFT is amount by which
   accents are raised when placed over upper-case letters.  RIGHT_SHIFT is
   applied as well, if the upper-case letter is italic. */

#define ACCENT_UP_SHIFT 7.0
#define ACCENT_RIGHT_SHIFT 2.0

/* Relative size of small Japanese Kana */
#define SMALL_KANA_SIZE 0.725

/* Hershey glyph arrays */

#define OCCIDENTAL 0
#define ORIENTAL 1

/* Location of first Kana in the occidental glyph arrays.  (Kana, unlike
   Kanji, are placed in the occidental array, at the very end.) */
#define BEGINNING_OF_KANA 4195

/* PAUL MURRELL
   Structure to contain vfont current position
*/
typedef struct {
    double currX;
    double currY;
    double angle;
} vfontContext;

/* forward references */
static bool _composite_char (unsigned char *composite,
                 unsigned char *character,
                 unsigned char *accent);
static void _draw_stroke (vfontContext *vc, const pGEcontext gc,
              pGEDevDesc dd,
              bool pendown, double deltax, double deltay);
static double _label_width_hershey (const pGEcontext gc, pGEDevDesc dd,
                    const unsigned short *label);
static void _draw_hershey_string (vfontContext *vc, const pGEcontext gc,
                  pGEDevDesc dd,
                  const unsigned short *string);

/* _draw_hershey_stroke() draws a stroke, taking into account the
   transformation from Hershey units to user units, and also the current
   transformation matrix (as set by the user).  _draw_stroke is similar,
   but takes arguments in user units. */
/* PAUL MURRELL
   _draw_stroke now takes arguments in INCHES;  it needs to work in
   an absolute coordinate system because it does the rotation
*/

static
void _draw_hershey_stroke (vfontContext *vc, const pGEcontext gc, pGEDevDesc dd,
               bool pendown, double deltax, double deltay)
{
    _draw_stroke(vc, gc, dd, pendown,
         fromDeviceWidth(HERSHEY_X_UNITS_TO_USER_UNITS (deltax),
                 GE_INCHES, dd),
         fromDeviceHeight(HERSHEY_Y_UNITS_TO_USER_UNITS (deltay),
                  GE_INCHES, dd));
}

static void moverel(double dx, double dy, vfontContext *vc) {
    vc->currX += dx;
    vc->currY += dy;
}

static void linerel(double dx, double dy,
            vfontContext *vc, const pGEcontext gc,
            pGEDevDesc dd) {
    GELine(toDeviceX(vc->currX, GE_INCHES, dd),
       toDeviceY(vc->currY, GE_INCHES, dd),
       toDeviceX(vc->currX+dx, GE_INCHES, dd),
       toDeviceY(vc->currY+dy, GE_INCHES, dd),
       gc, dd);
    vc->currX += dx;
    vc->currY += dy;
}

/* e.g. for some Windows headers */
#ifndef M_PI
#define M_PI        3.141592653589793238462643383279502884197169399375
#endif

static void _draw_stroke (vfontContext *vc, const pGEcontext gc, pGEDevDesc dd,
              bool pendown, double deltax, double deltay)
{
  double dx, dy;
  double theta;

  theta = M_PI * vc->angle / 180.0;

  dx = cos(theta) * deltax - sin(theta) * deltay;
  dy = sin(theta) * deltax + cos(theta) * deltay;

  if (pendown)
      linerel(dx, dy, vc, gc, dd);
  else
      moverel(dx, dy, vc);
}

/* PAUL MURRELL
   Renamed from _g_flabelwidth_hershey to GVStrWidth
   ... and added unit argument
   ... and rearranged order of arguments
*/

/* this is the version of the flabelwidth() method that is specific to the
   case when the current Plotter font is a Hershey font; called in
   g_flabelwidth () .
   
   Argument 'enc' is ignored: these are in their own encoding.
*/
attribute_hidden
double R_GE_VStrWidth (const char *s, cetype_t enc, 
               const pGEcontext gc, pGEDevDesc dd)
{
  double label_width;
  unsigned short *codestring;

  /* PAUL MURRELL
     _controlify using R_alloc instead of xmalloc so need to do
     vmaxget() ... vmaxset() instead of free()
  */

  const void *vmax = vmaxget();

  /* convert string to a codestring, including annotations */
  codestring = _controlify (dd, (const unsigned char *) s,
                gc->fontfamily[7] - 1, gc->fontface);

  label_width = _label_width_hershey (gc, dd, codestring);

  vmaxset(vmax);
  /*  free (codestring); */

  return label_width;
}

/* PAUL MURRELL
   Added _label_height_hershey and GVStrHeight function
*/

static double _label_height_hershey (const pGEcontext gc,
                     pGEDevDesc dd,
                     const unsigned short *label)
{
    return( HERSHEY_Y_UNITS_TO_USER_UNITS(HERSHEY_LARGE_CAPHEIGHT) );
}

attribute_hidden
double R_GE_VStrHeight (const char *s, cetype_t enc, 
            const pGEcontext gc, pGEDevDesc dd)
{
  double label_height;
  unsigned short *codestring;

  const void *vmax = vmaxget();

  /* convert string to a codestring, including annotations */
  codestring = _controlify (dd, (const unsigned char *) s,
                gc->fontfamily[7] - 1, gc->fontface);

  label_height = _label_height_hershey (gc, dd, codestring);

  vmaxset(vmax);

  return label_height;
}

/* PAUL MURRELL
   This guy is the entry point to the GNU code
   Renamed from _g_falabel_hershey to GVText
   Reordered arguments
   Added x, y, rotation, and font specification arguments
   Re-typed x/y_justify from int to double
   Re-typed s from "unsigned char *" to char *
   Changed return value from double to void
*/

/* this is the version of the falabel() method that is specific
   to the case when the current Plotter font is a Hershey font */
attribute_hidden
void R_GE_VText (double x, double y, const char *s, cetype_t enc,
         double x_justify, double y_justify, double rotation,
         const pGEcontext gc, pGEDevDesc dd)
{
  unsigned short *codestring;
  double label_width, label_height;
  double x_offset, y_offset;
/*  double x_displacement; */

  /* PAUL MURRELL
     _controlify using R_alloc instead of xmalloc so need to do
     vmaxget() ... vmaxset() instead of free()
  */
  const void *vmax = vmaxget();

  /* PAUL MURRELL
     initialise the local currX and currY
     work in INCHES because that is what moverel and linerel work in
  */
  vfontContext vc;
  vc.currX = fromDeviceX(x, GE_INCHES, dd);
  vc.currY = fromDeviceY(y, GE_INCHES, dd);
  vc.angle = rotation;

  /* PAUL MURRELL
   * Override gc settings for lty and lwd
   */
  gc->lty = LTY_SOLID;
  gc->lwd = HERSHEY_LINE_WIDTH_TO_LWD (HERSHEY_STROKE_WIDTH);
  gc->lend = GE_ROUND_CAP;
  gc->ljoin = GE_ROUND_JOIN;

  /* convert string to a codestring, including annotations */
  codestring = _controlify (dd, (const unsigned char *)s,
                gc->fontfamily[7] - 1, gc->fontface);


  /* PAUL MURRELL
     Justification changed from char (e.g., 'l', 'c', 'r') to
     double (e.g., 0, .5, 1)
     Also added handling of NaN x_justify and y_justify
     ... and replaced HERSHEY_Y_UNITS_TO_USER_UNITS(HERSHEY_HEIGHT)
     with HERSHEY_Y_UNITS_TO_USER_UNITS(HERSHEY_LARGE_CAPHEIGHT)
     ... and replaced ((double)HERSHEY_ASCENT / (double)HERSHEY_HEIGHT)
     with 1.0
  */

  /* dimensions of the string, in user units */
  label_width = _label_width_hershey (gc, dd, codestring);
  label_height = _label_height_hershey(gc, dd, codestring);

  if (!R_FINITE(x_justify))
      x_justify = 0.5;
  if (!R_FINITE(y_justify))
      y_justify = 0.5;

  x_offset = 0 - x_justify;
  /* x_displacement = 1 - 2 * x_justify; */

  y_offset = 0 - y_justify * 1.0;

  /* save relevant drawing attributes, and restore them later */

  {
/*
    char *old_line_mode, *old_cap_mode, *old_join_mode;
    int old_fill_type;
    double oldposx, oldposy;
    bool old_dash_array_in_effect;

    old_line_mode = (char *)_plot_xmalloc (strlen (_plotter->drawstate->line_mode) + 1);
    old_cap_mode = (char *)_plot_xmalloc (strlen (_plotter->drawstate->cap_mode) + 1);
    old_join_mode = (char *)_plot_xmalloc (strlen (_plotter->drawstate->join_mode) + 1);
    oldposx = (_plotter->drawstate->pos).x;
    oldposy = (_plotter->drawstate->pos).y;

    strcpy (old_line_mode, _plotter->drawstate->line_mode);
    strcpy (old_cap_mode, _plotter->drawstate->cap_mode);
    strcpy (old_join_mode, _plotter->drawstate->join_mode);
    old_fill_type = _plotter->drawstate->fill_type;
    old_dash_array_in_effect = _plotter->drawstate->dash_array_in_effect;
*/
    /* Our choices for rendering: solid lines, rounded capitals and joins,
       a line width equal to slightly more than 1 Hershey unit, and
       transparent filling. */
/*
    _plotter->linemod (R___(_plotter) "solid");
    _plotter->capmod (R___(_plotter) "round");
    _plotter->joinmod (R___(_plotter) "round");
    _plotter->filltype (R___(_plotter) 0);
*/

    /* move to take horizontal and vertical justification into account;
       arguments here are in user units */
      /* PAUL MURRELL
     arguments now in INCHES because _draw_stroke does rotation
     so it needs to use absolute coordinates
      */
      _draw_stroke (&vc, gc, dd,
            false,
            fromDeviceWidth(x_offset * label_width, GE_INCHES, dd),
            fromDeviceHeight(y_offset * label_height, GE_INCHES, dd));
    /* call stroker on the sequence of strokes obtained from each char (the
       stroker may manipulate the line width) */
      /* _draw_hershey_stroke (dd, true, 0, HERSHEY_EM);     */
      _draw_hershey_string (&vc, gc, dd, codestring);

    /* Restore original values of relevant drawing attributes, free
       storage.  endpath() will be invoked in here automatically, flushing
       the created polyline object comprising the stroked text. */
/*
    _plotter->linemod (R___(_plotter) old_line_mode);
    _plotter->capmod (R___(_plotter) old_cap_mode);
    _plotter->joinmod (R___(_plotter) old_join_mode);
    _plotter->filltype (R___(_plotter) old_fill_type);
    _plotter->drawstate->dash_array_in_effect = old_dash_array_in_effect;

    free (old_line_mode);
    free (old_cap_mode);
    free (old_join_mode);
*/
    /* return to original position */
      /*    _plotter->fmove (R___(_plotter) oldposx, oldposy); */
  }

  /* amount by which to shift after printing label (user units) */
/*
  postdx = x_displacement * label_width;
  theta = M_PI * rotation / 180.0;
  dx = cos (theta) * postdx
    - sin (theta) * 0;
  dy = sin (theta) * postdx
    + cos (theta) * 0;

  moverel(dx, dy);
*/

  vmaxset(vmax);
  /*  free (codestring); */

  /* PAUL MURRELL
     No return value
  */
  /* return label_width;         user units */
}

/* In addition to scaling the character sizes and the `width', we perform
   the following (dx, dy):

   enter subscript  (dx, dy) = (-1/9, -1/2) * width
   exit subscript   (dx, dy) = (+1/6, +1/2) * width

   enter superscript    (dx, dy) = (-1/9, +1/2) * width
   exit superscript (dx, dy) = (+1/6, -1/2) * width

   For clarity here, `width' refers to the width _before_ it is
   multiplied by a factor 2/3.

   [N.B. In Bob Beach's original UGS character stroke generator,
   the +1/6's here were +2/9 instead.  Better?] */

/* _label_width_hershey() computes the width (total delta x) of a
   controlified character string to be rendered in a vector font, in user
   units */
static double _label_width_hershey (const pGEcontext gc, pGEDevDesc dd,
                    const unsigned short *label)
{
  const unsigned short *ptr = label;
  unsigned short c;
  double charsize = 1.0;    /* relative char size, 1.0 means full size */
  double saved_charsize = 1.0;
  double width = 0.0;       /* label width */
  double saved_width = 0.0;

  /* loop through unsigned shorts in label */
  while ((c = (*ptr)) != (unsigned short)'\0')
    {
      int glyphnum;     /* glyph in Hershey array */
      const unsigned char *glyph;

      if (c & RAW_HERSHEY_GLYPH)
    /* glyph was spec'd via an escape, not as a char in a font */
    {
      glyphnum = c & GLYPH_SPEC;
      glyph = (const unsigned char *)(_occidental_hershey_glyphs[glyphnum]);

      if (*glyph != '\0')   /* nonempty glyph */
        /* 1st two chars are bounds */
        width += charsize * ((int)glyph[1] - (int)glyph[0]);
    }
      else if (c & RAW_ORIENTAL_HERSHEY_GLYPH)
    /* glyph was spec'd via an escape, not as a char in a font */
    {
      glyphnum = c & GLYPH_SPEC;
      glyph = (const unsigned char *)_oriental_hershey_glyphs[glyphnum];

      if (*glyph != '\0')   /* nonempty glyph */
        /* 1st two chars are bounds */
        width += charsize * ((int)glyph[1] - (int)glyph[0]);
    }
      else if (c & CONTROL_CODE)    /* parse control code */
    {
      switch (c & ~CONTROL_CODE)
        {
        case C_BEGIN_SUBSCRIPT:
        case C_BEGIN_SUPERSCRIPT :
          charsize *= SCRIPTSIZE;
          break;

        case C_END_SUBSCRIPT:
        case C_END_SUPERSCRIPT:
          charsize /= SCRIPTSIZE;
          break;

        case C_PUSH_LOCATION:
          saved_width = width;
          saved_charsize = charsize;
          break;

        case C_POP_LOCATION:
          width = saved_width;
          charsize = saved_charsize;
          break;

        case C_RIGHT_ONE_EM:
          width += charsize * HERSHEY_EM;
          break;

        case C_RIGHT_HALF_EM:
          width += charsize * HERSHEY_EM / 2.0;
          break;

        case C_RIGHT_QUARTER_EM:
          width += charsize * HERSHEY_EM / 4.0;
          break;

        case C_RIGHT_SIXTH_EM:
          width += charsize * HERSHEY_EM / 6.0;
          break;

        case C_RIGHT_EIGHTH_EM:
          width += charsize * HERSHEY_EM / 8.0;
          break;

        case C_RIGHT_TWELFTH_EM:
          width += charsize * HERSHEY_EM / 12.0;
          break;

        case C_LEFT_ONE_EM:
          width -= charsize * HERSHEY_EM;
          break;

        case C_LEFT_HALF_EM:
          width -= charsize * HERSHEY_EM / 2.0;
          break;

        case C_LEFT_QUARTER_EM:
          width -= charsize * HERSHEY_EM / 4.0;
          break;

        case C_LEFT_SIXTH_EM:
          width -= charsize * HERSHEY_EM / 6.0;
          break;

        case C_LEFT_EIGHTH_EM:
          width -= charsize * HERSHEY_EM / 8.0;
          break;

        case C_LEFT_TWELFTH_EM:
          width -= charsize * HERSHEY_EM / 12.0;
          break;

          /* unrecognized control code */
        default:
          break;
        }
    }
      else          /* yow, an actual character */
    {
      int raw_fontnum;

      /* compute index of font, in table in g_fontdb.c */
      raw_fontnum = (c >> FONT_SHIFT) & ONE_BYTE;

      c &= ~FONT_SPEC;  /* extract character proper */
      glyphnum = (_hershey_font_info[raw_fontnum].chars)[c];

      /* could be a pseudo glyph number, e.g. an indication that
         character is composite */
      if (glyphnum == ACC0 || glyphnum == ACC1 || glyphnum == ACC2)
        {
        unsigned char composite, character = '\0' /* -Wall */, accent;

          /* if so, use 1st element of composite character */
          composite = (unsigned char)c;
          if (_composite_char (&composite, &character, &accent))
        glyphnum = (_hershey_font_info[raw_fontnum].chars)[character];
          else
        glyphnum = UNDE; /* hope this won't happen */
        }

      /* could also be a glyph number displaced by KS, to indicate
         that this is a small kana */
      if (glyphnum & KS)
        glyphnum -= KS;

      glyph = (const unsigned char *)(_occidental_hershey_glyphs[glyphnum]);
      if (*glyph != '\0')   /* nonempty glyph */
        /* 1st two chars are bounds */
        width += charsize * ((int)glyph[1] - (int)glyph[0]);
    }

      ptr++;            /* bump pointer in string */
    }

  return HERSHEY_X_UNITS_TO_USER_UNITS (width);
}

/* _draw_hershey_penup_stroke() draws a penup stroke, along a vector
   specified in Hershey units.  Size scaling and obliquing (true/false) are
   specified.  This is used for repositioning during rendering of
   composite (accented) characters. */
static
void _draw_hershey_penup_stroke(vfontContext *vc, const pGEcontext gc,
                pGEDevDesc dd,
                double dx, double dy,
                double charsize, bool oblique)
{
  double shear;

  shear = oblique ? (SHEAR) : 0.0;
  _draw_hershey_stroke (vc, gc, dd,
            false,  /* pen up */
            charsize * (dx + shear * dy),
            charsize * dy);
}

/* _draw_hershey_glyph() invokes move() and cont() to draw a raw Hershey
   glyph, specified by index in the occidental or oriental glyph arrays.
   Size scaling and obliquing (true/false) are specified. */
static
void _draw_hershey_glyph (vfontContext *vc, const pGEcontext gc, pGEDevDesc dd,
              int glyphnum,
              double charsize, int type, bool oblique)
{
  double xcurr, ycurr;
  double xfinal, yfinal;
  bool pendown = false;
  const unsigned char *glyph;
  double dx, dy;
  double shear;

  shear = oblique ? (SHEAR) : 0.0;
  switch (type)
    {
    case OCCIDENTAL:
    default:
      glyph = (const unsigned char *)(_occidental_hershey_glyphs[glyphnum]);
      break;
    case ORIENTAL:
      glyph = (const unsigned char *)(_oriental_hershey_glyphs[glyphnum]);
      break;
    }

  if (*glyph != '\0')   /* nonempty glyph */
    {
      xcurr = charsize * (double)glyph[0];
      xfinal = charsize * (double)glyph[1];
      ycurr = yfinal = 0.0;
      glyph += 2;
      while (*glyph)
    {
      int xnewint;

      xnewint = (int)glyph[0];

      if (xnewint == (int)' ')
        pendown = false;
      else
        {
          double xnew, ynew;

          xnew = (double)charsize * xnewint;
          ynew = (double)charsize
        * ((int)'R'
           - ((int)glyph[1] + (double)HERSHEY_BASELINE));
          dx = xnew - xcurr;
          dy = ynew - ycurr;
          _draw_hershey_stroke (vc, gc, dd,
                    pendown, dx + shear * dy, dy);
          xcurr = xnew, ycurr = ynew;
          pendown = true;
        }

      glyph +=2;    /* on to next pair */
    }

      /* final penup stroke, to end where we should */
      dx = xfinal - xcurr;
      dy = yfinal - ycurr;
      _draw_hershey_stroke (vc, gc, dd, false, dx + shear * dy, dy);
    }
}

/* _draw_hershey_string() strokes a string beginning at present location,
   which is taken to be on the string's baseline.  Besides invoking move()
   and cont(), it invokes linewidth(). */
static
void _draw_hershey_string (vfontContext *vc, const pGEcontext gc, pGEDevDesc dd,
               const unsigned short *string)
{
  unsigned short c;
  const unsigned short *ptr = string;
  double charsize = 1.0;
  int line_width_type = 0;  /* 0,1,2 = unset,occidental,oriental */

  while ((c = (*ptr++)) != '\0')
    {
      /* Check for the four possibilities: (1) a Hershey glyph specified by
     glyph number, (2) an oriental Hershey glyph specified by glyph
     number, (3) a control code, and (4) an ordinary font character,
     which will be mapped to a Hershey glyph by one of the tables in
     g_fontdb.c. */

      if (c & RAW_HERSHEY_GLYPH)
    {
      if (line_width_type != 1)
        {
          gc->lwd = HERSHEY_LINE_WIDTH_TO_LWD (HERSHEY_STROKE_WIDTH);
          line_width_type = 1;
        }
      _draw_hershey_glyph (vc, gc, dd,
                   c & GLYPH_SPEC, charsize, OCCIDENTAL, false);
    }

      else if (c & RAW_ORIENTAL_HERSHEY_GLYPH)
    {
      if (line_width_type != 2)
        {
          gc->lwd = HERSHEY_LINE_WIDTH_TO_LWD (HERSHEY_STROKE_WIDTH);
          line_width_type = 2;
        }
      _draw_hershey_glyph (vc, gc, dd,
                   c & GLYPH_SPEC, charsize, ORIENTAL, false);
    }

      else if (c & CONTROL_CODE)
    switch (c & ~CONTROL_CODE) /* parse control codes */
      {
      case C_BEGIN_SUPERSCRIPT :
        _draw_hershey_stroke (vc, gc, dd,
                  false,
                  SUPERSCRIPT_DX * charsize * HERSHEY_EM,
                  SUPERSCRIPT_DY * charsize * HERSHEY_EM);
        charsize *= SCRIPTSIZE;
        break;

      case C_END_SUPERSCRIPT:
        charsize /= SCRIPTSIZE;
        _draw_hershey_stroke (vc, gc, dd,
                  false,
                  - SUPERSCRIPT_DX * charsize * HERSHEY_EM,
                  - SUPERSCRIPT_DY * charsize * HERSHEY_EM);
        break;

      case C_BEGIN_SUBSCRIPT:
        _draw_hershey_stroke (vc, gc, dd,
                  false,
                  SUBSCRIPT_DX * charsize * HERSHEY_EM,
                  SUBSCRIPT_DY * charsize * HERSHEY_EM);
        charsize *= SCRIPTSIZE;
        break;

      case C_END_SUBSCRIPT:
        charsize /= SCRIPTSIZE;
        _draw_hershey_stroke (vc, gc, dd,
                  false,
                  - SUBSCRIPT_DX * charsize * HERSHEY_EM,
                  - SUBSCRIPT_DY * charsize * HERSHEY_EM);
        break;

      case C_PUSH_LOCATION:
          /* saved_charsize = charsize;
         saved_position_x = _plotter->drawstate->pos.x;
         saved_position_y = _plotter->drawstate->pos.y; */
        break;

      case C_POP_LOCATION:
          /* charsize = saved_charsize;
         _plotter->fmove (R___(_plotter)
         saved_position_x, saved_position_y); */
        break;

      case C_RIGHT_ONE_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, charsize * HERSHEY_EM, 0.0);
        break;

      case C_RIGHT_HALF_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, charsize * HERSHEY_EM / 2.0, 0.0);
        break;

      case C_RIGHT_QUARTER_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, charsize * HERSHEY_EM / 4.0, 0.0);
        break;

      case C_RIGHT_SIXTH_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, charsize * HERSHEY_EM / 6.0, 0.0);
        break;

      case C_RIGHT_EIGHTH_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, charsize * HERSHEY_EM / 8.0, 0.0);
        break;

      case C_LEFT_ONE_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, - charsize * HERSHEY_EM, 0.0);
        break;

      case C_LEFT_HALF_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, - charsize * HERSHEY_EM / 2.0, 0.0);
        break;

      case C_LEFT_QUARTER_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, - charsize * HERSHEY_EM / 4.0, 0.0);
        break;

      case C_LEFT_SIXTH_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, - charsize * HERSHEY_EM / 6.0, 0.0);
        break;

      case C_LEFT_EIGHTH_EM:
        _draw_hershey_stroke (vc, gc, dd,
                  false, - charsize * HERSHEY_EM / 8.0, 0.0);
        break;

        /* unrecognized control code, punt */
      default:
        break;
      }

      else
    /* yow, an actual font character!  Several possibilities: could be
       a composite (accented) character, could be a small Kana, or
       could be a garden-variety character. */
    {
      int raw_fontnum;
      int glyphnum;     /* glyph in Hershey array */
      int char_glyphnum, accent_glyphnum; /* for composite chars */
      int char_width, accent_width; /* for composite chars */
      const unsigned char *char_glyph, *accent_glyph;
      unsigned char composite, character = '\0', accent = '\0' /* -Wall */;
      bool oblique, small_kana = false;

      /* compute index of font, in font table in g_fontdb.c */
      raw_fontnum = (c >> FONT_SHIFT) & ONE_BYTE;
      /* shear font?  (for HersheySans-Oblique, etc.) */
      oblique = _hershey_font_info[raw_fontnum].obliquing;

      c &= ~FONT_SPEC;  /* extract character proper */
      glyphnum = (_hershey_font_info[raw_fontnum].chars)[c];

      if (glyphnum & KS) /* a small kana? */
        {
          glyphnum -= KS;
          small_kana = true;
        }

      switch (glyphnum)
        {
          /* special case: this is a composite (accented) character;
         search font table in g_fontdb.c for it */
        case ACC0:
        case ACC1:
        case ACC2:
          composite = (unsigned char)c;
          if (_composite_char (&composite, &character, &accent))
        {
          char_glyphnum =
            (_hershey_font_info[raw_fontnum].chars)[character];
          accent_glyphnum =
            (_hershey_font_info[raw_fontnum].chars)[accent];
        }
          else
        {       /* hope this won't happen */
          char_glyphnum = UNDE;
          accent_glyphnum = 0;
        }
          char_glyph =
        (const unsigned char *)_occidental_hershey_glyphs[char_glyphnum];
          accent_glyph =
        (const unsigned char *)_occidental_hershey_glyphs[accent_glyphnum];

          if (*char_glyph != '\0') /* nonempty glyph */
        /* 1st two chars are bounds, in Hershey units */
        char_width = (int)char_glyph[1] - (int)char_glyph[0];
          else
        char_width = 0;

          if (*accent_glyph != '\0') /* nonempty glyph */
        /* 1st two chars are bounds, in Hershey units */
        accent_width = (int)accent_glyph[1] - (int)accent_glyph[0];
          else
        accent_width = 0;

          /* draw the character */
          if (line_width_type != 1)
          {
          gc->lwd = HERSHEY_LINE_WIDTH_TO_LWD (HERSHEY_STROKE_WIDTH);
          line_width_type = 1;
        }
          _draw_hershey_glyph (vc, gc, dd,
                   char_glyphnum, charsize,
                   OCCIDENTAL, oblique);
          /* back up to draw accent */
          _draw_hershey_penup_stroke (vc, gc, dd,
                      -0.5 * (double)char_width
                      -0.5 * (double)accent_width,
                      0.0, charsize, oblique);

          /* repositioning for uppercase and uppercase italic */
          if (glyphnum == ACC1)
        _draw_hershey_penup_stroke (vc, gc, dd,
                        0.0,
                        (double)(ACCENT_UP_SHIFT),
                        charsize, oblique);
          else if (glyphnum == ACC2)
        _draw_hershey_penup_stroke (vc, gc, dd,
                        (double)(ACCENT_RIGHT_SHIFT),
                        (double)(ACCENT_UP_SHIFT),
                        charsize, oblique);

          /* draw the accent */
          _draw_hershey_glyph (vc, gc, dd,
                   accent_glyphnum, charsize,
                   OCCIDENTAL, oblique);

          /* undo special repositioning if any */
          if (glyphnum == ACC1)
        _draw_hershey_penup_stroke (vc, gc, dd,
                        0.0,
                        -(double)(ACCENT_UP_SHIFT),
                        charsize, oblique);
          else if (glyphnum == ACC2)
        _draw_hershey_penup_stroke (vc, gc, dd,
                        -(double)(ACCENT_RIGHT_SHIFT),
                        -(double)(ACCENT_UP_SHIFT),
                        charsize, oblique);

          /* move forward, to end composite char where we should */
          _draw_hershey_penup_stroke (vc, gc, dd,
                      0.5 * (double)char_width
                      -0.5 * (double)accent_width,
                      0.0, charsize, oblique);
          break;

          /* not a composite (accented) character; just an ordinary
         glyph from occidental+Kana array (could be a Kana, in
         particular, could be a small Kana) */
        default:
          if (small_kana)
        {
          int kana_width;
          const unsigned char *kana_glyph;
          double shift = 0.5 * (1.0 - (SMALL_KANA_SIZE));

          kana_glyph =
            (const unsigned char *)_occidental_hershey_glyphs[glyphnum];
          kana_width = (int)kana_glyph[1] - (int)kana_glyph[0];

          /* draw small Kana, preceded and followed by a penup
             stroke in order to traverse the full width of an
             ordinary Kana */
          _draw_hershey_penup_stroke (vc, gc, dd,
                          shift * (double)kana_width,
                          0.0, charsize, oblique);
          if (line_width_type != 2)
            {
            gc->lwd = HERSHEY_LINE_WIDTH_TO_LWD (HERSHEY_STROKE_WIDTH);
              line_width_type = 2;
            }
          _draw_hershey_glyph (vc, gc, dd,
                       glyphnum,
                       (SMALL_KANA_SIZE) * charsize,
                       OCCIDENTAL, oblique);
          _draw_hershey_penup_stroke (vc, gc, dd,
                          shift * (double)kana_width,
                          0.0, charsize, oblique);
        }
          else
        /* whew! just an ordinary glyph from the occidental array
           (could be a Kana however, since they're confusingly
           placed in that array, at the end) */
        {
          if (glyphnum >= BEGINNING_OF_KANA)
            {
              if (line_width_type != 2)
            {
            gc->lwd = HERSHEY_LINE_WIDTH_TO_LWD (HERSHEY_ORIENTAL_STROKE_WIDTH);
              line_width_type = 2;
            }
            }
          else
              if (line_width_type != 1)
            {
            gc->lwd = HERSHEY_LINE_WIDTH_TO_LWD (HERSHEY_STROKE_WIDTH);
              line_width_type = 1;
            }
        _draw_hershey_glyph (vc, gc, dd,
                     glyphnum, charsize,
                     OCCIDENTAL, oblique);
        }
          break;
        } /* end of case statement that switches based on glyphnum */

    } /* end of font character case */

    } /* end of loop through unsigned shorts in the codestring */

  return;
}

/* retrieve the two elements of a composite character from the table in
   g_fontdb.c */
static bool _composite_char (unsigned char *composite,
                 unsigned char *character,
                 unsigned char *accent)
{
  const struct plHersheyAccentedCharInfoStruct *compchar = _hershey_accented_char_info;
  bool found = false;
  unsigned char given = *composite;

  while (compchar->composite)
    {
      if (compchar->composite == given)
    {
      found = true;
      /* return char and accent via pointers */
      *character = compchar->character;
      *accent = compchar->accent;
    }
      compchar++;
    }

  return found;
}