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. */
  
  #include "pdfwrite.h"
  #include "dynarr.h"
  #include "supportexcept.h"
  
  #include <time.h>
  #include <stdarg.h>
  #include <string.h>
  #include <math.h>
  #ifndef _WIN32
  #include <libgen.h>
  #else
  #define basename windows_basename
  #endif
  
  #define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
  
  typedef struct
  {
      float x;
      float y;
  } Vector;
  
  /* --------------------------------- Layout -------------------------------- */
  
  /* Tile layout */
  #define TILE_SIZE 35
  #define TILE_COLS 16
  #define TILE_ROWS 20
  #define TILES_PER_PAGE (TILE_ROWS * TILE_COLS)
  #define TILE_TOP (TILE_ROWS * TILE_SIZE)
  #define TILE_RIGHT (TILE_COLS * TILE_SIZE)
  #define TILE_TEXT_SIZE 5.0f
  #define TILE_GLYPH_SIZE 24.0f
  
  /* Page layout */
  #define PAGE_ORIG_H 18.0f
  #define PAGE_ORIG_V 18.0f
  #define PAGE_RIGHT TILE_RIGHT
  #define PAGE_TOP 747
  #define PAGE_TEXT_SIZE 10.0f
  
  /* Metrics layout (2 columns: keys/values; 3 groups) */
  #define MTX_VALUES_X (PAGE_RIGHT - 54)
  #define MTX_KEYS_X (MTX_VALUES_X - 30)
  #define MTX_LEADING (PAGE_TEXT_SIZE + 1)
  #define MTX_GROUP0_Y (12 * MTX_LEADING + PAGE_TEXT_SIZE)
  #define MTX_GROUP1_Y (8 * MTX_LEADING + PAGE_TEXT_SIZE / 2)
  #define MTX_GROUP2_Y (4 * MTX_LEADING)
  
  /* Coordinate labels */
  #define COORD_TEXT_SIZE 4.0f
  
  /* Outline glyph origin */
  #define EM_ORIG_H 72.0
  #define EM_ORIG_V 252.0
  
  /* Map pts to em coordinate space */
  #define TO_EM(v) ((float)(v) / h->glyph.scale)
  
  /* Map em value to font coordinate space */
  #define IN_EM(v) ((float)(v)*h->top->sup.UnitsPerEm)
  
  /* Round value to specified number of digits of precision */
  #define RND(p, v) (floor((v)*10 * (p) + 0.5) / 10.0 * (p))
  
  /* ------------------------------ Font Metrics ----------------------------- */
  
  typedef struct /* Font metrics (1000 units/em assumed) */
  {
      char *FontName; /* FontName */
      short baseline; /* Text baseline from em top */
      short capheight;
      short widths[128]; /* ASCII character widths */
  } Font;
  
  enum {
      FONT_TEXT,  /* Text font */
      FONT_COORD, /* Coord font */
      FONT_CNT
  };
  
  /* Font metrics */
  static Font fonts[FONT_CNT] =
      {
          {
              /* [0] Text font */
              "Helvetica",
              755,
              718,
              {
                    0,   0,   0,   0,   0,   0,   0,   0,
                    0,   0,   0,   0,   0,   0,   0,   0,
                    0,   0,   0,   0,   0,   0,   0,   0,
                    0,   0,   0,   0,   0,   0,   0,   0,
                  228, 228, 291, 456, 456, 729, 547, 182,
                  273, 273, 319, 479, 228, 273, 228, 228,
                  456, 456, 456, 456, 456, 456, 456, 456,
                  456, 456, 228, 228, 479, 479, 479, 456,
                  832, 547, 547, 592, 592, 547, 501, 638,
                  592, 228, 410, 547, 456, 683, 592, 638,
                  547, 638, 592, 547, 501, 592, 547, 774,
                  547, 547, 501, 228, 228, 228, 385, 456,
                  182, 456, 456, 410, 456, 456, 228, 456,
                  456, 182, 182, 410, 182, 683, 456, 456,
                  456, 456, 273, 410, 228, 456, 410, 592,
                  410, 410, 410, 274, 213, 274, 479,   0,
              },
          },
          {
              /* [1] Coord font */
              "Courier",
              689,
              572,
              {
                    0,   0,   0,   0,   0,   0,   0,   0,
                    0,   0,   0,   0,   0,   0,   0,   0,
                    0,   0,   0,   0,   0,   0,   0,   0,
                    0,   0,   0,   0,   0,   0,   0,   0,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600, 600,
                  600, 600, 600, 600, 600, 600, 600,   0,
              },
          },
  };
  
  /* ---------------------------- Library Context ---------------------------- */
  
  typedef long OBJ;          /* Object number */
  typedef dnaDCL(char, STM); /* Object stream */
  
  typedef struct /* Hint layer */
  {
      OBJ hint;
      OBJ path;
  } Layer;
  
  typedef struct /* Per-glyph objects */
  {
      struct /* Hint layers */
      {
          long cnt;   /* Number of layers */
          long index; /* Index to start */
      } layer;
      struct /* Palette tile objects */
      {
          OBJ mtx_value;
          OBJ annot;
      } tile;
      struct /* Glyph page objects */
      {
          OBJ coords;
          OBJ tics;
          OBJ flex;
          OBJ links;
          OBJ mtx_value;
          OBJ obj;
      } page;
      OBJ bearings; /* Side-bearing tics */
  } Glyph;
  
  enum /* Object streams */
  {
      STM_MISC,
      STM_PATH,
      STM_TICS,   /* Coord tics */
      STM_COORDS, /* Coord text */
      STM_HINT,
      STM_FLEX,
      STM_LINKS,
      STM_CNT
  };
  
  struct pdwCtx_ {
      long flags;       /* Control flags */
      long level;       /* Dump level */
      abfTopDict *top;  /* Top Dict data */
      char *FontName;   /* Current FontName */
      char date[10];    /* Today's date: dd mmm yy */
      char time[6];     /* Current time: hh:mm */
      char pdfdate[24]; /* Today's date and time in PDF date format */
      struct            /* Document objects */
      {
          OBJ init_page;    /* Initialize page state */
          OBJ root;         /* Catalog */
          OBJ header;       /* Page header */
          OBJ info;         /* Document info */
          OBJ tile_mtx_key; /* Tile metrics key */
          OBJ page_mtx_key; /* Page metrics key */
          OBJ text_font;    /* Text font */
          OBJ coord_font;   /* Coord font */
      } obj;
      STM stms[STM_CNT];  /* Object streams */
      dnaDCL(long, xref); /* Object offsets */
      dnaDCL(OBJ, pages); /* Page objects */
      struct              /* Text parameters */
      {
          short iStm;    /* Text stream */
          short iFont;   /* Base font */
          float size;    /* Point size */
          float leading; /* Line spacing */
          float x;       /* Current line x */
          float y;       /* Current line y */
      } text;
      struct /* Path data */
      {
          float bx; /* Last point */
          float by;
          float cx; /* Last but one point */
          float cy;
          float fx; /* First point in path */
          float fy;
          float sx; /* Second point in path */
          float sy;
          int cnt; /* Point count */
          int moves;
          int lines;
          int curves;
      } path;
      dnaDCL(Layer, layers); /* Hint layers */
      dnaDCL(Glyph, glyphs); /* Glyph data */
      struct                 /* Temporary glyph data */
      {
          float hAdv;
          float scale;
          Glyph *rec;
          Layer *layer;
      } glyph;
      struct /* Metric data */
      {
          struct abfMetricsCtx_ ctx;
          abfGlyphCallbacks cb;
      } metrics;
      struct
      {
          void *stm;  /* Destination stream */
          long start; /* Start offset */
      } dst;
      struct /* Client callbacks */
      {
          ctlMemoryCallbacks mem;
          ctlStreamCallbacks stm;
      } cb;
      dnaCtx dna; /* dynarr context */
      struct      /* Error handling */
      {
          _Exc_Buf env;
      } err;
  };
  
  static void writePDFBeg(pdwCtx h);
  static void writePDFEnd(pdwCtx h);
  static void writePages(pdwCtx h);
  
  /* ----------------------------- Error Handling ---------------------------- */
  
  /* Handle fatal error. */
  static void fatal(pdwCtx h, int err_code) {
      RAISE(&h->err.env, err_code, NULL);
  }
  
  /* ---------------------- Error Handling dynarr Context -------------------- */
  
  /* Manage memory. */
  static void *dna_manage(ctlMemoryCallbacks *cb, void *old, size_t size) {
      pdwCtx h = cb->ctx;
      void *ptr = h->cb.mem.manage(&h->cb.mem, old, size);
      if (size > 0 && ptr == NULL)
          fatal(h, pdwErrNoMemory);
      return ptr;
  }
  
  /* Initialize error handling dynarr context. Return 1 on failure else 0. */
  static int dna_init(pdwCtx h) {
      ctlMemoryCallbacks cb;
      cb.ctx = h;
      cb.manage = dna_manage;
      h->dna = dnaNew(&cb, DNA_CHECK_ARGS);
      return h->dna == NULL;
  }
  
  /* --------------------------- Context Management -------------------------- */
  
  /* Validate client and create context. */
  pdwCtx pdwNew(ctlMemoryCallbacks *mem_cb, ctlStreamCallbacks *stm_cb,
                CTL_CHECK_ARGS_DCL) {
      pdwCtx h;
  
      /* Check client/library compatibility */
      if (CTL_CHECK_ARGS_TEST(PDW_VERSION))
          return NULL;
  
      /* Allocate context */
      h = (pdwCtx)mem_cb->manage(mem_cb, NULL, sizeof(struct pdwCtx_));
      if (h == NULL)
          return NULL;
  
      /* Safety initialization */
      memset(h, 0, sizeof(*h));
  
      /* Copy callbacks */
      h->cb.mem = *mem_cb;
      h->cb.stm = *stm_cb;
  
      /* Initialize service library */
      if (dna_init(h)) {
          /* Initialize failed; clean up */
          mem_cb->manage(mem_cb, h, 0);
          return NULL;
      }
  
      dnaINIT(h->dna, h->xref, 1500, 6000);
      dnaINIT(h->dna, h->pages, 10, 50);
      dnaINIT(h->dna, h->layers, 750, 2500);
      dnaINIT(h->dna, h->glyphs, 250, 750);
      dnaINIT(h->dna, h->stms[STM_MISC], 200, 500);
      dnaINIT(h->dna, h->stms[STM_PATH], 200, 500);
      dnaINIT(h->dna, h->stms[STM_TICS], 200, 500);
      dnaINIT(h->dna, h->stms[STM_COORDS], 200, 500);
      dnaINIT(h->dna, h->stms[STM_HINT], 200, 500);
      dnaINIT(h->dna, h->stms[STM_FLEX], 200, 500);
      dnaINIT(h->dna, h->stms[STM_LINKS], 200, 500);
  
      return h;
  }
  
  /* Free context. */
  int pdwFree(pdwCtx h) {
      if (h == NULL)
          return pdwSuccess;
  
      /* Close destination stream */
      if (h->dst.stm != NULL)
          (void)h->cb.stm.close(&h->cb.stm, h->dst.stm);
  
      dnaFREE(h->xref);
      dnaFREE(h->pages);
      dnaFREE(h->layers);
      dnaFREE(h->glyphs);
      dnaFREE(h->stms[STM_MISC]);
      dnaFREE(h->stms[STM_PATH]);
      dnaFREE(h->stms[STM_TICS]);
      dnaFREE(h->stms[STM_COORDS]);
      dnaFREE(h->stms[STM_HINT]);
      dnaFREE(h->stms[STM_FLEX]);
      dnaFREE(h->stms[STM_LINKS]);
  
      dnaFree(h->dna);
  
      /* Free library context */
      h->cb.mem.manage(&h->cb.mem, h, 0);
  
      return pdwSuccess;
  }
  
  /* Set time fields. */
  static void settime(pdwCtx h) {
      time_t seconds_since_epoch;
      struct tm utc_time;
      struct tm local_time;
      double tzoff;
      long tzmins;
  
      time(&seconds_since_epoch);
  
      SAFE_GMTIME(&seconds_since_epoch, &utc_time);
      SAFE_LOCALTIME(&seconds_since_epoch, &local_time);
  
      tzoff = difftime(mktime(&utc_time), mktime(&local_time));
      tzmins = (long)fabs(tzoff) / 60;
  
      strftime(h->date, sizeof(h->date), "%d %b %y", &local_time);
      strftime(h->time, sizeof(h->time), "%H:%M", &local_time);
  
      strftime(h->pdfdate, sizeof(h->pdfdate), "D:%Y%m%d%H%M%S", &local_time);
      sprintf(&h->pdfdate[strlen(h->pdfdate)], "%c%02ld'%02ld'",
              (tzoff == 0) ? 'Z' : (tzoff < 0) ? '-' : '+', tzmins / 60L, tzmins % 60L);
  }
  
  /* Begin font. */
  int pdwBegFont(pdwCtx h, long flags, long level, abfTopDict *top) {
      /* Set error handler */
      DURING_EX(h->err.env)
  
      /* Initialize */
      h->flags = flags;
      if (level < 0)
          h->level = 0;
      else if (level > 2)
          h->level = 2;
      else
          h->level = level;
      h->top = top;
      h->glyph.scale = 500.0f / top->sup.UnitsPerEm;
      settime(h);
  
      /* Set FontName */
      if (h->top->sup.flags & ABF_CID_FONT)
          h->FontName = h->top->cid.CIDFontName.ptr;
      else
          h->FontName = h->top->FDArray.array[0].FontName.ptr;
      if (h->FontName == ABF_UNSET_PTR)
          h->FontName = "<unknown>";
  
      if (h->level > 0) {
          /* Set up metrics facility */
          h->metrics.cb = abfGlyphMetricsCallbacks;
          h->metrics.cb.direct_ctx = &h->metrics.ctx;
          h->metrics.ctx.flags = 0;
      }
  
      /* Open dst stream and get start offset */
      h->dst.stm = h->cb.stm.open(&h->cb.stm, PDW_DST_STREAM_ID, 0);
      h->dst.start = h->cb.stm.tell(&h->cb.stm, h->dst.stm);
      if (h->dst.start == -1)
          return pdwErrDstStream;
  
      /* Initialize xref list */
      h->xref.cnt = 0;
      *dnaNEXT(h->xref) = 0;
  
      writePDFBeg(h);
  
      HANDLER
      return Exception.Code;
      END_HANDLER
  
      return pdwSuccess;
  }
  
  /* Finish reading font. */
  int pdwEndFont(pdwCtx h) {
      /* Set error handler */
      DURING_EX(h->err.env)
  
      writePages(h);
      writePDFEnd(h);
  
      /* Close dst stream */
      if (h->cb.stm.close(&h->cb.stm, h->dst.stm) == -1)
          return pdwErrDstStream;
  
      HANDLER
      return Exception.Code;
      END_HANDLER
  
      return pdwSuccess;
  }
  
  /* Get version numbers of libraries. */
  void pdwGetVersion(ctlVersionCallbacks *cb) {
      if (cb->called & 1 << PDW_LIB_ID)
          return; /* Already enumerated */
  
      /* Support libraries */
      abfGetVersion(cb);
      dnaGetVersion(cb);
  
      /* This library */
      cb->getversion(cb, PDW_VERSION, "pdfwrite");
  
      /* Record this call */
      cb->called |= 1 << PDW_LIB_ID;
  }
  
  /* Map error code to error string. */
  char *pdwErrStr(int err_code) {
      static char *errstrs[] =
          {
  #undef CTL_DCL_ERR
  #define CTL_DCL_ERR(name, string) string,
  #include "pdwerr.h"
          };
      return (err_code < 0 || err_code >= (int)ARRAY_LEN(errstrs)) ? "unknown error" : errstrs[err_code];
  }
  
  /* --------------------------- Destination Stream -------------------------- */
  
  /* Return position of destination stream. */
  static long dstTell(pdwCtx h) {
      long offset = h->cb.stm.tell(&h->cb.stm, h->dst.stm);
      if (offset == -1)
          fatal(h, pdwErrDstStream);
      return offset;
  }
  
  /* Write buffer to dst stream. */
  static void dstWrite(pdwCtx h, size_t count, char *ptr) {
      if (h->cb.stm.write(&h->cb.stm, h->dst.stm, count, ptr) != count)
          fatal(h, pdwErrDstStream);
  }
  
  /* Write formatted string to dst stream. */
  static void CTL_CDECL dstPrint(pdwCtx h, char *fmt, ...) {
      char buf[500];
      va_list ap;
      va_start(ap, fmt);
      VSPRINTF_S(buf, sizeof(buf), fmt, ap);
      dstWrite(h, strlen(buf), buf);
      va_end(ap);
  }
  
  /* Cross-reference object about to be written to stream and allocate number. */
  static OBJ newObj(pdwCtx h) {
      OBJ num = h->xref.cnt;
      *dnaNEXT(h->xref) = dstTell(h) - h->dst.start;
      return num;
  }
  
  /* Write beginning of object to dst stream. */
  static long dstBegObj(pdwCtx h) {
      OBJ num = newObj(h);
      dstPrint(h,
               "%ld 0 obj\n"
               "<<\n",
               num);
      return num;
  }
  
  /* Write end of object to dst stream. */
  static void dstEndObj(pdwCtx h) {
      dstPrint(h,
               ">>\n"
               "endobj\n");
  }
  
  /* ------------------------- Stream Object Support ------------------------- */
  
  /* Save formatted string to stream object. */
  static void CTL_CDECL stmPrint(pdwCtx h, long iStm, char *fmt, ...) {
      char buf[500];
      long length;
      va_list ap;
      va_start(ap, fmt);
      VSPRINTF_S(buf, sizeof(buf), fmt, ap);
      va_end(ap);
      length = (long)strlen(buf);
      memcpy(dnaEXTEND(h->stms[iStm], length), buf, length);
  }
  
  /* Write stream object stream to dst stream. */
  static long stmWrite(pdwCtx h, long iStm) {
      OBJ num;
      STM *stm = &h->stms[iStm];
  
      if (stm->cnt == 0)
          return -1; /* Empty stream */
  
      num = newObj(h);
      dstPrint(h,
               "%ld 0 obj\n"
               "<< /Length %ld >>\n"
               "stream\n",
               num, stm->cnt);
      dstWrite(h, (size_t)stm->cnt, stm->array);
      dstPrint(h,
               "endstream\n"
               "endobj\n");
  
      stm->cnt = 0; /* Reset stream */
  
      return num;
  }
  
  /* ------------------------------ Text Support ----------------------------- */
  
  /* Begin text object. No leading set if leading parameter is 0. */
  static void textBeg(pdwCtx h, int iStm, int iFont, float size, float leading) {
      /* Select font */
      stmPrint(h, iStm,
               "BT\n"
               "/F%d %.2f Tf\n",
               iFont, RND(1, size));
  
      stmPrint(h, iStm, "%.2f TL\n", RND(1, leading));
      stmPrint(h, iStm, "%d Tz\n", (iFont == FONT_TEXT) ? 82 : 100);
  
      /* Save text parameters */
      h->text.iStm = iStm;
      h->text.iFont = iFont;
      h->text.size = size;
      h->text.leading = leading;
      h->text.x = 0;
      h->text.y = 0;
  }
  
  /* End text object. */
  static void textEnd(pdwCtx h) {
      stmPrint(h, h->text.iStm, "ET\n");
  }
  
  /* Set text position. */
  static void textSetPos(pdwCtx h, float x, float y) {
      y += h->text.leading;
      stmPrint(h, h->text.iStm, "%.2f %.2f Td\n",
               RND(1, x - h->text.x), RND(1, y - h->text.y));
      h->text.x = x;
      h->text.y = y;
  }
  
  /* Show text string. */
  static void CTL_CDECL textShow(pdwCtx h, char *fmt, ...) {
      char src[500];
      char dst[500];
      char *p;
      char *q;
      va_list ap;
  
      /* Format text */
      va_start(ap, fmt);
      VSPRINTF_S(src, sizeof(src), fmt, ap);
      va_end(ap);
  
      /* Double backslashes */
      p = src;
      q = dst;
      for (;;) {
          int c = *p++;
          *q++ = c;
          if (c == '\0')
              break;
          else if (c == '\\')
              *q++ = c;
      }
  
      /* Show text */
      if (h->text.leading != 0) {
          h->text.y -= h->text.leading;
          stmPrint(h, h->text.iStm, "(%s)'\n", dst);
      } else
          stmPrint(h, h->text.iStm, "(%s)Tj\n", dst);
  }
  
  /* Return text baseline from em top in current font. */
  static float textBaseline(pdwCtx h) {
      Font *font = &fonts[h->text.iFont];
      return h->text.size * font->baseline / 1000;
  }
  
  /* Return text cap height in current font. */
  static float textCapHeight(pdwCtx h) {
      Font *font = &fonts[h->text.iFont];
      return h->text.size * font->capheight / 1000;
  }
  
  /* Return text size in current font. */
  static float textSize(pdwCtx h) {
      return h->text.size;
  }
  
  /* Return length of text when shown in current font. */
  static float textLength(pdwCtx h, char *text) {
      Font *font = &fonts[h->text.iFont];
      long total = 0;
      while (*text != '\0')
          total += font->widths[*text++ & 0x7f];
      return total * h->text.size / 1000;
  }
  
  /* ------------------------------- Glyph Draw ------------------------------ */
  
  /* Draw glyph beginning. */
  static int glyphBeg(abfGlyphCallbacks *cb, abfGlyphInfo *info) {
      pdwCtx h = cb->direct_ctx;
  
      cb->info = info;
  
      h->glyph.rec = dnaNEXT(h->glyphs);
      h->glyph.rec->layer.index = h->layers.cnt;
  
      if (h->level > 0) {
          stmPrint(h, STM_TICS, "s\n");
          textBeg(h, STM_COORDS, FONT_COORD, TO_EM(COORD_TEXT_SIZE), 0);
          h->path.moves = 0;
          h->path.lines = 0;
          h->path.curves = 0;
          h->path.cnt = 0;
  
          h->metrics.cb.beg(&h->metrics.cb, info);
  
          h->glyph.rec->page.coords = 0;
          h->glyph.rec->page.tics = 0;
          h->glyph.rec->page.flex = 0;
          h->glyph.rec->page.links = 0;
      }
  
      return ABF_CONT_RET;
  }
  
  /* Draw glyph width. */
  static void glyphWidth(abfGlyphCallbacks *cb, float hAdv) {
      pdwCtx h = cb->direct_ctx;
  
      h->glyph.hAdv = hAdv;
      if (h->level > 0)
          h->metrics.cb.width(&h->metrics.cb, hAdv);
  
      /* Write bearings object */
      stmPrint(h, STM_MISC,
               "%.2f 0 m\n"
               "0 0 l\n"
               "0 %.2f l\n"
               "%.2f 0 m\n"
               "%.2f 0 l\n"
               "%.2f %.2f l\n"
               "S\n",
               IN_EM(-0.03),
               IN_EM(-0.03),
               hAdv + IN_EM(0.03),
               hAdv,
               hAdv, IN_EM(-0.03));
      h->glyph.rec->bearings = stmWrite(h, STM_MISC);
  }
  
  /* Make normalized vector. */
  static Vector makeVec(float x, float y) {
      Vector v;
      float d = (float)sqrt(x * x + y * y);
      if (d == 0) {
          v.x = 1;
          v.y = 1;
      } else {
          v.x = x / d;
          v.y = y / d;
      }
      return v;
  }
  
  /* Save label point. */
  static void savePt(pdwCtx h, float x, float y) {
      if (h->path.cnt == 1) {
          h->path.sx = x;
          h->path.sy = y;
      }
      h->path.cx = h->path.bx;
      h->path.cy = h->path.by;
      h->path.bx = x;
      h->path.by = y;
      h->path.cnt++;
  }
  
  /* Draw coordinate for last point. */
  static void drawCoord(pdwCtx h, float ax, float ay) {
      if (h->level == 0)
          return;
  
      if (h->path.cnt >= 2) {
          float x;
          float y;
          char buf[15];
  
          /* Make vector 90 degrees counter clockwise to a->c */
          Vector s = makeVec(h->path.bx - ax, h->path.by - ay);
          Vector t = makeVec(h->path.cx - h->path.bx, h->path.cy - h->path.by);
          Vector u = makeVec(s.x + t.x, s.y + t.y);
          Vector v;
          v.x = -u.y * TO_EM(5.5);
          v.y = u.x * TO_EM(5.5);
  
          if (h->flags & PDW_FLIP_TICS) {
              v.x = -v.x;
              v.y = -v.y;
          }
  
          /* Set off-curve end of tic */
          x = (float)RND(1, h->path.bx + v.x);
          y = (float)RND(1, h->path.by + v.y);
  
          /* Draw tic */
          stmPrint(h, STM_TICS,
                   "%.2f %.2f m\n"
                   "%.2f %.2f l\n",
                   h->path.bx, h->path.by,
                   x, y);
  
          /* Make label */
          sprintf(buf, "%.0f %.0f", h->path.bx, h->path.by);
  
          /* Adjust position for quadrant */
          if (v.x < 0 && v.y >= 0)
              x -= textLength(h, buf);
          else if (v.x <= 0 && v.y < 0) {
              x -= textLength(h, buf);
              y -= textBaseline(h);
          } else if (v.x > 0 && v.y <= 0)
              y -= textBaseline(h);
  
          /* Show label */
          textSetPos(h, x, y);
          textShow(h, buf);
      }
  
      savePt(h, ax, ay);
  }
  
  /* Draw circle of radius r center x,y using 4 Bezier curves. */
  static void drawCircle(pdwCtx h, int iStm, float x, float y, float r) {
      double left = RND(1, x - r);
      double bottom = RND(1, y - r);
      double right = RND(1, x + r);
      double top = RND(1, y + r);
      double l = r * 0.552285; /* Control arm length */
      double xp = RND(1, x + l);
      double xm = RND(1, x - l);
      double yp = RND(1, y + l);
      double ym = RND(1, y - l);
      stmPrint(h, iStm,
               "%.2f %.2f m\n"
               "%.2f %.2f %.2f %.2f %.2f %.2f c\n"
               "%.2f %.2f %.2f %.2f %.2f %.2f c\n"
               "%.2f %.2f %.2f %.2f %.2f %.2f c\n"
               "%.2f %.2f %.2f %.2f %.2f %.2f c\n"
               "h\n",
               x, top,
               xp, top, right, yp, right, y,
               right, ym, xp, bottom, x, bottom,
               xm, bottom, left, ym, left, y,
               left, yp, xm, top, x, top);
  }
  
  /* Draw closepath. */
  static void drawClose(pdwCtx h, int closepoint) {
      float dx;
      float dy;
      float l;
  
      if (closepoint) {
          dx = h->path.fx - h->path.cx;
          dy = h->path.fy - h->path.cy;
      } else {
          dx = h->path.fx - h->path.bx;
          dy = h->path.fy - h->path.by;
      }
      l = (float)sqrt(dx * dx + dy * dy);
  
      /* Draw close arrow */
      stmPrint(h, STM_TICS,
               "f\n"
               "q\n"
               "%.2f %.2f %.2f %.2f %.2f %.2f cm\n"
               "0 0 m\n"
               "%.2f %.2f l\n"
               "%.2f %.2f l\n"
               "h\n",
               dx / l, dy / l, -dy / l, dx / l, h->path.fx, h->path.fy,
               TO_EM(-6), TO_EM(-1.5),
               TO_EM(-6), TO_EM(1.5));
  
      if (closepoint)
          drawCircle(h, STM_TICS, 0, 0, TO_EM(1));
  
      stmPrint(h, STM_TICS,
               "f\n"
               "Q\n");
  
      stmPrint(h, STM_PATH, "h\n");
  }
  
  /* End current path. */
  static void endPath(pdwCtx h) {
      if (h->level == 0 || h->path.moves == 0)
          return;
  
      if (h->path.bx == h->path.fx && h->path.by == h->path.fy)
          drawClose(h, 1);
      else {
          drawClose(h, 0);
          drawCoord(h, h->path.fx, h->path.fy);
          h->path.lines++;
      }
      drawCoord(h, h->path.sx, h->path.sy);
  }
  
  /* Draw glyph move. */
  static void glyphMove(abfGlyphCallbacks *cb, float x0, float y0) {
      pdwCtx h = cb->direct_ctx;
  
      if (h->path.moves != 0)
          endPath(h);
  
      stmPrint(h, STM_PATH, "%.2f %.2f m\n", x0, y0);
  
      h->path.cnt = 1;
      if (h->level > 0) {
          /* Initialize point accumulator */
          h->path.bx = h->path.fx = x0;
          h->path.by = h->path.fy = y0;
          h->metrics.cb.move(&h->metrics.cb, x0, y0);
      }
  
      h->path.moves++;
  }
  
  /* Draw control point. */
  static void drawCntlPt(pdwCtx h, float x, float y) {
      float side;
  
      if (h->level == 0)
          return;
  
      side = (float)RND(1, TO_EM(0.6));
      stmPrint(h, STM_TICS,
               "%.2f %.2f %.2f %.2f re\n",
               RND(1, x - TO_EM(0.3)), RND(1, y - TO_EM(0.3)),
               side, side);
  }
  
  /* Draw glyph line. */
  static void glyphLine(abfGlyphCallbacks *cb, float x1, float y1) {
      pdwCtx h = cb->direct_ctx;
  
      stmPrint(h, STM_PATH, "%.2f %.2f l\n", x1, y1);
      drawCoord(h, x1, y1);
  
      if (h->level > 0)
          h->metrics.cb.line(&h->metrics.cb, x1, y1);
  
      h->path.lines++;
  }
  
  /* Draw glyph curve. */
  static void glyphCurve(abfGlyphCallbacks *cb,
                         float x1, float y1,
                         float x2, float y2,
                         float x3, float y3) {
      pdwCtx h = cb->direct_ctx;
  
      stmPrint(h, STM_PATH, "%.2f %.2f %.2f %.2f %.2f %.2f c\n", x1, y1, x2, y2, x3, y3);
  
      drawCntlPt(h, x1, y1);
      drawCntlPt(h, x2, y2);
  
      drawCoord(h, x1, y1);
      savePt(h, x2, y2);
      savePt(h, x3, y3);
  
      if (h->level > 0)
          h->metrics.cb.curve(&h->metrics.cb, x1, y1, x2, y2, x3, y3);
  
      h->path.curves++;
  }
  
  /* Draw horizontal stem. */
  static void drawHStem(pdwCtx h,
                        float left, float bottom, float right, float top) {
  }
  
  /* Draw vertical stem. */
  static void drawVStem(pdwCtx h,
                        float left, float bottom, float right, float top) {
      if (left == right)
          stmPrint(h, STM_MISC,
                   "%.2f %.2f m\n"
                   "%.2f %.2f l\n",
                   left, bottom, left, top);
      else
          stmPrint(h, STM_MISC,
                   "%.2f %.2f %.2f %.2f re\n",
                   left, bottom, right - left, top - bottom);
  }
  
  /* Draw horizontal stem coordinates. */
  static void drawHCoords(pdwCtx h, float left, float top, float bottom) {
      char buf[20];
      float baseshift = -textCapHeight(h) / 2;
  
      sprintf(buf, "%.2f", top);
      left -= textLength(h, buf);
      textSetPos(h, left, top + baseshift);
      textShow(h, buf);
  
      if (bottom == top)
          return;
  
      sprintf(buf, "%.2f", bottom);
      left -= textLength(h, buf);
      textSetPos(h, left, bottom + baseshift);
      textShow(h, buf);
  }
  
  /* Draw glyph stem. */
  static void glyphStem(abfGlyphCallbacks *cb,
                        int flags, float edge0, float edge1) {
      pdwCtx h = cb->direct_ctx;
      float left;
      float bottom;
      float right;
      float top;
  
      if (h->level < 2)
          return;
  
      if (flags & ABF_VERT_STEM) {
          left = edge0;
          bottom = TO_EM(24);
          right = edge1;
          top = TO_EM(TILE_TOP - 24);
  
      } else {
          /* Draw horizontal stem */
          left = TO_EM(24 - EM_ORIG_H);
          bottom = edge0;
          right = TO_EM(PAGE_RIGHT - EM_ORIG_H - 48);
          top = edge1;
  
          if (bottom == top)
              stmPrint(h, STM_HINT,
                       "%.2f %.2f m\n"
                       "%.2f %.2f l\n",
                       left, top, right, top);
          else
              stmPrint(h, STM_MISC,
                       "%.2f %.2f %.2f %.2f re\n",
                       left, bottom, right - left, top - bottom);
      }
  }
  
  /* Draw 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) {
      pdwCtx h = cb->direct_ctx;
  
      glyphCurve(cb, x1, y1, x2, y2, x3, y3);
      glyphCurve(cb, x4, y4, x5, y5, x6, y6);
  
      if (h->level < 2)
          return;
  
      stmPrint(h, STM_FLEX,
               "%.2f %.2f m\n"
               "%.2f %.2f %.2f %.2f %.2f %.2f c\n"
               "%.2f %.2f %.2f %.2f %.2f %.2f c\n",
               h->path.bx, h->path.by,
               x1, y1, x2, y2, x3, y3,
               x4, y4, x5, y5, x6, y6);
  }
  
  /* Draw glyph general operator. */
  static void glyphGenop(abfGlyphCallbacks *cb,
                         int cnt, float *args, int op) {
  }
  
  /* Draw glyph seac. */
  static void glyphSeac(abfGlyphCallbacks *cb,
                        float adx, float ady, int bchar, int achar) {
  }
  
  /* Make encoding string. Return buffer argument. */
  static char *makeEncStr(pdwCtx h, char *buf, abfGlyphInfo *info) {
      abfEncoding *enc = &info->encoding;
      if (enc->code == ABF_GLYPH_UNENC)
          sprintf(buf, "-");
      else {
          char *p = buf;
          char *sep = "";
          do {
              if (info->flags & ABF_GLYPH_UNICODE) {
                  if (enc->code < 0x10000)
                      sprintf(p, "%sU+%04lX", sep, enc->code);
                  else
                      sprintf(p, "%sU+%lX", sep, enc->code);
              } else
                  sprintf(p, "%sx%02lX", sep, enc->code);
              p += strlen(p);
              sep = "+";
              enc = enc->next;
          } while (enc != NULL);
      }
      return buf;
  }
  
  /* Draw tile box and labels to stream. The x and y parameters specify the
     top left corner of the tile. */
  static void drawTile(pdwCtx h, int iStm,
                       float x, float y, char *tag, char *hAdv, char *gname) {
      float left;
      float right;
      float top;
      float bottom;
  
      /* Draw tile rectangle */
      stmPrint(h, iStm,
               "%.2f %.2f %d %d re\n"
               "s\n",
               x, y - TILE_SIZE, TILE_SIZE, TILE_SIZE);
  
      /* Show labels */
      textBeg(h, iStm, FONT_TEXT, TILE_TEXT_SIZE, TILE_TEXT_SIZE);
  
      left = x + 1;
      right = x + TILE_SIZE - 1;
      top = y - 1 - textBaseline(h);
      bottom = y - TILE_SIZE + 0.2f + textSize(h) - textBaseline(h);
  
      textSetPos(h, left, top);
      textShow(h, tag);
  
      textSetPos(h, left, bottom);
      textShow(h, gname);
  
      textSetPos(h, right - textLength(h, hAdv), top);
      textShow(h, hAdv);
  
      textEnd(h);
  }
  
  /* Write tile metrics value object. */
  static long writeTileMtxValueObj(pdwCtx h, abfGlyphInfo *info) {
      char tag[20];
      char enc[50];
      char hAdv[20];
      char gname[127];
      long iTile = (h->glyphs.cnt - 1) % TILES_PER_PAGE;
      float x = (float)(iTile % TILE_COLS * TILE_SIZE);
      float y = (float)(TILE_ROWS - iTile / TILE_COLS) * TILE_SIZE;
      float scale = TILE_GLYPH_SIZE / h->top->sup.UnitsPerEm;
  
      /* Set glyph name */
      if (info->flags & ABF_GLYPH_CID) {
          sprintf(tag, "%hu,%u", info->tag, info->iFD);
          sprintf(gname, "\\%hu", info->cid);
      } else {
          sprintf(tag, "%hu,%s", info->tag, makeEncStr(h, enc, info));
          sprintf(gname, "%s", info->gname.ptr);
      }
      sprintf(hAdv, "%.2f", h->glyph.hAdv);
  
      drawTile(h, STM_MISC, x, y, tag, hAdv, gname);
      stmPrint(h, STM_MISC,
               "q\n"
               "%.2f 0 0 %.2f %.2f %.2f cm\n",
               scale, scale,
               RND(1, x + (TILE_SIZE - TILE_GLYPH_SIZE *
                                           h->glyph.hAdv / h->top->sup.UnitsPerEm) /
                              2),
               RND(1, y - TILE_SIZE * 0.7));
      return stmWrite(h, STM_MISC);
  }
  
  /* Write page metrics value object. */
  static long writePageMtxValueObj(pdwCtx h, abfGlyphInfo *info) {
      char buf[50];
  
      textBeg(h, STM_MISC, FONT_TEXT, PAGE_TEXT_SIZE, MTX_LEADING);
  
      textSetPos(h, MTX_VALUES_X, MTX_GROUP0_Y);
      if (info->flags & ABF_GLYPH_CID)
          textShow(h, "\\%hu", info->cid);
      else
          textShow(h, info->gname.ptr);
      textShow(h, "%hd", info->tag);
      if (info->flags & ABF_GLYPH_CID)
          textShow(h, "%u", info->iFD);
      else
          textShow(h, makeEncStr(h, buf, info));
      textShow(h, "%g", h->glyph.hAdv);
  
      textSetPos(h, MTX_VALUES_X, MTX_GROUP1_Y);
      textShow(h, "%ld", h->metrics.ctx.int_mtx.left);
      textShow(h, "%ld", h->metrics.ctx.int_mtx.bottom);
      textShow(h, "%ld", h->metrics.ctx.int_mtx.right);
      textShow(h, "%ld", h->metrics.ctx.int_mtx.top);
  
      textSetPos(h, MTX_VALUES_X, MTX_GROUP2_Y);
      textShow(h, "%d", h->path.moves);
      textShow(h, "%d", h->path.lines);
      textShow(h, "%d", h->path.curves);
      textShow(h, "%d", h->path.moves + h->path.lines + h->path.curves);
  
      textEnd(h);
  
      return stmWrite(h, STM_MISC);
  }
  
  /* Save hint layer. */
  static void saveLayer(pdwCtx h) {
      Layer *layer;
  
      if (h->stms[STM_HINT].cnt == 0 && h->stms[STM_PATH].cnt == 0)
          return;
  
      layer = dnaNEXT(h->layers);
      layer->hint = stmWrite(h, STM_HINT);
      layer->path = stmWrite(h, STM_PATH);
  }
  
  /* Draw glyph end. */
  static void glyphEnd(abfGlyphCallbacks *cb) {
      pdwCtx h = cb->direct_ctx;
      Glyph *glyph = h->glyph.rec;
  
      endPath(h);
  
      if (h->path.cnt == 0)
          h->stms[STM_TICS].cnt = 0;
      else
          stmPrint(h, STM_TICS, "S\n");
  
      if (h->level > 0)
          textEnd(h); /* End coord text */
  
      saveLayer(h);
  
      /* Save glyph objects */
      glyph->layer.cnt = h->layers.cnt - h->glyph.rec->layer.index;
      glyph->tile.mtx_value = writeTileMtxValueObj(h, cb->info);
      glyph->page.coords = stmWrite(h, STM_COORDS);
      glyph->page.tics = stmWrite(h, STM_TICS);
      glyph->page.flex = stmWrite(h, STM_FLEX);
      /* page.links */
      if (h->level > 0) {
          h->metrics.cb.end(&h->metrics.cb);
          glyph->page.mtx_value = writePageMtxValueObj(h, cb->info);
      }
  }
  
  /* Draw glyph callbacks */
  const abfGlyphCallbacks pdwGlyphCallbacks =
      {
          NULL,
          NULL,
          NULL,
          glyphBeg,
          glyphWidth,
          glyphMove,
          glyphLine,
          glyphCurve,
          glyphStem,
          glyphFlex,
          glyphGenop,
          glyphSeac,
          glyphEnd,
  };
  
  /* ------------------------------------------------- */
  
  /* Write page initialization object. */
  static long writeInitPageObj(pdwCtx h) {
      stmPrint(h, STM_MISC,
               "1 0 0 1 %g %g cm\n"
               "0 w\n",
               PAGE_ORIG_H, PAGE_ORIG_V);
      return stmWrite(h, STM_MISC);
  }
  
  /* Write info object. */
  static long writeInfoObj(pdwCtx h) {
      enum { MAX_VERSION_SIZE = 100 };
      char version[MAX_VERSION_SIZE + 1];
      char abf_version_buf[MAX_VERSION_SIZE];
      char pdw_version_buf[MAX_VERSION_SIZE];
      OBJ num;
  
      /* Make font version */
      if (h->top->sup.flags & ABF_CID_FONT)
          sprintf(version, "%g", h->top->cid.CIDFontVersion);
      else if (h->top->version.ptr != ABF_UNSET_PTR) {
          char format[20];
          sprintf(format, "%%.%ds", MAX_VERSION_SIZE);
          sprintf(version, format, h->top->version.ptr);
      } else
          version[0] = '\0';
  
      /* Write information dictionary */
      num = dstBegObj(h);
      dstPrint(h,
               "/Title (%s %s)\n"
               "/Creator (absfont %8s)\n"
               "/Producer (pdfwrite %8s)\n"
               "/CreationDate (%s)\n"
               "/ModDate (%s)\n",
               h->FontName, version,
               CTL_SPLIT_VERSION(abf_version_buf, ABF_VERSION),
               CTL_SPLIT_VERSION(pdw_version_buf, PDW_VERSION),
               h->pdfdate,
               h->pdfdate);
      dstEndObj(h);
  
      return num;
  }
  
  /* Write font object. Return object number. */
  static long writeFontObj(pdwCtx h, int iFont) {
      OBJ num = dstBegObj(h);
      dstPrint(h,
               "/Type /Font\n"
               "/Subtype /Type1\n"
               "/BaseFont /%s\n",
               fonts[iFont].FontName);
      dstEndObj(h);
      return num;
  }
  
  #ifdef _WIN32
  static char *windows_basename(char *full_path)
  {
      char *last_slash;
      char *base_name;
  
      last_slash = strrchr(full_path, '\\');
      base_name = last_slash ? last_slash + 1 : full_path;
      return base_name;
  }
  #endif
  
  /* Write header object used by every page. */
  static long writeHeaderObj(pdwCtx h) {
      char *p;
      float y;
      char buf[30];
  
      textBeg(h, STM_MISC, FONT_TEXT, PAGE_TEXT_SIZE, PAGE_TEXT_SIZE);
      y = PAGE_TOP - textBaseline(h);
  
      /* Show filename */
      if (h->top->sup.filename == ABF_UNSET_PTR)
          p = "<unknown>";
      else
          p = h->top->sup.filename;
      textSetPos(h, 0, y);
      textShow(h, "Filename:  %s", basename(p));
  
      /* Show FontName */
      textShow(h, "FontName:  %s", h->FontName);
  
      /* Show units-per-em */
      textShow(h, "Em:  %ld units", h->top->sup.UnitsPerEm);
  
      /* Show date */
      sprintf(buf, "Date:  %s", h->date);
      textSetPos(h, PAGE_RIGHT - textLength(h, buf), y);
      textShow(h, buf);
  
      /* Show time */
      y -= textSize(h);
      sprintf(buf, "Time:  %s", h->time);
      textSetPos(h, PAGE_RIGHT - textLength(h, buf), y);
      textShow(h, buf);
  
      textEnd(h);
  
      return stmWrite(h, STM_MISC);
  }
  
  /* Write palette tile key object. */
  static long writeTileKeyObj(pdwCtx h) {
      float x = (TILE_COLS - 3) * TILE_SIZE;
      float y = PAGE_TOP;
  
      if (h->top->sup.flags & ABF_CID_FONT)
          drawTile(h, STM_MISC, x, y, "tag,fd", "hAdv", "cid");
      else
          drawTile(h, STM_MISC, x, y, "tag,enc", "hAdv", "gname");
  
      return stmWrite(h, STM_MISC);
  }
  
  /* Write metrics key object. */
  static long writeMtxKeyObj(pdwCtx h) {
      textBeg(h, STM_MISC, FONT_TEXT, PAGE_TEXT_SIZE, MTX_LEADING);
  
      textSetPos(h, MTX_KEYS_X, MTX_GROUP0_Y);
      textShow(h, "glyph");
      textShow(h, "tag");
      textShow(h, "enc");
      textShow(h, "hAdv");
  
      textSetPos(h, MTX_KEYS_X, MTX_GROUP1_Y);
      textShow(h, "left");
      textShow(h, "bottom");
      textShow(h, "right");
      textShow(h, "top");
  
      textSetPos(h, MTX_KEYS_X, MTX_GROUP2_Y);
      textShow(h, "moves");
      textShow(h, "lines");
      textShow(h, "curves");
      textShow(h, "total");
  
      textEnd(h);
  
      return stmWrite(h, STM_MISC);
  }
  
  /* Write beginning PDF file. */
  static void writePDFBeg(pdwCtx h) {
      dstPrint(h, "%%PDF-1.1\n");
  
      h->obj.init_page = writeInitPageObj(h);
      h->obj.info = writeInfoObj(h);
      h->obj.text_font = writeFontObj(h, FONT_TEXT);
      h->obj.header = writeHeaderObj(h);
      h->obj.tile_mtx_key = writeTileKeyObj(h);
      if (h->level > 0) {
          h->obj.coord_font = writeFontObj(h, FONT_COORD);
          h->obj.page_mtx_key = writeMtxKeyObj(h);
      }
  }
  
  /* Write end of PDF file (xref + trailer). */
  static void writePDFEnd(pdwCtx h) {
      long i;
      long offset = dstTell(h) - h->dst.start;
  
      /* Write xref table */
      dstPrint(h,
               "xref\n"
               "0 %ld\n"
               "0000000000 65535 f \n",
               h->xref.cnt);
      for (i = 1; i < h->xref.cnt; i++)
          dstPrint(h, "%010ld 00000 n \n", h->xref.array[i]);
  
      /* Write trailer */
      dstPrint(h,
               "trailer\n"
               "<<\n"
               "/Size %ld\n"
               "/Root %ld 0 R\n"
               "/Info %ld 0 R\n"
               ">>\n",
               h->xref.cnt,
               h->obj.root,
               h->obj.info);
  
      /* Write tail */
      dstPrint(h,
               "startxref\n"
               "%ld\n"
               "%%%%EOF\n",
               offset);
  }
  
  /* Write catalog object. Return object number. */
  static OBJ writeCatalogObj(pdwCtx h, OBJ pages) {
      OBJ num = dstBegObj(h);
      dstPrint(h,
               "/Type /Catalog\n"
               "/Pages %ld 0 R\n",
               pages);
      dstEndObj(h);
      return num;
  }
  
  /* Write annotation object. */
  static OBJ writeAnnotObj(pdwCtx h, OBJ page,
                           float left, float bottom,
                           float right, float top) {
      OBJ num = dstBegObj(h);
      dstPrint(h,
               "/Type /Annot\n"
               "/Subtype /Link\n"
               "/Border [0 0 0]\n"
               "/Rect [%g %g %g %g]\n"
               "/Dest [%ld 0 R /Fit]\n",
               left, bottom, right, top,
               page);
      dstEndObj(h);
      return num;
  }
  
  /* Write tile annotation dictionaries. */
  static void writeTileAnnots(pdwCtx h, long iBase, long iNext) {
      long i;
      for (i = iBase; i < iNext; i++) {
          Glyph *glyph = &h->glyphs.array[i];
          long iTile = i - iBase;
          float x = iTile % TILE_COLS * TILE_SIZE + PAGE_ORIG_H;
          float y = (TILE_ROWS - iTile / TILE_COLS) * TILE_SIZE + PAGE_ORIG_V;
          glyph->tile.annot = writeAnnotObj(h, glyph->page.obj,
                                            x, y - TILE_SIZE, x + TILE_SIZE, y);
      }
  }
  
  /* Write object reference to dst stream. */
  static void writeObjRef(pdwCtx h, OBJ obj) {
      dstPrint(h, "%ld 0 R\n", obj);
  }
  
  /* Write palette pages. */
  static void writePalettePages(pdwCtx h, OBJ parent) {
      OBJ fQ_obj;
      long i;
      long iBase;
      long pages = h->glyphs.cnt / TILES_PER_PAGE + 1;
  
      dnaSET_CNT(h->pages, pages);
  
      /* Write fill/restore object */
      stmPrint(h, STM_MISC,
               "f\n"
               "Q\n");
      fQ_obj = stmWrite(h, STM_MISC);
  
      iBase = 0;
      for (i = 0; i < pages; i++) {
          long j;
          long iNext = iBase + TILES_PER_PAGE;
          if (iNext > h->glyphs.cnt)
              iNext = h->glyphs.cnt;
  
          if (h->level > 0)
              writeTileAnnots(h, iBase, iNext);
  
          /* Begin page object */
          h->pages.array[i] = dstBegObj(h);
          dstPrint(h,
                   "/Type /Page\n"
                   "/Parent %ld 0 R\n"
                   "/Contents [\n",
                   parent);
  
          /* Write contents objects */
          writeObjRef(h, h->obj.init_page);
          writeObjRef(h, h->obj.header);
          writeObjRef(h, h->obj.tile_mtx_key);
  
          for (j = iBase; j < iNext; j++) {
              long iLayer;
              long k;
              Glyph *glyph = &h->glyphs.array[j];
  
              writeObjRef(h, glyph->tile.mtx_value);
              writeObjRef(h, glyph->bearings);
  
              iLayer = glyph->layer.index;
              for (k = 0; k < glyph->layer.cnt; k++)
                  writeObjRef(h, h->layers.array[iLayer++].path);
  
              writeObjRef(h, fQ_obj);
          }
  
          dstPrint(h, "]\n");
  
          if (h->level > 0) {
              /* Add annotation array */
              dstPrint(h, "/Annots [\n");
              for (j = iBase; j < iNext; j++)
                  writeObjRef(h, h->glyphs.array[j].tile.annot);
              dstPrint(h, "]\n");
          }
  
          /* End page object */
          dstEndObj(h);
  
          iBase = iNext;
      }
  }
  
  #if 0
  /* Draw zone. */
  static void drawZone(pdwCtx h,
                       float left, float bottom, float right, float top,
                       float fuzz, float baseshift) {
      float x;
      char buf[20];
  
      /* Draw fuzz */
      stmPrint(h, STM_HINT_STEMS,
               "%g %g m\n"
               "%g %g l\n"
               "%g %g m\n"
               "%g %g l\n",
               left, top + fuzz,
               right, top + fuzz,
               left, bottom - fuzz,
               right, bottom - fuzz);
  
      /* Draw zone */
      if (bottom == top)
          stmPrint(h, STM_HINT_STEMS,
                   "%g %g m\n"
                   "%g %g l\n",
                   left, top,
                   right, top);
      else
          stmPrint(h, STM_HINT_STEMS,
                   "%g %g %g %g re\n",
                   left, bottom, right - left, top - bottom);
  
      sprintf(buf, "%g", top);
      textSetPos(h, right, top + baseshift);
      textShow(h, buf);
  
      if (bottom == top)
          return;
  
      x = right + textLength(h, buf);
  
      sprintf(buf, "%g", bottom);
      textSetPos(h, x, bottom + baseshift);
      textShow(h, buf);
  }
  
  /* Write hint zone object. */
  static OBJ writeZoneObj(pdwCtx h, long iFD) {
      abfPrivateDict *private = &h->top->FDArray.array[iFD].Private;
  
      for (i = 0; i < private->OtherBlues.cnt; i += 2)
          drawHStem(h,
                    left, private->OtherBlues.array[i + 0],
                    right, private->OtherBlues.array[i + 1]);
      if (private->BlueValues.cnt > 0)
          drawHStem(h,
                    left, private->BlueValues.array[i + 0],
                    right, private->BlueValues.array[i + 1]);
  
      if (private->BlueValues.cnt != 0) {
          drawZones(h, 2, private->BlueValues.array, private->BlueFuzz, 1);
          drawZones(h, private->BlueValues.cnt - 2,
                    &private->BlueValues.array[2], private->BlueFuzz, 0);
      }
      drawZones(h, private->OtherBlues.cnt, private->OtherBlues.array,
                private->BlueFuzz, 1);
  
      return stmWrite(h, STM_MISC);
  }
  #endif
  
  /* Draw zones. */
  static void drawZones(pdwCtx h,
                        long cnt, float *array, float fuzz, int bottom_zone) {
      long i;
      float baseshift;
      float left;
      float right;
  
      if (cnt == 0)
          return;
  
      left = TO_EM(-EM_ORIG_H);
      right = TO_EM(PAGE_RIGHT - EM_ORIG_H - 24);
  
      /* Draw zones */
      for (i = 0; i < cnt; i += 2) {
          float bottom = array[i + 0];
          float top = array[i + 1];
  
          /* Draw fuzz */
          stmPrint(h, STM_MISC,
                   "%g %g m\n"
                   "%g %g l\n"
                   "%g %g m\n"
                   "%g %g l\n",
                   left, top + fuzz, right, top + fuzz,
                   left, bottom - fuzz, right, bottom - fuzz);
  
          /* Draw zone */
          if (bottom == top)
              stmPrint(h, STM_MISC,
                       "%g %g m\n"
                       "%g %g l\n",
                       left, top, right, top);
          else
              stmPrint(h, STM_MISC,
                       "%g %g %g %g re\n",
                       left, bottom, right - left, top - bottom);
      }
  
      if (bottom_zone)
          stmPrint(h, STM_MISC, "0 .6 1 rg\n");
      else
          stmPrint(h, STM_MISC, "0 .8 1 rg\n");
  
      /* Draw labels */
      stmPrint(h, STM_MISC,
               "f\n"
               "0 0 0 rg\n");
  
      textBeg(h, STM_MISC, FONT_COORD, TO_EM(COORD_TEXT_SIZE), 0);
      baseshift = -textCapHeight(h) / 2;
  
      for (i = 0; i < cnt; i += 2) {
          float x;
          char buf[20];
          float bottom = array[i + 0];
          float top = array[i + 1];
  
          sprintf(buf, "%g", top);
          textSetPos(h, right, top + baseshift);
          textShow(h, buf);
  
          if (bottom == top)
              continue;
  
          x = right + textLength(h, buf);
  
          sprintf(buf, "%g", bottom);
          textSetPos(h, x, bottom + baseshift);
          textShow(h, buf);
      }
  
      textEnd(h);
  }
  
  /* Write hint zone object. */
  static OBJ writeZoneObj(pdwCtx h, long iFD) {
      abfPrivateDict *private = &h->top->FDArray.array[iFD].Private;
  
  #if 0
      for (i = 0; i < private->OtherBlues.cnt; i += 2)
          drawHStem(h,
                    left, private->OtherBlues.array[i + 0],
                    right, private->OtherBlues.array[i + 1]);
      if (private->BlueValues.cnt > 0)
          drawHStem(h,
                    left, private->BlueValues.array[i + 0],
                    right, private->BlueValues.array[i + 1]);
  #endif
  
      if (private->BlueValues.cnt != 0) {
          drawZones(h, 2, private->BlueValues.array, private->BlueFuzz, 1);
          drawZones(h, private->BlueValues.cnt - 2,
                    &private->BlueValues.array[2], private->BlueFuzz, 0);
      }
      drawZones(h, private->OtherBlues.cnt, private->OtherBlues.array,
                private->BlueFuzz, 1);
  
      return stmWrite(h, STM_MISC);
  }
  
  /* Write glyph pages. */
  static void writeGlyphPages(pdwCtx h, OBJ parent) {
      OBJ zone;
      OBJ transform_obj;
      long i;
  
      if (h->level == 0)
          return;
  
      zone = writeZoneObj(h, 0);
  
      /* Write path transformation object */
      stmPrint(h, STM_MISC,
               "%g 0 0 %g %g %g cm\n",
               h->glyph.scale, h->glyph.scale,
               EM_ORIG_H, EM_ORIG_V);
      transform_obj = stmWrite(h, STM_MISC);
  
      for (i = 0; i < h->glyphs.cnt; i++) {
          Glyph *glyph = &h->glyphs.array[i];
  
          /* Begin page object */
          glyph->page.obj = dstBegObj(h);
          dstPrint(h,
                   "/Type /Page\n"
                   "/Parent %ld 0 R\n"
                   "/Contents [\n",
                   parent);
  
          /* Write contents objects */
          writeObjRef(h, h->obj.init_page);
          writeObjRef(h, h->obj.header);
          writeObjRef(h, h->obj.page_mtx_key);
          writeObjRef(h, glyph->page.mtx_value);
          writeObjRef(h, transform_obj);
          if (zone != -1)
              writeObjRef(h, zone);
          writeObjRef(h, glyph->bearings);
  
          if (glyph->layer.cnt > 0) {
              /* Marking glyph; draw path, tics, and coords */
              long j;
              long iLayer = glyph->layer.index;
  
              for (j = 0; j < glyph->layer.cnt; j++)
                  writeObjRef(h, h->layers.array[iLayer++].path);
  
              writeObjRef(h, glyph->page.tics);
              writeObjRef(h, glyph->page.coords);
          }
  
          /* End page object */
          dstPrint(h, "]\n");
          dstEndObj(h);
      }
  }
  
  /* Write page objects. */
  static void writePages(pdwCtx h) {
      long i;
      long pagecnt;
      OBJ pages;
  
      /* Get pages object number and allocate dummy xref */
      pages = h->xref.cnt;
      *dnaNEXT(h->xref) = 0;
  
      writeGlyphPages(h, pages);
      writePalettePages(h, pages);
  
      /* Replace dummy xref by file offset */
      h->xref.array[pages] = dstTell(h) - h->dst.start;
  
      /* Write pages object */
      dstPrint(h,
               "%ld 0 obj\n"
               "<<\n"
               "/Type /Pages\n"
               "/MediaBox [0 0 612 792]\n"
               "/Resources <<\n"
               "/ProcSet [/PDF /Text]\n"
               "/Font <<\n"
               "/F0 %ld 0 R\n",
               pages,
               h->obj.text_font);
  
      pagecnt = h->pages.cnt;
      if (h->level > 0) {
          dstPrint(h, "/F1 %ld 0 R\n", h->obj.coord_font);
          pagecnt += h->glyphs.cnt;
      }
  
      dstPrint(h,
               ">>\n"
               ">>\n"
               "/Count %ld\n"
               "/Kids [\n",
               pagecnt);
      for (i = 0; i < h->pages.cnt; i++)
          writeObjRef(h, h->pages.array[i]);
      if (h->level > 0)
          for (i = 0; i < h->glyphs.cnt; i++)
              writeObjRef(h, h->glyphs.array[i].page.obj);
      dstPrint(h, "]\n");
      dstEndObj(h);
  
      /* Write catalog object */
      h->obj.root = writeCatalogObj(h, pages);
  }
  