text: /* Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
     This software is licensed as OpenSource, under the Apache License, Version 2.0.
     This license is available at: http://opensource.org/licenses/Apache-2.0. */
  
  /*
   * Abstract font dump.
   */
  
  #include "absfont.h"
  #include "dictops.h"
  #include "txops.h"
  #include "ctutil.h"
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <stdarg.h>
  #include <string.h>
  #include <ctype.h>
  #include <math.h>
  #include <limits.h>
  #include <float.h>
  
  #define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
  #define DUMP_LANG_GROUP (1 << 0)
  #define RND(v) ((float)floor((v) + 0.5))
  
  /* Dump string. */
  static void dumpString(abfDumpCtx h, char *name, char *ptr) {
      if (ptr == ABF_UNSET_PTR)
          return;
  
      FPRINTF_S(h->fp, "%-20s", name);
  
      /* Dump string with carriage return and control character handling */
      fputc('"', h->fp);
      for (;;) {
          int c = *ptr++;
          switch (c) {
              case '\0':
                  FPRINTF_S(h->fp, "\"\n");
                  return;
              case '\r':
                  fputc('\n', h->fp);
                  if (*ptr == '\n')
                      ptr++;
                  break;
              case '\n':
                  fputc('\n', h->fp);
                  break;
              default:
                  if (iscntrl(c))
                      FPRINTF_S(h->fp, "\\x%02X", c & 0xff);
                  else
                      fputc(c, h->fp);
                  break;
          }
      }
  }
  
  /* Dump integer array. */
  static void dumpIntArray(abfDumpCtx h, char *name, long cnt, long *array) {
      int i;
      char *sep;
  
      if (cnt == ABF_EMPTY_ARRAY)
          return;
  
      FPRINTF_S(h->fp, "%-20s", name);
      sep = "{";
      for (i = 0; i < cnt; i++) {
          FPRINTF_S(h->fp, "%s%ld", sep, array[i]);
          sep = ",";
      }
      FPRINTF_S(h->fp, "}\n");
  }
  
  /* Dump real array. */
  static void dumpRealArray(abfDumpCtx h, char *name, long cnt, float *array) {
      int i;
      char *sep;
  
      if (cnt == ABF_EMPTY_ARRAY)
          return;
  
      FPRINTF_S(h->fp, "%-20s", name);
      sep = "{";
      for (i = 0; i < cnt; i++) {
          FPRINTF_S(h->fp, "%s%g", sep, array[i]);
          sep = ",";
      }
      FPRINTF_S(h->fp, "}\n");
  }
  
  /* Dump FontMatrix. */
  static void dumpFontMatrix(abfDumpCtx h, char *name, abfFontMatrix *FontMatrix) {
      if (!abfIsDefaultFontMatrix(FontMatrix)) {
          /* Print values with higher precision than dumpRealArray() provides */
          int i;
          char *sep;
          FPRINTF_S(h->fp, "%-20s", name);
          sep = "{";
          for (i = 0; i < FontMatrix->cnt; i++) {
              FPRINTF_S(h->fp, "%s%.8g", sep, FontMatrix->array[i]);
              sep = ",";
          }
          FPRINTF_S(h->fp, "}\n");
      }
  }
  
  /* Dump top dictionary. */
  static void dumpTopDict(abfDumpCtx h, abfTopDict *top) {
      char *OrigFontType;
      char *srcFontType;
  
      dumpString(h, "version", top->version.ptr);
      dumpString(h, "Notice", top->Notice.ptr);
      dumpString(h, "Copyright", top->Copyright.ptr);
      dumpString(h, "FullName", top->FullName.ptr);
      dumpString(h, "FamilyName", top->FamilyName.ptr);
      dumpString(h, "Weight", top->Weight.ptr);
      if (top->isFixedPitch != cff_DFLT_isFixedPitch)
          FPRINTF_S(h->fp, "isFixedPitch        true\n");
      if (top->ItalicAngle != cff_DFLT_ItalicAngle)
          FPRINTF_S(h->fp, "ItalicAngle         %g\n", top->ItalicAngle);
      if (top->UnderlinePosition != cff_DFLT_UnderlinePosition)
          FPRINTF_S(h->fp, "UnderlinePosition   %g\n", top->UnderlinePosition);
      if (top->UnderlineThickness != cff_DFLT_UnderlineThickness)
          FPRINTF_S(h->fp, "UnderlineThickness  %g\n", top->UnderlineThickness);
      if (top->UniqueID != ABF_UNSET_INT)
          FPRINTF_S(h->fp, "UniqueID            %ld\n", top->UniqueID);
      if (top->FontBBox[0] != 0.0 || top->FontBBox[1] != 0.0 ||
          top->FontBBox[2] != 0.0 || top->FontBBox[3] != 0.0)
          FPRINTF_S(h->fp, "FontBBox            {%g,%g,%g,%g}\n",
                    top->FontBBox[0], top->FontBBox[1],
                    top->FontBBox[2], top->FontBBox[3]);
      if (top->StrokeWidth != cff_DFLT_StrokeWidth)
          FPRINTF_S(h->fp, "StrokeWidth         %g\n", top->StrokeWidth);
      dumpIntArray(h, "XUID", top->XUID.cnt, top->XUID.array);
  
      /* CFF and Acrobat extensions */
      dumpString(h, "PostScript", top->PostScript.ptr);
      dumpString(h, "BaseFontName", top->BaseFontName.ptr);
      dumpIntArray(h, "BaseFontBlend",
                   top->BaseFontBlend.cnt, top->BaseFontBlend.array);
      if (top->FSType != ABF_UNSET_INT)
          FPRINTF_S(h->fp, "FSType              %ld\n", top->FSType);
      switch (top->OrigFontType) {
          case abfOrigFontTypeType1:
              OrigFontType = "Type1";
              break;
          case abfOrigFontTypeCID:
              OrigFontType = "CID";
              break;
          case abfOrigFontTypeTrueType:
              OrigFontType = "TrueType";
              break;
          case abfOrigFontTypeOCF:
              OrigFontType = "OCF";
              break;
          default:
              OrigFontType = NULL;
              break;
      }
      if (OrigFontType != NULL)
          FPRINTF_S(h->fp, "OrigFontType        %s\n", OrigFontType);
      if (top->WasEmbedded != 0)
          FPRINTF_S(h->fp, "WasEmbedded         true\n");
  
      /* Synthetic font */
      dumpString(h, "SynBaseFontName", top->SynBaseFontName.ptr);
  
      /* CID-keyed font extensions */
      dumpFontMatrix(h, "cid.FontMatrix", &top->cid.FontMatrix);
      dumpString(h, "cid.CIDFontName", top->cid.CIDFontName.ptr);
      dumpString(h, "cid.Registry", top->cid.Registry.ptr);
      dumpString(h, "cid.Ordering", top->cid.Ordering.ptr);
      if (top->cid.Supplement != ABF_UNSET_INT)
          FPRINTF_S(h->fp, "cid.Supplement      %ld\n", top->cid.Supplement);
      if (top->cid.CIDFontVersion != cff_DFLT_CIDFontVersion)
          FPRINTF_S(h->fp, "cid.CIDFontVersion  %.03f\n", top->cid.CIDFontVersion);
      if (top->cid.CIDFontRevision != cff_DFLT_CIDFontRevision)
          FPRINTF_S(h->fp, "cid.CIDFontRevision %ld\n", top->cid.CIDFontRevision);
      if (top->cid.CIDCount != cff_DFLT_CIDCount)
          FPRINTF_S(h->fp, "cid.CIDCount        %ld\n", top->cid.CIDCount);
      if (top->cid.UIDBase != ABF_UNSET_INT)
          FPRINTF_S(h->fp, "cid.UIDBase         %ld\n", top->cid.UIDBase);
  
      /* Supplementary data */
      if (top->sup.flags != 0) {
          char *sep = " (";
          FPRINTF_S(h->fp, "sup.flags           0x%08lx", top->sup.flags);
          if (top->sup.flags & ABF_SYN_FONT) {
              FPRINTF_S(h->fp, "%sABF_SYN_FONT", sep);
              sep = ",";
          }
          if (top->sup.flags & ABF_CID_FONT) {
              FPRINTF_S(h->fp, "%sABF_CID_FONT", sep);
              sep = ",";
          }
          if (sep[0] == ',')
              FPRINTF_S(h->fp, ")");
          FPRINTF_S(h->fp, "\n");
      }
      switch (top->sup.srcFontType) {
          case abfSrcFontTypeType1Name:
              srcFontType = "Type 1 (name-keyed)";
              break;
          case abfSrcFontTypeType1CID:
              srcFontType = "Type 1 (cid-keyed)";
              break;
          case abfSrcFontTypeCFFName:
              srcFontType = "CFF (name-keyed)";
              break;
          case abfSrcFontTypeCFFCID:
              srcFontType = "CFF (cid-keyed)";
              break;
          case abfSrcFontTypeSVGName:
              srcFontType = "SVG (name-keyed)";
              break;
          case abfSrcFontTypeUFOName:
              srcFontType = "UFO (name-keyed)";
              break;
          case abfSrcFontTypeTrueType:
              srcFontType = "TrueType";
              break;
          default:
              srcFontType = NULL;
              break;
      }
      if (srcFontType != NULL)
          FPRINTF_S(h->fp, "sup.srcFontType     %s\n", srcFontType);
      if (top->sup.UnitsPerEm != 1000)
          FPRINTF_S(h->fp, "sup.UnitsPerEm      %ld\n", top->sup.UnitsPerEm);
      if (top->sup.nGlyphs != ABF_UNSET_INT)
          FPRINTF_S(h->fp, "sup.nGlyphs         %ld\n", top->sup.nGlyphs);
  }
  
  /* Dump Private dictionary. */
  static void dumpPrivateDict(abfDumpCtx h, abfPrivateDict *priv) {
      dumpRealArray(h, "BlueValues",
                    priv->BlueValues.cnt, priv->BlueValues.array);
      dumpRealArray(h, "OtherBlues",
                    priv->OtherBlues.cnt, priv->OtherBlues.array);
      dumpRealArray(h, "FamilyBlues",
                    priv->FamilyBlues.cnt, priv->FamilyBlues.array);
      dumpRealArray(h, "FamilyOtherBlues",
                    priv->FamilyOtherBlues.cnt,
                    priv->FamilyOtherBlues.array);
      if (priv->BlueScale != (float)cff_DFLT_BlueScale)
          FPRINTF_S(h->fp, "BlueScale           %g\n", priv->BlueScale);
      if (priv->BlueShift != cff_DFLT_BlueShift)
          FPRINTF_S(h->fp, "BlueShift           %g\n", priv->BlueShift);
      if (priv->BlueFuzz != cff_DFLT_BlueFuzz)
          FPRINTF_S(h->fp, "BlueFuzz            %g\n", priv->BlueFuzz);
      if (priv->StdHW != ABF_UNSET_REAL)
          FPRINTF_S(h->fp, "StdHW               %g\n", priv->StdHW);
      if (priv->StdVW != ABF_UNSET_REAL)
          FPRINTF_S(h->fp, "StdVW               %g\n", priv->StdVW);
      dumpRealArray(h, "StemSnapH",
                    priv->StemSnapH.cnt, priv->StemSnapH.array);
      dumpRealArray(h, "StemSnapV",
                    priv->StemSnapV.cnt, priv->StemSnapV.array);
      if (priv->ForceBold != cff_DFLT_ForceBold)
          FPRINTF_S(h->fp, "ForceBold           true\n");
      if (priv->LanguageGroup != cff_DFLT_LanguageGroup)
          FPRINTF_S(h->fp, "LanguageGroup       %ld\n", priv->LanguageGroup);
      if (priv->ExpansionFactor != (float)cff_DFLT_ExpansionFactor)
          FPRINTF_S(h->fp, "ExpansionFactor     %g\n", priv->ExpansionFactor);
      if (priv->initialRandomSeed != cff_DFLT_initialRandomSeed)
          FPRINTF_S(h->fp, "initialRandomSeed   %g\n", priv->initialRandomSeed);
  }
  
  /* Dump font dictionary. */
  static void dumpFontDict(abfDumpCtx h, abfFontDict *font) {
      dumpString(h, "FontName", font->FontName.ptr);
      if (font->PaintType != cff_DFLT_PaintType)
          FPRINTF_S(h->fp, "PaintType           %ld\n", font->PaintType);
      dumpFontMatrix(h, "FontMatrix", &font->FontMatrix);
      FPRINTF_S(h->fp, "## Private\n");
      dumpPrivateDict(h, &font->Private);
  }
  
  /* Begin font dump by dumping dicts. */
  void abfDumpBegFont(abfDumpCtx h, abfTopDict *top) {
      if (h->level < 0 || h->level > 6)
          h->level = 1; /* Set default for invalid levels */
  
      if (h->level <= 3) {
          long i, j;
  
          FPRINTF_S(h->fp, "## Filename %s\n",
                    (top->sup.filename == ABF_UNSET_PTR) ? "<unknown>" : top->sup.filename);
  
          FPRINTF_S(h->fp, "## Top Dict\n");
          dumpTopDict(h, top);
          for (i = 0; i < top->FDArray.cnt; i++) {
              if (h->fdCnt == 0) {
                  FPRINTF_S(h->fp, "## FontDict[%ld]\n", i);
                  dumpFontDict(h, &top->FDArray.array[i]);
              } else {
                  if (h->excludeSubset) {
                      unsigned int match = 0;
                      for (j = 0; j < h->fdCnt; j++) {
                          if (i == h->fdArray[j]) {
                              match = 1;
                              break;
                          }
                      }
                      if (!match) {
                          FPRINTF_S(h->fp, "## FontDict[%ld]\n", i);
                          dumpFontDict(h, &top->FDArray.array[i]);
                      }
  
                  } else {
                      for (j = 0; j < h->fdCnt; j++) {
                          if (i == h->fdArray[j]) {
                              FPRINTF_S(h->fp, "## FontDict[%ld]\n", i);
                              dumpFontDict(h, &top->FDArray.array[i]);
                              break;
                          }
                      }
                  }
              }
          }
      }
  
      if (h->level == 0)
          return;
  
      {
          long i;
          h->flags = 0;
          for (i = 0; i < top->FDArray.cnt; i++)
              if (top->FDArray.array[i].Private.LanguageGroup == 1) {
                  h->flags |= DUMP_LANG_GROUP;
                  break;
              }
          h->left = 0;
      }
  
      /* Print glyph comment */
      if (top->sup.flags & ABF_CID_FONT)
          FPRINTF_S(h->fp, "## glyph[tag] {cid,iFD");
      else
          FPRINTF_S(h->fp, "## glyph[tag] {name,encoding");
      if (h->flags & DUMP_LANG_GROUP)
          FPRINTF_S(h->fp, ",LanguageGroup");
      if (h->level == 1 || h->level == 4)
          FPRINTF_S(h->fp, "}\n");
      else
          FPRINTF_S(h->fp, ",path}\n");
  }
  
  /* ------------------------------- Glyph Dump ------------------------------ */
  
  /* Dump glyph beginning. */
  static int glyphBeg(abfGlyphCallbacks *cb, abfGlyphInfo *info) {
      abfDumpCtx h = (abfDumpCtx)cb->direct_ctx;
  
      cb->info = info;
      if (h->level == 0)
          return ABF_SKIP_RET;
  
      FPRINTF_S(h->fp, "glyph[%hu] {", info->tag);
      if (info->flags & ABF_GLYPH_CID)
          /* Dump CID-keyed glyph */
          FPRINTF_S(h->fp, "%hu,%u", info->cid, info->iFD);
      else {
          /* Dump name-keyed glyph */
          abfEncoding *enc = &info->encoding;
          if (info->gname.ptr == NULL)
              FPRINTF_S(h->fp, "(missing)");
          else
              FPRINTF_S(h->fp, "%s", info->gname.ptr);
          if (enc->code == ABF_GLYPH_UNENC)
              FPRINTF_S(h->fp, ",-");
          else {
              /* Dump encoding */
              char *sep = ",";
              do {
                  if (info->flags & ABF_GLYPH_UNICODE) {
                      if (enc->code < 0x10000)
                          FPRINTF_S(h->fp, "%sU+%04lX", sep, enc->code);
                      else
                          FPRINTF_S(h->fp, "%sU+%lX", sep, enc->code);
                  } else
                      FPRINTF_S(h->fp, "%s0x%02lX", sep, enc->code);
                  sep = "+";
                  enc = enc->next;
              } while (enc != NULL);
          }
      }
      if (h->flags & DUMP_LANG_GROUP)
          FPRINTF_S(h->fp, ",%d", (info->flags & ABF_GLYPH_LANG_1) != 0);
      switch (h->level) {
          case 1:
          case 4:
              FPRINTF_S(h->fp, "}\n");
              return ABF_SKIP_RET;
          case 2:
          case 5:
              FPRINTF_S(h->fp, ",");
              break;
          case 3:
          case 6:
              FPRINTF_S(h->fp, ",\n");
              break;
      }
      h->left = 0;
  
      return ABF_CONT_RET;
  }
  
  /* Dump instruction according to dump level. */
  static void CTL_CDECL dumpInstr(abfGlyphCallbacks *cb, char *fmt, ...) {
      abfDumpCtx h = (abfDumpCtx)cb->direct_ctx;
      va_list ap;
      va_start(ap, fmt);
      switch (h->level) {
          case 2:
          case 5: {
              int length;
              char buf[128];
              const size_t bufLen = sizeof(buf);
              VSPRINTF_S(buf, bufLen, fmt, ap);
              length = (int)strnlen(buf, bufLen);
              if (length > h->left) {
                  char *p;
                  /* Find break point */
                  for (p = &buf[h->left]; *p != ' '; p--)
                      ;
                  FPRINTF_S(h->fp, "%.*s\n", (int)(p - buf), buf);
                  FPRINTF_S(h->fp, "%s", p);
                  h->left = (int)(79 - (strlen(p) + 1));
              } else {
                  FPRINTF_S(h->fp, "%s", buf);
                  h->left -= length;
              }
          } break;
          case 3:
          case 6:
              FPRINTF_S(h->fp, " ");
              VFPRINTF_S(h->fp, fmt, ap);
              FPRINTF_S(h->fp, "\n");
              break;
          default:
              break;
      }
      va_end(ap);
  }
  
  /* Dump glyph width. */
  static void glyphWidth(abfGlyphCallbacks *cb, float hAdv) {
      dumpInstr(cb, " %g width", roundf(hAdv));
  }
  
  /* Write real number in ASCII to dst stream. */
  #define TX_EPSILON 0.0003
  /*In Xcode, FLT_EPSILON is 1.192..x10-7, but the diff between value-roundf(value) can be 3.05x10-5, when the input value is an integer. */
  static void writeReal(char *buf, const size_t bufLen, float value) {
      char tmp[50];
  
      /* if no decimal component, perform a faster to string conversion */
      if ((fabs(value - roundf(value)) < TX_EPSILON) && (value > LONG_MIN) && (value < LONG_MAX))
          SPRINTF_S(tmp, sizeof(tmp), " %ld", (long int)roundf(value));
      else {
          int l;
          float value2 = (float)RND_ON_WRITE(value);  // to avoid getting -0 from 0.0004.
          if ((value2 == 0) && (value < 0))
              value2 = 0;
          SPRINTF_S(tmp, sizeof(tmp), " %.2f", value2);
          l = (int)strlen(tmp) - 1;
          if ((tmp[l] == '0') && (tmp[l - 1] == '0')) {
              tmp[l - 2] = 0;
          }
      }
      STRCAT_S(buf, bufLen, tmp);
  }
  
  /* Dump glyph move. */
  static void glyphMove(abfGlyphCallbacks *cb, float x0, float y0) {
      char buf[128];
      const size_t bufLen = sizeof(buf);
      buf[0] = 0;
      writeReal(buf, bufLen, x0);
      writeReal(buf, bufLen, y0);
      STRCAT_S(buf, bufLen, " move");
      dumpInstr(cb, "%s", buf);
  }
  
  /* Dump glyph line. */
  static void glyphLine(abfGlyphCallbacks *cb, float x1, float y1) {
      char buf[128];
      const size_t bufLen = sizeof(buf);
      buf[0] = 0;
      writeReal(buf, bufLen, x1);
      writeReal(buf, bufLen, y1);
      STRCAT_S(buf, bufLen, " line");
      dumpInstr(cb, "%s", buf);
  }
  
  /* Dump glyph curve. */
  static void glyphCurve(abfGlyphCallbacks *cb,
                         float x1, float y1,
                         float x2, float y2,
                         float x3, float y3) {
      char buf[128];
      const size_t bufLen = sizeof(buf);
      buf[0] = 0;
      writeReal(buf, bufLen, x1);
      writeReal(buf, bufLen, y1);
      writeReal(buf, bufLen, x2);
      writeReal(buf, bufLen, y2);
      writeReal(buf, bufLen, x3);
      writeReal(buf, bufLen, y3);
      STRCAT_S(buf, bufLen, " curve");
      dumpInstr(cb, "%s", buf);
  }
  
  /* Dump glyph stem. */
  static void glyphStem(abfGlyphCallbacks *cb,
                        int flags, float edge0, float edge1) {
      char buf[128];
      const size_t bufLen = sizeof(buf);
      buf[0] = 0;
  
      if (flags & ABF_NEW_HINTS)
          dumpInstr(cb, " newhints");
      if (flags & ABF_NEW_GROUP)
          dumpInstr(cb, " newgroup");
      if (flags & ABF_CNTR_STEM) {
          writeReal(buf, bufLen, edge0);
          writeReal(buf, bufLen, edge1);
          STRCAT_S(buf, bufLen, (flags & ABF_VERT_STEM) ? " vcntr" : " hcntr");
          // dumpInstr(cb, " %.3f %.3f %s", edge0, edge1, (flags & ABF_VERT_STEM)? "vcntr": "hcntr");
      } else if (flags & ABF_STEM3_STEM) {
          writeReal(buf, bufLen, edge0);
          writeReal(buf, bufLen, edge1);
          STRCAT_S(buf, bufLen, (flags & ABF_VERT_STEM) ? " vstem3" : " hstem3");
          // dumpInstr(cb, " %.3f %.3f %s", edge0, edge1, (flags & ABF_VERT_STEM)? "vstem3": "hstem3");
      } else {
          float width = RND(edge1 - edge0);
          if (width == ABF_EDGE_HINT_LO) {
              writeReal(buf, bufLen, edge1);
              STRCAT_S(buf, bufLen, (flags & ABF_VERT_STEM) ? " leftedge" : " bottomedge");
              // dumpInstr(cb, " %.3f %s", edge1, (flags & ABF_VERT_STEM)? "leftedge": "bottomedge");
          } else if (width == ABF_EDGE_HINT_HI) {
              writeReal(buf, bufLen, edge0);
              STRCAT_S(buf, bufLen, (flags & ABF_VERT_STEM) ? " rightedge" : " topedge");
              // dumpInstr(cb, " %.3f %s", edge0, (flags & ABF_VERT_STEM)? "rightedge": "topedge");
          } else {
              writeReal(buf, bufLen, edge0);
              writeReal(buf, bufLen, edge1);
              STRCAT_S(buf, bufLen, (flags & ABF_VERT_STEM) ? " vstem" : " hstem");
              // dumpInstr(cb, " %.3f %.3f %s", edge0, edge1, (flags & ABF_VERT_STEM)? "vstem": "hstem");
          }
      }
      dumpInstr(cb, "%s", buf);
  }
  
  /* Dump glyph flex. */
  static void glyphFlex(abfGlyphCallbacks *cb, float depth,
                        float x1, float y1,
                        float x2, float y2,
                        float x3, float y3,
                        float x4, float y4,
                        float x5, float y5,
                        float x6, float y6) {
      char buf[256];
      const size_t bufLen = sizeof(buf);
      buf[0] = 0;
  
      writeReal(buf, bufLen, x1);
      writeReal(buf, bufLen, y1);
      writeReal(buf, bufLen, x2);
      writeReal(buf, bufLen, y2);
      writeReal(buf, bufLen, x3);
      writeReal(buf, bufLen, y3);
      writeReal(buf, bufLen, x4);
      writeReal(buf, bufLen, y4);
      writeReal(buf, bufLen, x5);
      writeReal(buf, bufLen, y5);
      writeReal(buf, bufLen, x6);
      writeReal(buf, bufLen, y6);
      writeReal(buf, bufLen, depth);
      STRCAT_S(buf, bufLen, " flex");
      dumpInstr(cb, "%s", buf);
  }
  
  /* Dump glyph general operator. */
  static void glyphGenop(abfGlyphCallbacks *cb,
                         int cnt, float *args, int op) {
      static char *opnames[] =
          {
              "reserved0",
              "hstem",
              "reserved2",
              "vstem",
              "vmoveto",
              "rlineto",
              "hlineto",
              "vlineto",
              "rrcurveto",
              "reserved9",
              "callsubr",
              "return",
              "escape",
              "reserved13",
              "endchar",
              "vsindex",
              "blend",
              "reserved17",
              "hstemhm",
              "hintmask",
              "cntrmask",
              "rmoveto",
              "hmoveto",
              "vstemhm",
              "rcurveline",
              "rlinecurve",
              "vvcurveto",
              "hhcurveto",
              "shortint",
              "callgsubr",
              "vhcurveto",
              "hvcurveto",
          };
      static char *escopnames[] =
          {
              "dotsection",
              "reservedESC1",
              "reservedESC2",
              "and",
              "or",
              "not",
              "reservedESC6",
              "reservedESC7",
              "reservedESC8",
              "abs",
              "add",
              "sub",
              "div",
              "reservedESC13",
              "neg",
              "eq",
              "reservedESC16",
              "reservedESC17",
              "drop",
              "reservedESC19",
              "put",
              "get",
              "ifelse",
              "random",
              "mul",
              "reservedESC25",
              "sqrt",
              "dup",
              "exch",
              "index",
              "roll",
              "reservedESC31",
              "reservedESC32",
              "cntroff", /* Reserved but used as cntroff by implementation */
              "hflex",
              "flex",
              "hflex1",
              "flex1",
              "cntron",
          };
      int hi = op >> 8 & 0xff;
      int lo = op & 0xff;
      char buf[80];
      const size_t bufLen = sizeof(buf);
      char *p = buf;
  
      /* Format args */
      size_t remainingBufLength = bufLen;
      while (cnt--) {
          size_t usedPrintLength;
          SPRINTF_S(p, remainingBufLength, " %g", *args++);
          usedPrintLength = strnlen(p, remainingBufLength);
          p += usedPrintLength;
          remainingBufLength -= usedPrintLength;
      }
  
      /* Format operator */
      if (hi) {
          if (hi != tx_escape)
              SPRINTF_S(p, remainingBufLength, " invalid");
          else if (lo >= (int)ARRAY_LEN(escopnames))
              SPRINTF_S(p, remainingBufLength, " reservedESC%d", lo);
          else
              SPRINTF_S(p, remainingBufLength, " %s", escopnames[lo]);
      } else if (lo >= (int)ARRAY_LEN(opnames))
          SPRINTF_S(p, remainingBufLength, " reserved%d", lo);
      else
          SPRINTF_S(p, remainingBufLength, " %s", opnames[lo]);
  
      dumpInstr(cb, buf);
  }
  
  /* Dump glyph seac. */
  static void glyphSeac(abfGlyphCallbacks *cb,
                        float adx, float ady, int bchar, int achar) {
      dumpInstr(cb, " %g %g %d %d seac", adx, ady, bchar, achar);
  }
  
  /* Dump glyph end. */
  static void glyphEnd(abfGlyphCallbacks *cb) {
      abfDumpCtx h = (abfDumpCtx)cb->direct_ctx;
  
      dumpInstr(cb, " endchar}");
      switch (h->level) {
          case 2:
          case 5:
              FPRINTF_S(h->fp, "\n");
              break;
      }
  }
  
  /* Dump glyph callbacks */
  const abfGlyphCallbacks abfGlyphDumpCallbacks =
      {
          NULL,
          NULL,
          NULL,
          glyphBeg,
          glyphWidth,
          glyphMove,
          glyphLine,
          glyphCurve,
          glyphStem,
          glyphFlex,
          glyphGenop,
          glyphSeac,
          glyphEnd,
  };
  