Logo Search packages:      
Sourcecode: koffice version File versions

XOutputDev.cc

//========================================================================
//
// XOutputDev.cc
//
// Copyright 1996-2002 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "gmem.h"
#include "gfile.h"
#include "GString.h"
#include "GList.h"
#include "Object.h"
#include "Stream.h"
#include "Link.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "UnicodeMap.h"
#include "CharCodeToUnicode.h"
#include "FontFile.h"
#include "Error.h"
#include "TextOutputDev.h"
#include "XOutputDev.h"
#if HAVE_T1LIB_H
#include "T1Font.h"
#endif
#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
#include "FTFont.h"
#endif
#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
#include "TTFont.h"
#endif

#ifdef VMS
#if (__VMS_VER < 70000000)
extern "C" int unlink(char *filename);
#endif
#endif

#ifdef XlibSpecificationRelease
#if XlibSpecificationRelease < 5
typedef char *XPointer;
#endif
#else
typedef char *XPointer;
#endif

//------------------------------------------------------------------------
// Constants and macros
//------------------------------------------------------------------------

#define xoutRound(x) ((int)(x + 0.5))

#define maxCurveSplits 6      // max number of splits when recursively
                        //   drawing Bezier curves

//------------------------------------------------------------------------
// Font substitutions
//------------------------------------------------------------------------

struct XOutFontSubst {
  char *name;
  double mWidth;
};

// index: {symbolic:12, fixed:8, serif:4, sans-serif:0} + bold*2 + italic
static XOutFontSubst xOutSubstFonts[16] = {
  {"Helvetica",             0.833},
  {"Helvetica-Oblique",     0.833},
  {"Helvetica-Bold",        0.889},
  {"Helvetica-BoldOblique", 0.889},
  {"Times-Roman",           0.788},
  {"Times-Italic",          0.722},
  {"Times-Bold",            0.833},
  {"Times-BoldItalic",      0.778},
  {"Courier",               0.600},
  {"Courier-Oblique",       0.600},
  {"Courier-Bold",          0.600},
  {"Courier-BoldOblique",   0.600},
  {"Symbol",                0.576},
  {"Symbol",                0.576},
  {"Symbol",                0.576},
  {"Symbol",                0.576}
};

//------------------------------------------------------------------------

static void outputToFile(void *stream, char *data, int len) {
  fwrite(data, 1, len, (FILE *)stream);
}

//------------------------------------------------------------------------
// XOutputFont
//------------------------------------------------------------------------

XOutputFont::XOutputFont(Ref *idA, double m11OrigA, double m12OrigA,
                   double m21OrigA, double m22OrigA,
                   double m11A, double m12A, double m21A, double m22A,
                   Display *displayA, XOutputDev *xOutA) {
  id = *idA;
  display = displayA;
  xOut = xOutA;
  m11Orig = m11OrigA;
  m12Orig = m12OrigA;
  m21Orig = m21OrigA;
  m22Orig = m22OrigA;
  m11 = m11A;
  m12 = m12A;
  m21 = m21A;
  m22 = m22A;
}

XOutputFont::~XOutputFont() {
}

void XOutputFont::getCharPath(GfxState *state,
                        CharCode c, Unicode *u, int ulen) {
}

#if HAVE_T1LIB_H
//------------------------------------------------------------------------
// XOutputT1Font
//------------------------------------------------------------------------

XOutputT1Font::XOutputT1Font(Ref *idA, T1FontFile *fontFileA,
                       double m11OrigA, double m12OrigA,
                       double m21OrigA, double m22OrigA,
                       double m11A, double m12A,
                       double m21A, double m22A,
                       Display *displayA, XOutputDev *xOutA):
  XOutputFont(idA, m11OrigA, m12OrigA, m21OrigA, m22OrigA,
            m11A, m12A, m21A, m22A, displayA, xOutA)
{
  double matrix[4];

  fontFile = fontFileA;

  // create the transformed instance
  matrix[0] = m11;
  matrix[1] = -m12;
  matrix[2] = m21;
  matrix[3] = -m22;
  font = new T1Font(fontFile, matrix);
}

XOutputT1Font::~XOutputT1Font() {
  if (font) {
    delete font;
  }
}

GBool XOutputT1Font::isOk() {
  return font != NULL;
}

void XOutputT1Font::updateGC(GC gc) {
}

void XOutputT1Font::drawChar(GfxState *state, Pixmap pixmap, int w, int h,
                       GC gc, GfxRGB *rgb,
                       double x, double y, double dx, double dy,
                       CharCode c, Unicode *u, int uLen) {
  font->drawChar(pixmap, w, h, gc, xoutRound(x), xoutRound(y),
             (int)(rgb->r * 65535), (int)(rgb->g * 65535),
             (int)(rgb->b * 65535), c, u[0]);
}

void XOutputT1Font::getCharPath(GfxState *state,
                        CharCode c, Unicode *u, int uLen) {
  font->getCharPath(c, u[0], state);
}
#endif // HAVE_T1LIB_H

#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
//------------------------------------------------------------------------
// XOutputFTFont
//------------------------------------------------------------------------

XOutputFTFont::XOutputFTFont(Ref *idA, FTFontFile *fontFileA,
                       double m11OrigA, double m12OrigA,
                       double m21OrigA, double m22OrigA,
                       double m11A, double m12A,
                       double m21A, double m22A,
                       Display *displayA, XOutputDev *xOutA):
  XOutputFont(idA, m11OrigA, m12OrigA, m21OrigA, m22OrigA,
            m11A, m12A, m21A, m22A, displayA, xOutA)
{
  double matrix[4];

  fontFile = fontFileA;

  // create the transformed instance
  matrix[0] = m11;
  matrix[1] = -m12;
  matrix[2] = m21;
  matrix[3] = -m22;
  font = new FTFont(fontFile, matrix);
}

XOutputFTFont::~XOutputFTFont() {
  if (font) {
    delete font;
  }
}

GBool XOutputFTFont::isOk() {
  return font != NULL;
}

void XOutputFTFont::updateGC(GC gc) {
}

void XOutputFTFont::drawChar(GfxState *state, Pixmap pixmap, int w, int h,
                       GC gc, GfxRGB *rgb,
                       double x, double y, double dx, double dy,
                       CharCode c, Unicode *u, int uLen) {
  font->drawChar(pixmap, w, h, gc, xoutRound(x), xoutRound(y),
             (int)(rgb->r * 65535), (int)(rgb->g * 65535),
             (int)(rgb->b * 65535), c, uLen > 0 ? u[0] : 0);
}

void XOutputFTFont::getCharPath(GfxState *state,
                        CharCode c, Unicode *u, int uLen) {
  font->getCharPath(c, u[0], state);
}
#endif // FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)

#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
//------------------------------------------------------------------------
// XOutputTTFont
//------------------------------------------------------------------------

XOutputTTFont::XOutputTTFont(Ref *idA, TTFontFile *fontFileA,
                       double m11OrigA, double m12OrigA,
                       double m21OrigA, double m22OrigA,
                       double m11A, double m12A,
                       double m21A, double m22A,
                       Display *displayA, XOutputDev *xOutA):
  XOutputFont(idA, m11OrigA, m12OrigA, m21OrigA, m22OrigA,
            m11A, m12A, m21A, m22A, displayA, xOutA)
{
  double matrix[4];

  fontFile = fontFileA;

  // create the transformed instance
  matrix[0] = m11;
  matrix[1] = -m12;
  matrix[2] = m21;
  matrix[3] = -m22;
  font = new TTFont(fontFile, matrix);
}

XOutputTTFont::~XOutputTTFont() {
  if (font) {
    delete font;
  }
}

GBool XOutputTTFont::isOk() {
  return font != NULL;
}

void XOutputTTFont::updateGC(GC gc) {
}

void XOutputTTFont::drawChar(GfxState *state, Pixmap pixmap, int w, int h,
                       GC gc, GfxRGB *rgb,
                       double x, double y, double dx, double dy,
                       CharCode c, Unicode *u, int uLen) {
  font->drawChar(pixmap, w, h, gc, xoutRound(x), xoutRound(y),
             (int)(rgb->r * 65535), (int)(rgb->g * 65535),
             (int)(rgb->b * 65535), c, u[0]);
}
#endif // !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)

//------------------------------------------------------------------------
// XOutputServer8BitFont
//------------------------------------------------------------------------

// Copy <fmt>, substituting <val> for one occurrence of "%s", into
// <buf>.
static void stringSubst(char *buf, int bufSize, char *fmt, char *val) {
  char *p, *q;
  int i;

  i = 0;
  p = fmt;
  while (*p) {
    if (p[0] == '%' && p[1] == 's') {
      q = val;
      while (*q && i < bufSize - 1) {
      buf[i++] = *q++;
      }
      p += 2;
    } else {
      if (i < bufSize - 1) {
      buf[i++] = *p;
      }
      ++p;
    }
  }
  buf[i] = '\0';
}

XOutputServer8BitFont::XOutputServer8BitFont(Ref *idA, GString *xlfdFmt, 
                                   UnicodeMap *xUMapA,
                                   CharCodeToUnicode *fontUMap,
                                   double m11OrigA, double m12OrigA,
                                   double m21OrigA, double m22OrigA,
                                   double m11A, double m12A,
                                   double m21A, double m22A,
                                   Display *displayA,
                                   XOutputDev *xOutA):
  XOutputFont(idA, m11OrigA, m12OrigA, m21OrigA, m22OrigA,
            m11A, m12A, m21A, m22A, displayA, xOutA)
{
  double size, ntm11, ntm12, ntm21, ntm22;
  GBool rotated;
  int startSize, sz;
  char fontName[500], fontSize[100];
  Unicode u;
  char buf;
  int i;

  // compute size and normalized transform matrix
  size = sqrt(m21*m21 + m22*m22);
  ntm11 = m11 / size;
  ntm12 = -m12 / size;
  ntm21 = m21 / size;
  ntm22 = -m22 / size;

  // try to get a rotated font?
  rotated = !(ntm11 > 0 && ntm22 > 0 &&
            fabs(ntm11 / ntm22 - 1) < 0.2 &&
            fabs(ntm12) < 0.01 &&
            fabs(ntm21) < 0.01);

  // open X font -- if font is not found (which means the server can't
  // scale fonts), try progressively smaller and then larger sizes
  startSize = (int)size;
  if (rotated) {
    sprintf(fontSize, "[%s%0.2f %s%0.2f %s%0.2f %s%0.2f]",
          ntm11<0 ? "~" : "", fabs(ntm11 * size),
          ntm12<0 ? "~" : "", fabs(ntm12 * size),
          ntm21<0 ? "~" : "", fabs(ntm21 * size),
          ntm22<0 ? "~" : "", fabs(ntm22 * size));
  } else {
    sprintf(fontSize, "%d", startSize);
  }
  stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(), fontSize);
  xFont = XLoadQueryFont(display, fontName);
  if (!xFont) {
    for (sz = startSize; sz >= startSize/2 && sz >= 1; --sz) {
      sprintf(fontSize, "%d", sz);
      stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(), fontSize);
      if ((xFont = XLoadQueryFont(display, fontName)))
      break;
    }
    if (!xFont) {
      for (sz = startSize + 1; sz < startSize + 10; ++sz) {
      sprintf(fontSize, "%d", sz);
      stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(),
                fontSize);
      if ((xFont = XLoadQueryFont(display, fontName))) {
        break;
      }
      }
      if (!xFont) {
      sprintf(fontSize, "%d", startSize);
      stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(),
                fontSize);
      error(-1, "Failed to open font: '%s'", fontName);
      return;
      }
    }
  }

  // Construct char code map.
  xUMap = xUMapA;
  for (i = 0; i < 256; ++i) {
    if (fontUMap->mapToUnicode((CID)i, &u, 1) == 1 &&
      xUMap->mapUnicode(u, &buf, 1) == 1) {
      map[i] = buf & 0xff;
    } else {
      map[i] = 0;
    }
  }
}

XOutputServer8BitFont::~XOutputServer8BitFont() {
  if (xFont) {
    XFreeFont(display, xFont);
  }
}

GBool XOutputServer8BitFont::isOk() {
  return xFont != NULL;
}

void XOutputServer8BitFont::updateGC(GC gc) {
  XSetFont(display, gc, xFont->fid);
}

void XOutputServer8BitFont::drawChar(GfxState *state, Pixmap pixmap,
                             int w, int h, GC gc, GfxRGB *rgb,
                             double x, double y, double dx, double dy,
                             CharCode c, Unicode *u, int uLen) {
  Gushort c1;
  char buf[8];
  double dx1, dy1;
  int m, n, i, j, k;

  c1 = map[c];
  if (c1 > 0) {
    buf[0] = (char)c1;
    XDrawString(display, pixmap, gc, xoutRound(x), xoutRound(y), buf, 1);
  } else {
    // substituted character, using more than one character
    n = 0;
    for (i = 0; i < uLen; ++i) {
      n += xUMap->mapUnicode(u[i], buf, sizeof(buf));
    }
    if (n > 0) {
      dx1 = dx / n;
      dy1 = dy / n;
      k = 0;
      for (i = 0; i < uLen; ++i) {
      m = xUMap->mapUnicode(u[i], buf, sizeof(buf));
      for (j = 0; j < m; ++j) {
        XDrawString(display, pixmap, gc,
                  xoutRound(x + k*dx1), xoutRound(y + k*dy1),
                  buf + j, 1);
        ++k;
      }
      }
    }
  }
}

//------------------------------------------------------------------------
// XOutputServer16BitFont
//------------------------------------------------------------------------

XOutputServer16BitFont::XOutputServer16BitFont(Ref *idA, GString *xlfdFmt, 
                                     UnicodeMap *xUMapA,
                                     CharCodeToUnicode *fontUMap,
                                     double m11OrigA,
                                     double m12OrigA,
                                     double m21OrigA,
                                     double m22OrigA,
                                     double m11A, double m12A,
                                     double m21A, double m22A,
                                     Display *displayA,
                                     XOutputDev *xOutA):
  XOutputFont(idA, m11OrigA, m12OrigA, m21OrigA, m22OrigA,
            m11A, m12A, m21A, m22A, displayA, xOutA)
{
  double size, ntm11, ntm12, ntm21, ntm22;
  GBool rotated;
  int startSize, sz;
  char fontName[500], fontSize[100];

  xUMap = xUMapA;
  xUMap->incRefCnt();

  // compute size and normalized transform matrix
  size = sqrt(m21*m21 + m22*m22);
  ntm11 = m11 / size;
  ntm12 = -m12 / size;
  ntm21 = m21 / size;
  ntm22 = -m22 / size;

  // try to get a rotated font?
  rotated = !(ntm11 > 0 && ntm22 > 0 &&
            fabs(ntm11 / ntm22 - 1) < 0.2 &&
            fabs(ntm12) < 0.01 &&
            fabs(ntm21) < 0.01);

  // open X font -- if font is not found (which means the server can't
  // scale fonts), try progressively smaller and then larger sizes
  startSize = (int)size;
  if (rotated) {
    sprintf(fontSize, "[%s%0.2f %s%0.2f %s%0.2f %s%0.2f]",
          ntm11<0 ? "~" : "", fabs(ntm11 * size),
          ntm12<0 ? "~" : "", fabs(ntm12 * size),
          ntm21<0 ? "~" : "", fabs(ntm21 * size),
          ntm22<0 ? "~" : "", fabs(ntm22 * size));
  } else {
    sprintf(fontSize, "%d", startSize);
  }
  stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(), fontSize);
  xFont = XLoadQueryFont(display, fontName);
  if (!xFont) {
    for (sz = startSize; sz >= startSize/2 && sz >= 1; --sz) {
      sprintf(fontSize, "%d", sz);
      stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(), fontSize);
      if ((xFont = XLoadQueryFont(display, fontName)))
      break;
    }
    if (!xFont) {
      for (sz = startSize + 1; sz < startSize + 10; ++sz) {
      sprintf(fontSize, "%d", sz);
      stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(),
                fontSize);
      if ((xFont = XLoadQueryFont(display, fontName))) {
        break;
      }
      }
      if (!xFont) {
      sprintf(fontSize, "%d", startSize);
      stringSubst(fontName, sizeof(fontName), xlfdFmt->getCString(),
                fontSize);
      error(-1, "Failed to open font: '%s'", fontName);
      return;
      }
    }
  }
}

XOutputServer16BitFont::~XOutputServer16BitFont() {
  xUMap->decRefCnt();
  if (xFont) {
    XFreeFont(display, xFont);
  }
}

GBool XOutputServer16BitFont::isOk() {
  return xFont != NULL;
}

void XOutputServer16BitFont::updateGC(GC gc) {
  XSetFont(display, gc, xFont->fid);
}

void XOutputServer16BitFont::drawChar(GfxState *state, Pixmap pixmap,
                              int w, int h, GC gc, GfxRGB *rgb,
                              double x, double y, double dx, double dy,
                              CharCode c, Unicode *u, int uLen) {
  char buf[16];
  XChar2b c1;
  double dx1, dy1;
  int m, n, i, j, k;

  n = 0;
  for (i = 0; i < uLen; ++i) {
    n += xUMap->mapUnicode(u[i], buf, sizeof(buf));
  }
  if (n > 0) {
    dx1 = dx / n;
    dy1 = dy / n;
    k = 0;
    for (i = 0; i < uLen; ++i) {
      m = xUMap->mapUnicode(u[i], buf, sizeof(buf));
      for (j = 0; j+1 < m; j += 2) {
      c1.byte1 = buf[j];
      c1.byte2 = buf[j+1];
      XDrawString16(display, pixmap, gc,
                  xoutRound(x + k*dx1), xoutRound(y + k*dy1),
                  &c1, 1);
      ++k;
      }
    }
  } else if (c != 0) {
    // some PDF files use CID 0, which is .notdef, so just ignore it
    error(-1, "Unknown character (CID=%d Unicode=%04x)",
        c, uLen > 0 ? u[0] : (Unicode)0);
  }
}

//------------------------------------------------------------------------
// XOutputFontCache
//------------------------------------------------------------------------

#if HAVE_T1LIB_H
XOutputT1FontFile::~XOutputT1FontFile() {
  delete fontFile;
  if (tmpFileName) {
    unlink(tmpFileName->getCString());
    delete tmpFileName;
  }
}
#endif

#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
XOutputFTFontFile::~XOutputFTFontFile() {
  delete fontFile;
  if (tmpFileName) {
    unlink(tmpFileName->getCString());
    delete tmpFileName;
  }
}
#endif

#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
XOutputTTFontFile::~XOutputTTFontFile() {
  delete fontFile;
  if (tmpFileName) {
    unlink(tmpFileName->getCString());
    delete tmpFileName;
  }
}
#endif

XOutputFontCache::XOutputFontCache(Display *displayA, Guint depthA,
                           XOutputDev *xOutA,
                           FontRastControl t1libControlA,
                           FontRastControl freetypeControlA) {
  display = displayA;
  depth = depthA;
  xOut = xOutA;

#if HAVE_T1LIB_H
  t1Engine = NULL;
  t1libControl = t1libControlA;
#endif

#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  ftEngine = NULL;
#endif
#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  ttEngine = NULL;
#endif
#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
  freetypeControl = freetypeControlA;
#endif

  clear();
}

XOutputFontCache::~XOutputFontCache() {
  delFonts();
}

void XOutputFontCache::startDoc(int screenNum, Visual *visual,
                        Colormap colormap, GBool trueColor,
                        int rMul, int gMul, int bMul,
                        int rShift, int gShift, int bShift,
                        Gulong *colors, int numColors) {
  delFonts();
  clear();

#if HAVE_T1LIB_H
  if (t1libControl != fontRastNone) {
    t1Engine = new T1FontEngine(display, visual, depth, colormap,
                        t1libControl == fontRastAALow ||
                          t1libControl == fontRastAAHigh,
                        t1libControl == fontRastAAHigh);
    if (t1Engine->isOk()) {
      if (trueColor) {
      t1Engine->useTrueColor(rMul, rShift, gMul, gShift, bMul, bShift);
      } else {
      t1Engine->useColorCube(colors, numColors);
      }
    } else {
      delete t1Engine;
      t1Engine = NULL;
    }
  }
#endif // HAVE_T1LIB_H

#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  if (freetypeControl != fontRastNone) {
    ftEngine = new FTFontEngine(display, visual, depth, colormap,
                        freetypeControl == fontRastAALow ||
                          freetypeControl == fontRastAAHigh);
    if (ftEngine->isOk()) {
      if (trueColor) {
      ftEngine->useTrueColor(rMul, rShift, gMul, gShift, bMul, bShift);
      } else {
      ftEngine->useColorCube(colors, numColors);
      }
    } else {
      delete ftEngine;
      ftEngine = NULL;
    }
  }
#endif // FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)

#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  if (freetypeControl != fontRastNone) {
    ttEngine = new TTFontEngine(display, visual, depth, colormap,
                        freetypeControl == fontRastAALow ||
                          freetypeControl == fontRastAAHigh);
    if (ttEngine->isOk()) {
      if (trueColor) {
      ttEngine->useTrueColor(rMul, rShift, gMul, gShift, bMul, bShift);
      } else {
      ttEngine->useColorCube(colors, numColors);
      }
    } else {
      delete ttEngine;
      ttEngine = NULL;
    }
  }
#endif // !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
}

void XOutputFontCache::delFonts() {
  int i;

  for (i = 0; i < nFonts; ++i) {
    delete fonts[i];
  }

#if HAVE_T1LIB_H
  // delete Type 1 font files
  deleteGList(t1FontFiles, XOutputT1FontFile);
  if (t1Engine) {
    delete t1Engine;
  }
#endif

#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  // delete FreeType font files
  deleteGList(ftFontFiles, XOutputFTFontFile);
  if (ftEngine) {
    delete ftEngine;
  }
#endif

#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  // delete TrueType fonts
  deleteGList(ttFontFiles, XOutputTTFontFile);
  if (ttEngine) {
    delete ttEngine;
  }
#endif
}

void XOutputFontCache::clear() {
  int i;

  for (i = 0; i < xOutFontCacheSize; ++i) {
    fonts[i] = NULL;
  }
  nFonts = 0;

#if HAVE_T1LIB_H
  // clear Type 1 font files
  t1FontFiles = new GList();
#endif

#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  // clear FreeType font cache
  ftFontFiles = new GList();
#endif

#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
  // clear TrueType font cache
  ttFontFiles = new GList();
#endif
}

XOutputFont *XOutputFontCache::getFont(XRef *xref, GfxFont *gfxFont,
                               double m11, double m12,
                               double m21, double m22) {
  XOutputFont *font;
  DisplayFontParam *dfp;
  GString *substName;
  double m11New, m12New, m21New, m22New;
  double w1, w2, v;
  double *fm;
  char *name;
  int index;
  int code;
  int i, j;

  // is it the most recently used font?
  if (nFonts > 0 && fonts[0]->matches(gfxFont->getID(), m11, m12, m21, m22)) {
    return fonts[0];
  }

  // is it in the cache?
  for (i = 1; i < nFonts; ++i) {
    if (fonts[i]->matches(gfxFont->getID(), m11, m12, m21, m22)) {
      font = fonts[i];
      for (j = i; j > 0; --j) {
      fonts[j] = fonts[j-1];
      }
      fonts[0] = font;
      return font;
    }
  }

  // try for a cached FontFile, an embedded font, or an external font
  // file
  font = NULL;
  switch (gfxFont->getType()) {
  case fontType1:
  case fontType1C:
#if HAVE_T1LIB_H
    if (t1libControl != fontRastNone) {
      font = tryGetT1Font(xref, gfxFont, m11, m12, m21, m22);
    }
#endif
#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    if (!font) {
      if (freetypeControl != fontRastNone) {
      font = tryGetFTFont(xref, gfxFont, m11, m12, m21, m22);
      }
    }
#endif
    break;
  case fontTrueType:
  case fontCIDType2:
#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    if (freetypeControl != fontRastNone) {
      font = tryGetFTFont(xref, gfxFont, m11, m12, m21, m22);
    }
#endif
#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    if (freetypeControl != fontRastNone) {
      font = tryGetTTFont(xref, gfxFont, m11, m12, m21, m22);
    }
#endif
    break;
  case fontCIDType0C:
#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    if (freetypeControl != fontRastNone) {
      font = tryGetFTFont(xref, gfxFont, m11, m12, m21, m22);
    }
#endif
    break;
  default:
    break;
  }

  if (!font) {

    // search for a display font mapping
    dfp = NULL;
    if (gfxFont->isCIDFont()) {
      if (((GfxCIDFont *)gfxFont)->getCollection()) {
      dfp = globalParams->
              getDisplayCIDFont(gfxFont->getName(),
                          ((GfxCIDFont *)gfxFont)->getCollection());
      } else {
      // this error (no CMap file) was already reported by GfxFont
      return NULL;
      }
    } else {
      if (gfxFont->getName()) {
      dfp = globalParams->getDisplayFont(gfxFont->getName());
      }
    }
    if (dfp) {
      font = tryGetFont(xref, dfp, gfxFont, m11, m12, m21, m22,
                  m11, m12, m21, m22, gFalse);
    }

    // substitute a font (8-bit fonts only)
    if (!font && !gfxFont->isCIDFont()) {

      // choose a substitute font
      if (gfxFont->isFixedWidth()) {
      index = 8;
      } else if (gfxFont->isSerif()) {
      index = 4;
      } else {
      index = 0;
      }
      if (gfxFont->isBold()) {
      index += 2;
      }
      if (gfxFont->isItalic()) {
      index += 1;
      }
      substName = new GString(xOutSubstFonts[index].name);

      // adjust the font matrix -- compare the width of 'm' in the
      // original font and the substituted font
      m11New = m11;
      m12New = m12;
      m21New = m21;
      m22New = m22;
      for (code = 0; code < 256; ++code) {
      if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) &&
          name[0] == 'm' && name[1] == '\0') {
        break;
      }
      }
      if (code < 256) {
      w1 = ((Gfx8BitFont *)gfxFont)->getWidth(code);
      w2 = xOutSubstFonts[index].mWidth;
      if (gfxFont->getType() == fontType3) {
        // This is a hack which makes it possible to substitute for some
        // Type 3 fonts.  The problem is that it's impossible to know what
        // the base coordinate system used in the font is without actually
        // rendering the font.  This code tries to guess by looking at the
        // width of the character 'm' (which breaks if the font is a
        // subset that doesn't contain 'm').
        if (w1 > 0 && (w1 > 1.1 * w2 || w1 < 0.9 * w2)) {
          w1 /= w2;
          m11New *= w1;
          m12New *= w1;
          m21New *= w1;
          m22New *= w1;
        }
        fm = gfxFont->getFontMatrix();
        v = (fm[0] == 0) ? 1 : (fm[3] / fm[0]);
        m21New *= v;
        m22New *= v;
      } else if (!gfxFont->isSymbolic()) {
        // if real font is substantially narrower than substituted
        // font, reduce the font size accordingly
        if (w1 > 0.01 && w1 < 0.9 * w2) {
          w1 /= w2;
          m11New *= w1;
          m21New *= w1;
        }
      }
      }

      // get the font
      dfp = globalParams->getDisplayFont(substName);
      delete substName;
      if (!dfp) {
      // this should never happen since GlobalParams sets up default
      // mappings for the Base-14 fonts
      error(-1, "Couldn't find a font for '%s'",
            gfxFont->getName()->getCString());
      return NULL;
      }
      font = tryGetFont(xref, dfp, gfxFont, m11, m12, m21, m22,
                  m11New, m12New, m21New, m22New, gTrue);
    }
  }

  // check for error
  if (!font) {
    // This will happen if the user specifies a bogus font in the
    // config file (a non-existent font file or a font that requires a
    // rasterizer that is disabled or wasn't built in), or if a CID
    // font has no associated font in the config file.
    if (gfxFont->isCIDFont()) {
      error(-1, "Couldn't find a font for the '%s' character collection",
          ((GfxCIDFont *)gfxFont)->getCollection()->getCString());
    } else {
      error(-1, "Couldn't find a font for '%s'",
          gfxFont->getName() ?
              gfxFont->getName()->getCString() : "[unnamed]");
    }
    return NULL;
  }

  // insert font in cache
  if (nFonts == xOutFontCacheSize) {
    --nFonts;
    delete fonts[nFonts];
  }
  for (j = nFonts; j > 0; --j) {
    fonts[j] = fonts[j-1];
  }
  fonts[0] = font;
  ++nFonts;

  return font;
}

XOutputFont *XOutputFontCache::tryGetFont(XRef *xref, DisplayFontParam *dfp,
                                GfxFont *gfxFont,
                                double m11Orig, double m12Orig,
                                double m21Orig, double m22Orig,
                                double m11, double m12,
                                double m21, double m22,
                                GBool subst) {
  XOutputFont *font;

  font = NULL;

  // create the new font
  switch (dfp->kind) {

  case displayFontX:
    font = tryGetServerFont(dfp->x.xlfd, dfp->x.encoding, gfxFont,
                      m11Orig, m12Orig, m21Orig, m22Orig,
                      m11, m12, m21, m22);
    break;

  case displayFontT1:
#if HAVE_T1LIB_H
    if (t1libControl != fontRastNone) {
      font = tryGetT1FontFromFile(xref, dfp->t1.fileName, gFalse, gfxFont,
                          m11Orig, m12Orig, m21Orig, m22Orig,
                          m11, m12, m21, m22, subst);
    }
#endif
#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    if (!font) {
      if (freetypeControl != fontRastNone) {
      font = tryGetFTFontFromFile(xref, dfp->t1.fileName, gFalse, gfxFont,
                            m11Orig, m12Orig, m21Orig, m22Orig,
                            m11, m12, m21, m22, subst);
      }
    }
#endif
#if !((FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)) || defined(HAVE_T1LIB_H))
    error(-1, "Config file specifies a Type 1 font,");
    error(-1, "but xpdf was not built with t1lib or FreeType2 support");
#endif
    break;

  case displayFontTT:
#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    if (freetypeControl != fontRastNone) {
      font = tryGetFTFontFromFile(xref, dfp->tt.fileName, gFalse, gfxFont,
                          m11Orig, m12Orig, m21Orig, m22Orig,
                          m11, m12, m21, m22, subst);
    }
#endif
#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    if (freetypeControl != fontRastNone) {
      font = tryGetTTFontFromFile(xref, dfp->tt.fileName, gFalse, gfxFont,
                          m11Orig, m12Orig, m21Orig, m22Orig,
                          m11, m12, m21, m22, subst);
    }
#endif
#if !(HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
    error(-1, "Config file specifies a TrueType font,");
    error(-1, "but xpdf was not built with FreeType support");
    dfp = NULL;
#endif
    break;
  }

  return font;
}

#if HAVE_T1LIB_H
XOutputFont *XOutputFontCache::tryGetT1Font(XRef *xref,
                                  GfxFont *gfxFont,
                                  double m11, double m12,
                                  double m21, double m22) {
  Ref *id;
  XOutputT1FontFile *xFontFile;
  XOutputFont *font;
  Ref embRef;
  GString *fileName;
  FILE *f;
  char *fontBuf;
  int fontLen;
  Type1CFontFile *ff;
  Object refObj, strObj;
  int c;
  int i;

  // check the already available font files
  id = gfxFont->getID();
  for (i = 0; i < t1FontFiles->getLength(); ++i) {
    xFontFile = (XOutputT1FontFile *)t1FontFiles->get(i);
    if (xFontFile->num == id->num && xFontFile->gen == id->gen &&
      !xFontFile->subst) {
      font = new XOutputT1Font(id, xFontFile->fontFile,
                         m11, m12, m21, m22,
                         m11, m12, m21, m22, display, xOut);
      if (!font->isOk()) {
      delete font;
      return NULL;
      }
      return font;
    }
  }

  // check for an embedded font
  if (gfxFont->getEmbeddedFontID(&embRef)) {

    // create the font file
    fileName = NULL;
    if (!openTempFile(&fileName, &f, "wb", NULL)) {
      error(-1, "Couldn't create temporary Type 1 font file");
      return NULL;
    }
    if (gfxFont->getType() == fontType1C) {
      if (!(fontBuf = gfxFont->readEmbFontFile(xref, &fontLen))) {
      fclose(f);
      return NULL;
      }
      ff = new Type1CFontFile(fontBuf, fontLen);
      ff->convertToType1(outputToFile, f);
      delete ff;
      gfree(fontBuf);
    } else { // fontType1
      refObj.initRef(embRef.num, embRef.gen);
      refObj.fetch(xref, &strObj);
      refObj.free();
      strObj.streamReset();
      while ((c = strObj.streamGetChar()) != EOF) {
      fputc(c, f);
      }
      strObj.streamClose();
      strObj.free();
    }
    fclose(f);

    // create the Font
    font = tryGetT1FontFromFile(xref, fileName, gTrue, gfxFont,
                        m11, m12, m21, m22,
                        m11, m12, m21, m22, gFalse);

    // on systems with Unix hard link semantics, this will remove the
    // last link to the temp file
    unlink(fileName->getCString());

    delete fileName;

  // check for an external font file
  } else if ((fileName = gfxFont->getExtFontFile())) {
    font = tryGetT1FontFromFile(xref, fileName, gFalse, gfxFont,
                        m11, m12, m21, m22,
                        m11, m12, m21, m22, gFalse);

  } else {
    font = NULL;
  }

  return font;
}

XOutputFont *XOutputFontCache::tryGetT1FontFromFile(XRef *xref,
                                        GString *fileName,
                                        GBool deleteFile,
                                        GfxFont *gfxFont,
                                        double m11Orig,
                                        double m12Orig,
                                        double m21Orig,
                                        double m22Orig,
                                        double m11, double m12,
                                        double m21, double m22,
                                        GBool subst) {
  Ref *id;
  T1FontFile *fontFile;
  XOutputFont *font;

  // create the t1lib font file
  fontFile = new T1FontFile(t1Engine, fileName->getCString(),
                      ((Gfx8BitFont *)gfxFont)->getEncoding(),
                      gfxFont->getFontBBox());
  if (!fontFile->isOk()) {
    error(-1, "Couldn't create t1lib font from '%s'",
        fileName->getCString());
    delete fontFile;
    if (deleteFile) {
      unlink(fileName->getCString());
    }
    return NULL;
  }

  // add to list
  id = gfxFont->getID();
  t1FontFiles->append(new XOutputT1FontFile(id->num, id->gen,
                                  subst, fontFile,
                                  deleteFile ? fileName->copy()
                                             : (GString *)NULL));

  // create the Font
  font = new XOutputT1Font(gfxFont->getID(), fontFile,
                     m11Orig, m12Orig, m21Orig, m22Orig,
                     m11, m12, m21, m22, display, xOut);
  if (!font->isOk()) {
    delete font;
    return NULL;
  }
  return font;
}
#endif // HAVE_T1LIB_H

#if FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
XOutputFont *XOutputFontCache::tryGetFTFont(XRef *xref,
                                  GfxFont *gfxFont,
                                  double m11, double m12,
                                  double m21, double m22) {
  Ref *id;
  XOutputFTFontFile *xFontFile;
  XOutputFont *font;
  Ref embRef;
  GString *fileName;
  FILE *f;
#if 1 //~ need this until FT can handle fonts with missing tables
  char *fontBuf;
  int fontLen;
  TrueTypeFontFile *ff;
#endif
  Object refObj, strObj;
  int c;
  int i;

  // check the already available font files
  id = gfxFont->getID();
  for (i = 0; i < ftFontFiles->getLength(); ++i) {
    xFontFile = (XOutputFTFontFile *)ftFontFiles->get(i);
    if (xFontFile->num == id->num && xFontFile->gen == id->gen &&
      !xFontFile->subst) {
      font = new XOutputFTFont(id, xFontFile->fontFile,
                         m11, m12, m21, m22,
                         m11, m12, m21, m22, display, xOut);
      if (!font->isOk()) {
      delete font;
      return NULL;
      }
      return font;
    }
  }

  // check for an embedded font
  if (gfxFont->getEmbeddedFontID(&embRef)) {

    // create the font file
    fileName = NULL;
    if (!openTempFile(&fileName, &f, "wb", NULL)) {
      error(-1, "Couldn't create temporary TrueType font file");
      return NULL;
    }
#if 1 //~ need this until FT can handle fonts with missing tables
    if (gfxFont->getType() == fontTrueType ||
      gfxFont->getType() == fontCIDType2) {
      if (!(fontBuf = gfxFont->readEmbFontFile(xref, &fontLen))) {
      fclose(f);
      return NULL;
      }
      ff = new TrueTypeFontFile(fontBuf, fontLen);
      ff->writeTTF(f);
      delete ff;
      gfree(fontBuf);
    } else {
      refObj.initRef(embRef.num, embRef.gen);
      refObj.fetch(xref, &strObj);
      refObj.free();
      strObj.streamReset();
      while ((c = strObj.streamGetChar()) != EOF) {
      fputc(c, f);
      }
      strObj.streamClose();
      strObj.free();
    }
#else
    refObj.initRef(embRef.num, embRef.gen);
    refObj.fetch(xref, &strObj);
    refObj.free();
    strObj.streamReset();
    while ((c = strObj.streamGetChar()) != EOF) {
      fputc(c, f);
    }
    strObj.streamClose();
    strObj.free();
#endif
    fclose(f);

    // create the Font
    font = tryGetFTFontFromFile(xref, fileName, gTrue, gfxFont,
                        m11, m12, m21, m22,
                        m11, m12, m21, m22, gFalse);

    // on systems with Unix hard link semantics, this will remove the
    // last link to the temp file
    unlink(fileName->getCString());

    delete fileName;

  // check for an external font file
  } else if ((fileName = gfxFont->getExtFontFile())) {
    font = tryGetFTFontFromFile(xref, fileName, gFalse, gfxFont,
                        m11, m12, m21, m22,
                        m11, m12, m21, m22, gFalse);

  } else {
    font = NULL;
  }

  return font;
}

XOutputFont *XOutputFontCache::tryGetFTFontFromFile(XRef *xref,
                                        GString *fileName,
                                        GBool deleteFile,
                                        GfxFont *gfxFont,
                                        double m11Orig,
                                        double m12Orig,
                                        double m21Orig,
                                        double m22Orig,
                                        double m11, double m12,
                                        double m21, double m22,
                                        GBool subst) {
  Ref *id;
  FTFontFile *fontFile;
  XOutputFont *font;

  // create the FreeType font file
  if (gfxFont->isCIDFont()) {
    if (gfxFont->getType() == fontCIDType2) {
      fontFile = new FTFontFile(ftEngine, fileName->getCString(),
                        ((GfxCIDFont *)gfxFont)->getCIDToGID(),
                        ((GfxCIDFont *)gfxFont)->getCIDToGIDLen());
    } else { // fontCIDType0C
      fontFile = new FTFontFile(ftEngine, fileName->getCString());
    }
  } else {
    fontFile = new FTFontFile(ftEngine, fileName->getCString(),
                        ((Gfx8BitFont *)gfxFont)->getEncoding(),
                        ((Gfx8BitFont *)gfxFont)->getHasEncoding());
  }
  if (!fontFile->isOk()) {
    error(-1, "Couldn't create FreeType font from '%s'",
        fileName->getCString());
    delete fontFile;
    if (deleteFile) {
      unlink(fileName->getCString());
    }
    return NULL;
  }

  // add to list
  id = gfxFont->getID();
  ftFontFiles->append(new XOutputFTFontFile(id->num, id->gen,
                                  subst, fontFile,
                                  deleteFile ? fileName->copy()
                                             : (GString *)NULL));

  // create the Font
  font = new XOutputFTFont(gfxFont->getID(), fontFile,
                     m11Orig, m12Orig, m21Orig, m22Orig,
                     m11, m12, m21, m22, display, xOut);
  if (!font->isOk()) {
    delete font;
    return NULL;
  }
  return font;
}
#endif // FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)

#if !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)
XOutputFont *XOutputFontCache::tryGetTTFont(XRef *xref,
                                  GfxFont *gfxFont,
                                  double m11, double m12,
                                  double m21, double m22) {
  Ref *id;
  XOutputTTFontFile *xFontFile;
  XOutputFont *font;
  Ref embRef;
  GString *fileName;
  FILE *f;
  Object refObj, strObj;
  int c;
  int i;

  // check the already available font files
  id = gfxFont->getID();
  xFontFile = NULL;
  for (i = 0; i < ttFontFiles->getLength(); ++i) {
    xFontFile = (XOutputTTFontFile *)ttFontFiles->get(i);
    if (xFontFile->num == id->num && xFontFile->gen == id->gen &&
      !xFontFile->subst) {
      font = new XOutputTTFont(id, xFontFile->fontFile,
                         m11, m12, m21, m22,
                         m11, m12, m21, m22, display, xOut);
      if (!font->isOk()) {
      delete font;
      return NULL;
      }
      return font;
    }
  }

  // check for an embedded font
  if (gfxFont->getEmbeddedFontID(&embRef)) {

    // create the font file
    fileName = NULL;
    if (!openTempFile(&fileName, &f, "wb", NULL)) {
      error(-1, "Couldn't create temporary TrueType font file");
      return NULL;
    }
    refObj.initRef(embRef.num, embRef.gen);
    refObj.fetch(xref, &strObj);
    refObj.free();
    strObj.streamReset();
    while ((c = strObj.streamGetChar()) != EOF) {
      fputc(c, f);
    }
    strObj.streamClose();
    strObj.free();
    fclose(f);

    // create the Font
    font = tryGetTTFontFromFile(xref, fileName, gTrue, gfxFont,
                        m11, m12, m21, m22,
                        m11, m12, m21, m22, gFalse);

    // on systems with Unix hard link semantics, this will remove the
    // last link to the temp file
    unlink(fileName->getCString());

    delete fileName;

  } else if ((fileName = gfxFont->getExtFontFile())) {
    font = tryGetTTFontFromFile(xref, fileName, gFalse, gfxFont,
                        m11, m12, m21, m22,
                        m11, m12, m21, m22, gFalse);

  } else {
    font = NULL;
  }

  return font;
}

XOutputFont *XOutputFontCache::tryGetTTFontFromFile(XRef *xref,
                                        GString *fileName,
                                        GBool deleteFile,
                                        GfxFont *gfxFont,
                                        double m11Orig,
                                        double m12Orig,
                                        double m21Orig,
                                        double m22Orig,
                                        double m11, double m12,
                                        double m21, double m22,
                                        GBool subst) {
  Ref *id;
  TTFontFile *fontFile;
  XOutputFont *font;

  // create the FreeType font file
  if (gfxFont->isCIDFont()) {
    // fontCIDType2
    fontFile = new TTFontFile(ttEngine, fileName->getCString(),
                        ((GfxCIDFont *)gfxFont)->getCIDToGID(),
                        ((GfxCIDFont *)gfxFont)->getCIDToGIDLen());
  } else {
    fontFile = new TTFontFile(ttEngine, fileName->getCString(),
                        ((Gfx8BitFont *)gfxFont)->getEncoding(),
                        ((Gfx8BitFont *)gfxFont)->getHasEncoding());
  }
  if (!fontFile->isOk()) {
    error(-1, "Couldn't create FreeType font from '%s'",
        fileName->getCString());
    delete fontFile;
    if (deleteFile) {
      unlink(fileName->getCString());
    }
    return NULL;
  }

  // add to list
  id = gfxFont->getID();
  ttFontFiles->append(new XOutputTTFontFile(id->num, id->gen,
                                  subst, fontFile,
                                  deleteFile ? fileName->copy()
                                             : (GString *)NULL));

  // create the Font
  font = new XOutputTTFont(gfxFont->getID(), fontFile,
                     m11Orig, m12Orig, m21Orig, m22Orig,
                     m11, m12, m21, m22, display, xOut);
  if (!font->isOk()) {
    delete font;
    return NULL;
  }
  return font;
}
#endif // !FREETYPE2 && (HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H)

XOutputFont *XOutputFontCache::tryGetServerFont(GString *xlfd,
                                    GString *encodingName,
                                    GfxFont *gfxFont,
                                    double m11Orig, double m12Orig,
                                    double m21Orig, double m22Orig,
                                    double m11, double m12,
                                    double m21, double m22) {
  XOutputFont *font;
  UnicodeMap *uMap;
  CharCodeToUnicode *ctu;

  uMap = globalParams->getUnicodeMap(encodingName);
  if (gfxFont->isCIDFont()) {
    ctu = ((GfxCIDFont *)gfxFont)->getToUnicode();
    font = new XOutputServer16BitFont(gfxFont->getID(), xlfd, uMap, ctu,
                              m11Orig, m12Orig, m21Orig, m22Orig,
                              m11, m12, m21, m22,
                              display, xOut);
    ctu->decRefCnt();
  } else {
    ctu = ((Gfx8BitFont *)gfxFont)->getToUnicode();
    font = new XOutputServer8BitFont(gfxFont->getID(), xlfd, uMap, ctu,
                             m11Orig, m12Orig, m21Orig, m22Orig,
                             m11, m12, m21, m22,
                             display, xOut);
    ctu->decRefCnt();
  }
  uMap->decRefCnt();
  if (!font->isOk()) {
    delete font;
    return NULL;
  }
  return font;
}

//------------------------------------------------------------------------
// T3FontCache
//------------------------------------------------------------------------

struct T3FontCacheTag {
  Gushort code;
  Gushort mru;                // valid bit (0x8000) and MRU index
  double wx, wy;        // untransformed glyph metrics
};

class T3FontCache {
public:

  T3FontCache(Ref *fontID, double m11A, double m12A,
            double m21A, double m22A,
            int glyphXA, int glyphYA, int glyphWA, int glyphHA,
            Display *displayA, Visual *visual, Guint depth,
            Pixmap origPixmap);
  ~T3FontCache();
  GBool matches(Ref *idA, double m11A, double m12A,
            double m21A, double m22A)
    { return fontID.num == idA->num && fontID.gen == idA->gen &&
           m11 == m11A && m12 == m12A && m21 == m21A && m22 == m22A; }

  Ref fontID;                 // PDF font ID
  double m11, m12, m21, m22;  // transform matrix
  int glyphX, glyphY;         // pixel offset of glyph pixmaps
  int glyphW, glyphH;         // size of glyph pixmaps, in pixels
  int glyphSize;        // size of glyph pixmaps, in bytes
  int cacheSets;        // number of sets in cache
  int cacheAssoc;       // cache associativity (glyphs per set)
  Guchar *cacheData;          // glyph pixmap cache
  T3FontCacheTag *cacheTags;  // cache tags, i.e., char codes
  Display *display;
  Pixmap pixmap;
  XImage *image;
};

T3FontCache::T3FontCache(Ref *fontIDA, double m11A, double m12A,
                   double m21A, double m22A,
                   int glyphXA, int glyphYA, int glyphWA, int glyphHA,
                   Display *displayA, Visual *visual, Guint depth,
                   Pixmap origPixmap) {
  int i;

  fontID = *fontIDA;
  m11 = m11A;
  m12 = m12A;
  m21 = m21A;
  m22 = m22A;
  glyphX = glyphXA;
  glyphY = glyphYA;
  glyphW = glyphWA;
  glyphH = glyphHA;
  glyphSize = glyphW * glyphH;
  cacheAssoc = 8;
  if (glyphSize <= 256) {
    cacheSets = 8;
  } else if (glyphSize <= 512) {
    cacheSets = 4;
  } else if (glyphSize <= 1024) {
    cacheSets = 2;
  } else {
    cacheSets = 1;
  }
  cacheData = (Guchar *)gmalloc(cacheSets * cacheAssoc * glyphSize);
  cacheTags = (T3FontCacheTag *)gmalloc(cacheSets * cacheAssoc *
                              sizeof(T3FontCacheTag));
  for (i = 0; i < cacheSets * cacheAssoc; ++i) {
    cacheTags[i].mru = i & (cacheAssoc - 1);
  }
  display = displayA;
  pixmap = XCreatePixmap(display, origPixmap, glyphW, glyphH, depth);
  image = XCreateImage(display, visual, depth, ZPixmap, 0, NULL,
                   glyphW, glyphH, 8, 0);
  image->data = (char *)gmalloc(glyphH * image->bytes_per_line);
}

T3FontCache::~T3FontCache() {
  gfree(cacheData);
  gfree(cacheTags);
  XFreePixmap(display, pixmap);
  gfree(image->data);
  image->data = NULL;
  XDestroyImage(image);
}

struct T3GlyphStack {
  GBool cacheable;
  Gushort code;
  T3FontCache *cache;
  int cacheIdx;
  T3FontCacheTag *cacheTag;
  Guchar *cacheData;
  double x, y;
  Unicode *u;
  int uLen;
  GfxRGB color;
  int origPixmapW, origPixmapH;
  Pixmap origPixmap;
  GC origStrokeGC;
  GC origFillGC;
  Region origClipRegion;
  double origCTM4, origCTM5;
  double wx, wy;        // untransformed glyph metrics
  T3GlyphStack *next;
};

//------------------------------------------------------------------------
// XOutputDev
//------------------------------------------------------------------------

XOutputDev::XOutputDev(Display *displayA, int screenNumA,
                   Visual *visualA, Colormap colormapA,
                   GBool reverseVideoA, unsigned long paperColorA,
                   GBool installCmap, int rgbCubeSize,
                   int forceDepth) {
  XVisualInfo visualTempl;
  XVisualInfo *visualList;
  int nVisuals;
  Gulong mask;
  XColor xcolor;
  XColor *xcolors;
  int r, g, b, n, m;
  GBool ok;

  // no document yet
  xref = NULL;

  // display / screen / visual / colormap
  display = displayA;
  screenNum = screenNumA;
  visual = visualA;
  colormap = colormapA;

  // no pixmap yet
  pixmapW = pixmapH = 0;

  // check for TrueColor visual
  if (forceDepth != 0) {
    depth = forceDepth;
    trueColor = depth >= 16;
  } else {
    visualTempl.visualid = XVisualIDFromVisual(visual);
    visualList = XGetVisualInfo(display, VisualIDMask,
                        &visualTempl, &nVisuals);
    if (nVisuals < 1) {
      // this shouldn't happen
      XFree((XPointer)visualList);
      visualList = XGetVisualInfo(display, VisualNoMask, &visualTempl,
                          &nVisuals);
    }
    depth = visualList->depth;
    if (visualList->c_class == TrueColor) {
      trueColor = gTrue;
      for (mask = visualList->red_mask, rShift = 0;
         mask && !(mask & 1);
         mask >>= 1, ++rShift) ;
      rMul = (int)mask;
      for (mask = visualList->green_mask, gShift = 0;
         mask && !(mask & 1);
         mask >>= 1, ++gShift) ;
      gMul = (int)mask;
      for (mask = visualList->blue_mask, bShift = 0;
         mask && !(mask & 1);
         mask >>= 1, ++bShift) ;
      bMul = (int)mask;
    } else {
      trueColor = gFalse;
    }
    XFree((XPointer)visualList);
  }

  // allocate a color cube
  if (!trueColor) {
    redMap[BlackPixel(display, screenNum) & 0xff] = 0;
    redMap[WhitePixel(display, screenNum) & 0xff] = 1;

    // set colors in private colormap
    if (installCmap) {
      for (numColors = 6; numColors >= 2; --numColors) {
      m = numColors * numColors * numColors;
      if (XAllocColorCells(display, colormap, False, NULL, 0, colors, m)) {
        break;
      }
      }
      if (numColors >= 2) {
      m = numColors * numColors * numColors;
      xcolors = (XColor *)gmalloc(m * sizeof(XColor));
      n = 0;
      for (r = 0; r < numColors; ++r) {
        for (g = 0; g < numColors; ++g) {
          for (b = 0; b < numColors; ++b) {
            xcolors[n].pixel = colors[n];
            xcolors[n].red = (r * 65535) / (numColors - 1);
            xcolors[n].green = (g * 65535) / (numColors - 1);
            xcolors[n].blue = (b * 65535) / (numColors - 1);
            xcolors[n].flags = DoRed | DoGreen | DoBlue;
            redMap[xcolors[n].pixel & 0xff] = xcolors[n].red / 65535.0;
            ++n;
          }
        }
      }
      XStoreColors(display, colormap, xcolors, m);
      gfree(xcolors);
      } else {
      numColors = 1;
      colors[0] = BlackPixel(display, screenNum);
      colors[1] = WhitePixel(display, screenNum);
      }

    // allocate colors in shared colormap
    } else {
      if (rgbCubeSize > maxRGBCube) {
      rgbCubeSize = maxRGBCube;
      }
      ok = gFalse;
      for (numColors = rgbCubeSize; numColors >= 2; --numColors) {
      ok = gTrue;
      n = 0;
      for (r = 0; r < numColors && ok; ++r) {
        for (g = 0; g < numColors && ok; ++g) {
          for (b = 0; b < numColors && ok; ++b) {
            if (n == 0) {
            colors[n] = BlackPixel(display, screenNum);
            redMap[colors[n] & 0xff] = 0;
            ++n;
            } else {
            xcolor.red = (r * 65535) / (numColors - 1);
            xcolor.green = (g * 65535) / (numColors - 1);
            xcolor.blue = (b * 65535) / (numColors - 1);
            if (XAllocColor(display, colormap, &xcolor)) {
              colors[n++] = xcolor.pixel;
              redMap[xcolor.pixel & 0xff] = xcolor.red / 65535.0;
            } else {
              ok = gFalse;
            }
            }
          }
        }
      }
      if (ok) {
        break;
      }
      XFreeColors(display, colormap, &colors[1], n-1, 0);
      }
      if (!ok) {
      numColors = 1;
      colors[0] = BlackPixel(display, screenNum);
      colors[1] = WhitePixel(display, screenNum);
      }
    }
  }

  // misc parameters
  reverseVideo = reverseVideoA;
  paperColor = paperColorA;

  // set up the font cache and fonts
  gfxFont = NULL;
  font = NULL;
  fontCache = new XOutputFontCache(display, depth, this,
                           globalParams->getT1libControl(),
                           globalParams->getFreeTypeControl());
  nT3Fonts = 0;
  t3GlyphStack = NULL;

  // empty state stack
  save = NULL;

  // create text object
  text = new TextPage(gFalse);
}

XOutputDev::~XOutputDev() {
  int i;

  delete fontCache;
  for (i = 0; i < nT3Fonts; ++i) {
    delete t3FontCache[i];
  }
  delete text;
}

void XOutputDev::startDoc(XRef *xrefA) {
  int i;

  xref = xrefA;
  fontCache->startDoc(screenNum, visual, colormap, trueColor, rMul, gMul, bMul,
                  rShift, gShift, bShift, colors, numColors);
  for (i = 0; i < nT3Fonts; ++i) {
    delete t3FontCache[i];
  }
  nT3Fonts = 0;
}

void XOutputDev::startPage(int pageNum, GfxState *state) {
  XGCValues gcValues;
  XRectangle rect;

  // default line flatness
  flatness = 0;

  // allocate GCs
  gcValues.foreground = BlackPixel(display, screenNum);
  gcValues.background = WhitePixel(display, screenNum);
  gcValues.line_width = 0;
  gcValues.line_style = LineSolid;
  strokeGC = XCreateGC(display, pixmap,
                   GCForeground | GCBackground | GCLineWidth | GCLineStyle,
                       &gcValues);
  fillGC = XCreateGC(display, pixmap,
                 GCForeground | GCBackground | GCLineWidth | GCLineStyle,
                 &gcValues);
  gcValues.foreground = paperColor;
  paperGC = XCreateGC(display, pixmap,
                  GCForeground | GCBackground | GCLineWidth | GCLineStyle,
                  &gcValues);

  // initialize clip region
  clipRegion = XCreateRegion();
  rect.x = rect.y = 0;
  rect.width = pixmapW;
  rect.height = pixmapH;
  XUnionRectWithRegion(&rect, clipRegion, clipRegion);
  XSetRegion(display, strokeGC, clipRegion);
  XSetRegion(display, fillGC, clipRegion);

  // clear font
  gfxFont = NULL;
  font = NULL;

  // clear window
  XFillRectangle(display, pixmap, paperGC, 0, 0, pixmapW, pixmapH);

  // clear text object
  text->clear();
}

void XOutputDev::endPage() {
  XOutputState *s;

  text->coalesce();

  // clear state stack, free all GCs, free the clip region
  while (save) {
    s = save;
    save = save->next;
    XFreeGC(display, s->strokeGC);
    XFreeGC(display, s->fillGC);
    XDestroyRegion(s->clipRegion);
    delete s;
  }
  XFreeGC(display, strokeGC);
  XFreeGC(display, fillGC);
  XFreeGC(display, paperGC);
  XDestroyRegion(clipRegion);
}

void XOutputDev::drawLink(Link *link, Catalog *catalog) {
  double x1, y1, x2, y2, w;
  GfxRGB rgb;
  XPoint points[5];
  int x, y;

  link->getBorder(&x1, &y1, &x2, &y2, &w);
  if (w > 0) {
    rgb.r = 0;
    rgb.g = 0;
    rgb.b = 1;
    XSetForeground(display, strokeGC, findColor(&rgb));
    XSetLineAttributes(display, strokeGC, xoutRound(w),
                   LineSolid, CapRound, JoinRound);
    cvtUserToDev(x1, y1, &x, &y);
    points[0].x = points[4].x = x;
    points[0].y = points[4].y = y;
    cvtUserToDev(x2, y1, &x, &y);
    points[1].x = x;
    points[1].y = y;
    cvtUserToDev(x2, y2, &x, &y);
    points[2].x = x;
    points[2].y = y;
    cvtUserToDev(x1, y2, &x, &y);
    points[3].x = x;
    points[3].y = y;
    XDrawLines(display, pixmap, strokeGC, points, 5, CoordModeOrigin);
  }
}

void XOutputDev::saveState(GfxState *state) {
  XOutputState *s;
  XGCValues values;

  // save current state
  s = new XOutputState;
  s->strokeGC = strokeGC;
  s->fillGC = fillGC;
  s->clipRegion = clipRegion;

  // push onto state stack
  s->next = save;
  save = s;

  // create a new current state by copying
  strokeGC = XCreateGC(display, pixmap, 0, &values);
  XCopyGC(display, s->strokeGC, 0xffffffff, strokeGC);
  fillGC = XCreateGC(display, pixmap, 0, &values);
  XCopyGC(display, s->fillGC, 0xffffffff, fillGC);
  clipRegion = XCreateRegion();
  XUnionRegion(s->clipRegion, clipRegion, clipRegion);
  XSetRegion(display, strokeGC, clipRegion);
  XSetRegion(display, fillGC, clipRegion);
}

void XOutputDev::restoreState(GfxState *state) {
  XOutputState *s;

  if (save) {
    // kill current state
    XFreeGC(display, strokeGC);
    XFreeGC(display, fillGC);
    XDestroyRegion(clipRegion);

    // restore state
    flatness = state->getFlatness();
    strokeGC = save->strokeGC;
    fillGC = save->fillGC;
    clipRegion = save->clipRegion;
    XSetRegion(display, strokeGC, clipRegion);
    XSetRegion(display, fillGC, clipRegion);

    // pop state stack
    s = save;
    save = save->next;
    delete s;
  }
}

void XOutputDev::updateAll(GfxState *state) {
  updateLineAttrs(state, gTrue);
  updateFlatness(state);
  updateMiterLimit(state);
  updateFillColor(state);
  updateStrokeColor(state);
  updateFont(state);
}

void XOutputDev::updateCTM(GfxState *state, double m11, double m12,
                     double m21, double m22, double m31, double m32) {
  updateLineAttrs(state, gTrue);
}

void XOutputDev::updateLineDash(GfxState *state) {
  updateLineAttrs(state, gTrue);
}

void XOutputDev::updateFlatness(GfxState *state) {
  flatness = state->getFlatness();
}

void XOutputDev::updateLineJoin(GfxState *state) {
  updateLineAttrs(state, gFalse);
}

void XOutputDev::updateLineCap(GfxState *state) {
  updateLineAttrs(state, gFalse);
}

// unimplemented
void XOutputDev::updateMiterLimit(GfxState *state) {
}

void XOutputDev::updateLineWidth(GfxState *state) {
  updateLineAttrs(state, gFalse);
}

void XOutputDev::updateLineAttrs(GfxState *state, GBool updateDash) {
  double width;
  int cap, join;
  double *dashPattern;
  int dashLength;
  double dashStart;
  char dashList[20];
  int i;

  width = state->getTransformedLineWidth();
  switch (state->getLineCap()) {
  case 0: cap = CapButt; break;
  case 1: cap = CapRound; break;
  case 2: cap = CapProjecting; break;
  default:
    error(-1, "Bad line cap style (%d)", state->getLineCap());
    cap = CapButt;
    break;
  }
  switch (state->getLineJoin()) {
  case 0: join = JoinMiter; break;
  case 1: join = JoinRound; break;
  case 2: join = JoinBevel; break;
  default:
    error(-1, "Bad line join style (%d)", state->getLineJoin());
    join = JoinMiter;
    break;
  }
  state->getLineDash(&dashPattern, &dashLength, &dashStart);
#if 1 //~ work around a bug in XFree86 (???)
  if (dashLength > 0 && cap == CapProjecting) {
    cap = CapButt;
  }
#endif
  XSetLineAttributes(display, strokeGC, xoutRound(width),
                 dashLength > 0 ? LineOnOffDash : LineSolid,
                 cap, join);
  if (updateDash && dashLength > 0) {
    if (dashLength > 20)
      dashLength = 20;
    for (i = 0; i < dashLength; ++i) {
      dashList[i] = xoutRound(state->transformWidth(dashPattern[i]));
      if (dashList[i] == 0)
      dashList[i] = 1;
    }
    XSetDashes(display, strokeGC, xoutRound(dashStart), dashList, dashLength);
  }
}

void XOutputDev::updateFillColor(GfxState *state) {
  GfxRGB rgb;

  state->getFillRGB(&rgb);
  if (reverseVideo) {
    rgb.r = 1 - rgb.r;
    rgb.g = 1 - rgb.g;
    rgb.b = 1 - rgb.b;
  }
  XSetForeground(display, fillGC, findColor(&rgb));
}

void XOutputDev::updateStrokeColor(GfxState *state) {
  GfxRGB rgb;

  state->getStrokeRGB(&rgb);
  if (reverseVideo) {
    rgb.r = 1 - rgb.r;
    rgb.g = 1 - rgb.g;
    rgb.b = 1 - rgb.b;
  }
  XSetForeground(display, strokeGC, findColor(&rgb));
}

void XOutputDev::updateFont(GfxState *state) {
  double m11, m12, m21, m22;

  text->updateFont(state);

  if (!(gfxFont = state->getFont())) {
    font = NULL;
    return;
  }
  if (gfxFont->getType() == fontType3) {
    font = NULL;
    return;
  }
  state->getFontTransMat(&m11, &m12, &m21, &m22);
  m11 *= state->getHorizScaling();
  m12 *= state->getHorizScaling();
  font = fontCache->getFont(xref, gfxFont, m11, m12, m21, m22);
  if (font) {
    font->updateGC(fillGC);
    font->updateGC(strokeGC);
  }
}

void XOutputDev::stroke(GfxState *state) {
  XPoint *points;
  int *lengths;
  int n, size, numPoints, i, j;

  // transform points
  n = convertPath(state, &points, &size, &numPoints, &lengths, gFalse);

  // draw each subpath
  j = 0;
  for (i = 0; i < n; ++i) {
    XDrawLines(display, pixmap, strokeGC, points + j, lengths[i],
             CoordModeOrigin);
    j += lengths[i];
  }

  // free points and lengths arrays
  if (points != tmpPoints)
    gfree(points);
  if (lengths != tmpLengths)
    gfree(lengths);
}

void XOutputDev::fill(GfxState *state) {
  doFill(state, WindingRule);
}

void XOutputDev::eoFill(GfxState *state) {
  doFill(state, EvenOddRule);
}

//
//  X doesn't color the pixels on the right-most and bottom-most
//  borders of a polygon.  This means that one-pixel-thick polygons
//  are not colored at all.  I think this is supposed to be a
//  feature, but I can't figure out why.  So after it fills a
//  polygon, it also draws lines around the border.  This is done
//  only for single-component polygons, since it's not very
//  compatible with the compound polygon kludge (see convertPath()).
//
void XOutputDev::doFill(GfxState *state, int rule) {
  XPoint *points;
  int *lengths;
  int n, size, numPoints, i, j;

  // set fill rule
  XSetFillRule(display, fillGC, rule);

  // transform points, build separate polygons
  n = convertPath(state, &points, &size, &numPoints, &lengths, gTrue);

  // fill them
  j = 0;
  for (i = 0; i < n; ++i) {
    XFillPolygon(display, pixmap, fillGC, points + j, lengths[i],
             Complex, CoordModeOrigin);
    if (state->getPath()->getNumSubpaths() == 1) {
      XDrawLines(display, pixmap, fillGC, points + j, lengths[i],
             CoordModeOrigin);
    }
    j += lengths[i] + 1;
  }

  // free points and lengths arrays
  if (points != tmpPoints)
    gfree(points);
  if (lengths != tmpLengths)
    gfree(lengths);
}

void XOutputDev::clip(GfxState *state) {
  doClip(state, WindingRule);
}

void XOutputDev::eoClip(GfxState *state) {
  doClip(state, EvenOddRule);
}

void XOutputDev::doClip(GfxState *state, int rule) {
  Region region, region2;
  XPoint *points;
  int *lengths;
  int n, size, numPoints, i, j;

  // transform points, build separate polygons
  n = convertPath(state, &points, &size, &numPoints, &lengths, gTrue);

  // construct union of subpath regions
  // (XPolygonRegion chokes if there aren't at least three points --
  // this happens if the PDF file does moveto/closepath/clip, which
  // sets an empty clipping region)
  if (lengths[0] > 2) {
    region = XPolygonRegion(points, lengths[0], rule);
  } else {
    region = XCreateRegion();
  }
  j = lengths[0] + 1;
  for (i = 1; i < n; ++i) {
    if (lengths[i] > 2) {
      region2 = XPolygonRegion(points + j, lengths[i], rule);
    } else {
      region2 = XCreateRegion();
    }
    XUnionRegion(region2, region, region);
    XDestroyRegion(region2);
    j += lengths[i] + 1;
  }

  // intersect region with clipping region
  XIntersectRegion(region, clipRegion, clipRegion);
  XDestroyRegion(region);
  XSetRegion(display, strokeGC, clipRegion);
  XSetRegion(display, fillGC, clipRegion);

  // free points and lengths arrays
  if (points != tmpPoints)
    gfree(points);
  if (lengths != tmpLengths)
    gfree(lengths);
}

//
// Transform points in the path and convert curves to line segments.
// Builds a set of subpaths and returns the number of subpaths.
// If <fillHack> is set, close any unclosed subpaths and activate a
// kludge for polygon fills:  First, it divides up the subpaths into
// non-overlapping polygons by simply comparing bounding rectangles.
// Then it connects subaths within a single compound polygon to a single
// point so that X can fill the polygon (sort of).
//
int XOutputDev::convertPath(GfxState *state, XPoint **points, int *size,
                      int *numPoints, int **lengths, GBool fillHack) {
  GfxPath *path;
  BoundingRect *rects;
  BoundingRect rect;
  int n, i, ii, j, k, k0;

  // get path and number of subpaths
  path = state->getPath();
  n = path->getNumSubpaths();

  // allocate lengths array
  if (n < numTmpSubpaths)
    *lengths = tmpLengths;
  else
    *lengths = (int *)gmalloc(n * sizeof(int));

  // allocate bounding rectangles array
  if (fillHack) {
    if (n < numTmpSubpaths)
      rects = tmpRects;
    else
      rects = (BoundingRect *)gmalloc(n * sizeof(BoundingRect));
  } else {
    rects = NULL;
  }

  // do each subpath
  *points = tmpPoints;
  *size = numTmpPoints;
  *numPoints = 0;
  for (i = 0; i < n; ++i) {

    // transform the points
    j = *numPoints;
    convertSubpath(state, path->getSubpath(i), points, size, numPoints);

    // construct bounding rectangle
    if (fillHack) {
      rects[i].xMin = rects[i].xMax = (*points)[j].x;
      rects[i].yMin = rects[i].yMax = (*points)[j].y;
      for (k = j + 1; k < *numPoints; ++k) {
      if ((*points)[k].x < rects[i].xMin)
        rects[i].xMin = (*points)[k].x;
      else if ((*points)[k].x > rects[i].xMax)
        rects[i].xMax = (*points)[k].x;
      if ((*points)[k].y < rects[i].yMin)
        rects[i].yMin = (*points)[k].y;
      else if ((*points)[k].y > rects[i].yMax)
        rects[i].yMax = (*points)[k].y;
      }
    }

    // close subpath if necessary
    if (fillHack && ((*points)[*numPoints-1].x != (*points)[j].x ||
                 (*points)[*numPoints-1].y != (*points)[j].y)) {
      addPoint(points, size, numPoints, (*points)[j].x, (*points)[j].y);
    }

    // length of this subpath
    (*lengths)[i] = *numPoints - j;

    // leave an extra point for compound fill hack
    if (fillHack)
      addPoint(points, size, numPoints, 0, 0);
  }

  // kludge: munge any points that are *way* out of bounds - these can
  // crash certain (buggy) X servers
  for (i = 0; i < *numPoints; ++i) {
    if ((*points)[i].x < -pixmapW) {
      (*points)[i].x = -pixmapW;
    } else if ((*points)[i].x > 2 * pixmapW) {
      (*points)[i].x = 2 * pixmapW;
    }
    if ((*points)[i].y < -pixmapH) {
      (*points)[i].y = -pixmapH;
    } else if ((*points)[i].y > 2 * pixmapH) {
      (*points)[i].y = 2 * pixmapH;
    }
  }

  // combine compound polygons
  if (fillHack) {
    i = j = k = 0;
    while (i < n) {

      // start with subpath i
      rect = rects[i];
      (*lengths)[j] = (*lengths)[i];
      k0 = k;
      (*points)[k + (*lengths)[i]] = (*points)[k0];
      k += (*lengths)[i] + 1;
      ++i;

      // combine overlapping polygons
      do {

      // look for the first subsequent subpath, if any, which overlaps
      for (ii = i; ii < n; ++ii) {
        if (rects[ii].xMax > rects[i].xMin &&
            rects[ii].xMin < rects[i].xMax &&
            rects[ii].yMax > rects[i].yMin &&
            rects[ii].yMin < rects[i].yMax) {
          break;
        }
      }

      // if there is an overlap, combine the polygons
      if (ii < n) {
        for (; i <= ii; ++i) {
          if (rects[i].xMin < rect.xMin)
            rect.xMin = rects[j].xMin;
          if (rects[i].xMax > rect.xMax)
            rect.xMax = rects[j].xMax;
          if (rects[i].yMin < rect.yMin)
            rect.yMin = rects[j].yMin;
          if (rects[i].yMax > rect.yMax)
            rect.yMax = rects[j].yMax;
          (*lengths)[j] += (*lengths)[i] + 1;
          (*points)[k + (*lengths)[i]] = (*points)[k0];
          k += (*lengths)[i] + 1;
        }
      }
      } while (ii < n && i < n);

      ++j;
    }

    // free bounding rectangles
    if (rects != tmpRects)
      gfree(rects);

    n = j;
  }

  return n;
}

//
// Transform points in a single subpath and convert curves to line
// segments.
//
void XOutputDev::convertSubpath(GfxState *state, GfxSubpath *subpath,
                        XPoint **points, int *size, int *n) {
  double x0, y0, x1, y1, x2, y2, x3, y3;
  int m, i;

  m = subpath->getNumPoints();
  i = 0;
  while (i < m) {
    if (i >= 1 && subpath->getCurve(i)) {
      state->transform(subpath->getX(i-1), subpath->getY(i-1), &x0, &y0);
      state->transform(subpath->getX(i), subpath->getY(i), &x1, &y1);
      state->transform(subpath->getX(i+1), subpath->getY(i+1), &x2, &y2);
      state->transform(subpath->getX(i+2), subpath->getY(i+2), &x3, &y3);
      doCurve(points, size, n, x0, y0, x1, y1, x2, y2, x3, y3);
      i += 3;
    } else {
      state->transform(subpath->getX(i), subpath->getY(i), &x1, &y1);
      addPoint(points, size, n, xoutRound(x1), xoutRound(y1));
      ++i;
    }
  }
}

//
// Subdivide a Bezier curve.  This uses floating point to avoid
// propagating rounding errors.  (The curves look noticeably more
// jagged with integer arithmetic.)
//
void XOutputDev::doCurve(XPoint **points, int *size, int *n,
                   double x0, double y0, double x1, double y1,
                   double x2, double y2, double x3, double y3) {
  double x[(1<<maxCurveSplits)+1][3];
  double y[(1<<maxCurveSplits)+1][3];
  int next[1<<maxCurveSplits];
  int p1, p2, p3;
  double xx1, yy1, xx2, yy2;
  double dx, dy, mx, my, d1, d2;
  double xl0, yl0, xl1, yl1, xl2, yl2;
  double xr0, yr0, xr1, yr1, xr2, yr2, xr3, yr3;
  double xh, yh;
  double flat;

  flat = (double)(flatness * flatness);
  if (flat < 1)
    flat = 1;

  // initial segment
  p1 = 0;
  p2 = 1<<maxCurveSplits;
  x[p1][0] = x0;  y[p1][0] = y0;
  x[p1][1] = x1;  y[p1][1] = y1;
  x[p1][2] = x2;  y[p1][2] = y2;
  x[p2][0] = x3;  y[p2][0] = y3;
  next[p1] = p2;

  while (p1 < (1<<maxCurveSplits)) {

    // get next segment
    xl0 = x[p1][0];  yl0 = y[p1][0];
    xx1 = x[p1][1];  yy1 = y[p1][1];
    xx2 = x[p1][2];  yy2 = y[p1][2];
    p2 = next[p1];
    xr3 = x[p2][0];  yr3 = y[p2][0];

    // compute distances from control points to midpoint of the
    // straight line (this is a bit of a hack, but it's much faster
    // than computing the actual distances to the line)
    mx = (xl0 + xr3) * 0.5;
    my = (yl0 + yr3) * 0.5;
    dx = xx1 - mx;
    dy = yy1 - my;
    d1 = dx*dx + dy*dy;
    dx = xx2 - mx;
    dy = yy2 - my;
    d2 = dx*dx + dy*dy;

    // if curve is flat enough, or no more divisions allowed then
    // add the straight line segment
    if (p2 - p1 <= 1 || (d1 <= flat && d2 <= flat)) {
      addPoint(points, size, n, xoutRound(xr3), xoutRound(yr3));
      p1 = p2;

    // otherwise, subdivide the curve
    } else {
      xl1 = (xl0 + xx1) * 0.5;
      yl1 = (yl0 + yy1) * 0.5;
      xh = (xx1 + xx2) * 0.5;
      yh = (yy1 + yy2) * 0.5;
      xl2 = (xl1 + xh) * 0.5;
      yl2 = (yl1 + yh) * 0.5;
      xr2 = (xx2 + xr3) * 0.5;
      yr2 = (yy2 + yr3) * 0.5;
      xr1 = (xh + xr2) * 0.5;
      yr1 = (yh + yr2) * 0.5;
      xr0 = (xl2 + xr1) * 0.5;
      yr0 = (yl2 + yr1) * 0.5;

      // add the new subdivision points
      p3 = (p1 + p2) / 2;
      x[p1][1] = xl1;  y[p1][1] = yl1;
      x[p1][2] = xl2;  y[p1][2] = yl2;
      next[p1] = p3;
      x[p3][0] = xr0;  y[p3][0] = yr0;
      x[p3][1] = xr1;  y[p3][1] = yr1;
      x[p3][2] = xr2;  y[p3][2] = yr2;
      next[p3] = p2;
    }
  }
}

//
// Add a point to the points array.  (This would use a generic resizable
// array type if C++ supported parameterized types in some reasonable
// way -- templates are a disgusting kludge.)
//
void XOutputDev::addPoint(XPoint **points, int *size, int *k, int x, int y) {
  if (*k >= *size) {
    *size += 32;
    if (*points == tmpPoints) {
      *points = (XPoint *)gmalloc(*size * sizeof(XPoint));
      memcpy(*points, tmpPoints, *k * sizeof(XPoint));
    } else {
      *points = (XPoint *)grealloc(*points, *size * sizeof(XPoint));
    }
  }
  (*points)[*k].x = x;
  (*points)[*k].y = y;
  ++(*k);
}

void XOutputDev::beginString(GfxState *state, GString *s) {
  text->beginString(state, state->getCurX(), state->getCurY());
}

void XOutputDev::endString(GfxState *state) {
  text->endString();
}

void XOutputDev::drawChar(GfxState *state, double x, double y,
                    double dx, double dy,
                    double originX, double originY,
                    CharCode code, Unicode *u, int uLen) {
  int render;
  double x1, y1, dx1, dy1;
  GfxRGB rgb;
  double saveCurX, saveCurY;
  double *ctm;
  double saveCTM[6];

  text->addChar(state, x, y, dx, dy, u, uLen);

  if (!font) {
    return;
  }

  // check for invisible text -- this is used by Acrobat Capture
  render = state->getRender();
  if ((render & 3) == 3) {
    return;
  }

  x -= originX;
  y -= originY;
  state->transform(x, y, &x1, &y1);
  state->transformDelta(dx, dy, &dx1, &dy1);

  // fill
  if (!(render & 1)) {
    state->getFillRGB(&rgb);
    if (reverseVideo) {
      rgb.r = 1 - rgb.r;
      rgb.g = 1 - rgb.g;
      rgb.b = 1 - rgb.b;
    }
    font->drawChar(state, pixmap, pixmapW, pixmapH, fillGC, &rgb,
               x1, y1, dx1, dy1, code, u, uLen);
  }

  // stroke
  if ((render & 3) == 1 || (render & 3) == 2) {
    if (font->hasGetCharPath()) {
      saveCurX = state->getCurX();
      saveCurY = state->getCurY();
      ctm = state->getCTM();
      memcpy(saveCTM, ctm, 6 * sizeof(double));
      state->setCTM(1, 0, 0, 1, x1, y1);
      font->getCharPath(state, code, u, uLen);
      stroke(state);
      state->clearPath();
      state->setCTM(saveCTM[0], saveCTM[1], saveCTM[2], saveCTM[3],
                saveCTM[4], saveCTM[5]);
      state->moveTo(saveCurX, saveCurY);
    } else {
      // can't stroke the outline, so just fill it using the stroke
      // color
      state->getStrokeRGB(&rgb);
      if (reverseVideo) {
      rgb.r = 1 - rgb.r;
      rgb.g = 1 - rgb.g;
      rgb.b = 1 - rgb.b;
      }
      font->drawChar(state, pixmap, pixmapW, pixmapH, strokeGC, &rgb,
                 x1, y1, dx1, dy1, code, u, uLen);
    }
  }

#if 0 //~ unimplemented: clipping to char path
  // clip
  if (render & 4) {
  }
#endif
}

GBool XOutputDev::beginType3Char(GfxState *state,
                         CharCode code, Unicode *u, int uLen) {
  Ref *fontID;
  double *ctm, *bbox;
  GfxRGB color;
  T3FontCache *t3Font;
  T3GlyphStack *t3gs;
  double x1, y1, xMin, yMin, xMax, yMax, xt, yt;
  int i, j;

  if (!gfxFont) {
    return gFalse;
  }
  fontID = gfxFont->getID();
  ctm = state->getCTM();
  state->transform(0, 0, &xt, &yt);

  // is it the first (MRU) font in the cache?
  if (!(nT3Fonts > 0 &&
      t3FontCache[0]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3]))) {

    // is the font elsewhere in the cache?
    for (i = 1; i < nT3Fonts; ++i) {
      if (t3FontCache[i]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3])) {
      t3Font = t3FontCache[i];
      for (j = i; j > 0; --j) {
        t3FontCache[j] = t3FontCache[j - 1];
      }
      t3FontCache[0] = t3Font;
      break;
      }
    }
    if (i >= nT3Fonts) {

      // create new entry in the font cache
      if (nT3Fonts == xOutT3FontCacheSize) {
      delete t3FontCache[nT3Fonts - 1];
      --nT3Fonts;
      }
      for (j = nT3Fonts; j > 0; --j) {
      t3FontCache[j] = t3FontCache[j - 1];
      }
      ++nT3Fonts;
      bbox = gfxFont->getFontBBox();
      if (bbox[0] == 0 && bbox[1] == 0 && bbox[2] == 0 && bbox[3] == 0) {
      // broken bounding box -- just take a guess
      xMin = xt - 5;
      xMax = xMin + 30;
      yMax = yt + 15;
      yMin = yMax - 45;
      } else {
      state->transform(bbox[0], bbox[1], &x1, &y1);
      xMin = xMax = x1;
      yMin = yMax = y1;
      state->transform(bbox[0], bbox[3], &x1, &y1);
      if (x1 < xMin) {
        xMin = x1;
      } else if (x1 > xMax) {
        xMax = x1;
      }
      if (y1 < yMin) {
        yMin = y1;
      } else if (y1 > yMax) {
        yMax = y1;
      }
      state->transform(bbox[2], bbox[1], &x1, &y1);
      if (x1 < xMin) {
        xMin = x1;
      } else if (x1 > xMax) {
        xMax = x1;
      }
      if (y1 < yMin) {
        yMin = y1;
      } else if (y1 > yMax) {
        yMax = y1;
      }
      state->transform(bbox[2], bbox[3], &x1, &y1);
      if (x1 < xMin) {
        xMin = x1;
      } else if (x1 > xMax) {
        xMax = x1;
      }
      if (y1 < yMin) {
        yMin = y1;
      } else if (y1 > yMax) {
        yMax = y1;
      }
      }
      t3FontCache[0] = new T3FontCache(fontID, ctm[0], ctm[1], ctm[2], ctm[3],
                                     (int)floor(xMin - xt),
                               (int)floor(yMin - yt),
                               (int)ceil(xMax) - (int)floor(xMin) + 3,
                               (int)ceil(yMax) - (int)floor(yMin) + 3,
                               display, visual, depth, pixmap);
    }
  }
  t3Font = t3FontCache[0];

  // is the glyph in the cache?
  i = (code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc;
  for (j = 0; j < t3Font->cacheAssoc; ++j) {
    if ((t3Font->cacheTags[i+j].mru & 0x8000) &&
      t3Font->cacheTags[i+j].code == code) {
      state->getFillRGB(&color);
      if (reverseVideo) {
      color.r = 1 - color.r;
      color.g = 1 - color.g;
      color.b = 1 - color.b;
      }
      text->addChar(state, 0, 0,
                t3Font->cacheTags[i+j].wx, t3Font->cacheTags[i+j].wy,
                u, uLen);
      drawType3Glyph(t3Font, &t3Font->cacheTags[i+j],
                 t3Font->cacheData + (i+j) * t3Font->glyphSize,
                 xt, yt, &color);
      return gTrue;
    }
  }

  // push a new Type 3 glyph record
  t3gs = new T3GlyphStack();
  t3gs->next = t3GlyphStack;
  t3GlyphStack = t3gs;
  t3GlyphStack->cacheable = gFalse;
  t3GlyphStack->code = code;
  t3GlyphStack->cache = t3Font;
  t3GlyphStack->cacheIdx = i;
  t3GlyphStack->x = xt;
  t3GlyphStack->y = yt;
  t3GlyphStack->u = u;
  t3GlyphStack->uLen = uLen;

  return gFalse;
}

void XOutputDev::endType3Char(GfxState *state) {
  XImage *image;
  Guchar *p;
  int x, y;
  Gulong pixel;
  double alpha;
  T3GlyphStack *t3gs;
  double *ctm;

  if (t3GlyphStack->cacheable) {
    image = t3GlyphStack->cache->image;
    XGetSubImage(display, pixmap, 0, 0,
             t3GlyphStack->cache->glyphW, t3GlyphStack->cache->glyphH,
             (1 << depth) - 1, ZPixmap, image, 0, 0);
    p = t3GlyphStack->cacheData;
    for (y = 0; y < t3GlyphStack->cache->glyphH; ++y) {
      for (x = 0; x < t3GlyphStack->cache->glyphW; ++x) {
      pixel = XGetPixel(image, x, y);
      if (trueColor) {
        alpha = (double)((pixel >> rShift) & rMul) / (double)rMul;
      } else {
        alpha = redMap[pixel & 0xff];
      }
      if (alpha <= 0.2) {
        *p++ = 4;
      } else if (alpha <= 0.4) {
        *p++ = 3;
      } else if (alpha <= 0.6) {
        *p++ = 2;
      } else if (alpha <= 0.8) {
        *p++ = 1;
      } else {
        *p++ = 0;
      }
      }
    }
    XDestroyRegion(clipRegion);
    XFreeGC(display, strokeGC);
    XFreeGC(display, fillGC);
    pixmapW = t3GlyphStack->origPixmapW;
    pixmapH = t3GlyphStack->origPixmapH;
    pixmap = t3GlyphStack->origPixmap;
    strokeGC = t3GlyphStack->origStrokeGC;
    fillGC = t3GlyphStack->origFillGC;
    clipRegion = t3GlyphStack->origClipRegion;
    drawType3Glyph(t3GlyphStack->cache,
               t3GlyphStack->cacheTag, t3GlyphStack->cacheData,
               t3GlyphStack->x, t3GlyphStack->y, &t3GlyphStack->color);
    // the CTM must be restored here in order for TextPage::addChar to
    // work correctly
    ctm = state->getCTM();
    state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3],
              t3GlyphStack->origCTM4, t3GlyphStack->origCTM5);
  }
  text->addChar(state, 0, 0, t3GlyphStack->wx, t3GlyphStack->wy,
            t3GlyphStack->u, t3GlyphStack->uLen);
  t3gs = t3GlyphStack;
  t3GlyphStack = t3gs->next;
  delete t3gs;
}

void XOutputDev::drawType3Glyph(T3FontCache *t3Font,
                        T3FontCacheTag *tag, Guchar *data,
                        double x, double y, GfxRGB *color) {
  XImage *image;
  XColor xcolor;
  GfxRGB bg, rgb;
  Gulong map[5];
  Gulong pixel;
  Guchar *p;
  int x0, y0, w0, h0, x1, y1;
  int ix, iy;

  // compute: (x0,y0) = position in destination pixmap
  //          (x1,y1) = position in the XImage
  //          (w0,h0) = size of XImage transfer
  x0 = xoutRound(x + t3Font->glyphX);
  y0 = xoutRound(y + t3Font->glyphY);
  x1 = 0;
  y1 = 0;
  w0 = t3Font->glyphW;
  h0 = t3Font->glyphH;
  if (x0 < 0) {
    x1 = -x0;
    w0 += x0;
    x0 = 0;
  }
  if (x0 + w0 > pixmapW) {
    w0 = pixmapW - x0;
  }
  if (w0 <= 0) {
    return;
  }
  if (y0 < 0) {
    y1 = -y0;
    h0 += y0;
    y0 = 0;
  }
  if (y0 + h0 > pixmapH) {
    h0 = pixmapH - y0;
  }
  if (h0 <= 0) {
    return;
  }

  image = t3Font->image;
  XGetSubImage(display, pixmap, x0, y0, w0, h0,
             (1 << depth) - 1, ZPixmap, image, x1, y1);
  xcolor.pixel = XGetPixel(image, t3Font->glyphW / 2, t3Font->glyphH / 2);
  XQueryColor(display, colormap, &xcolor);
  bg.r = xcolor.red / 65535.0;
  bg.g = xcolor.green / 65535.0;
  bg.b = xcolor.blue / 65535.0;
  rgb.r = 0.25 * (color->r + 3 * bg.r);
  rgb.g = 0.25 * (color->g + 3 * bg.g);
  rgb.b = 0.25 * (color->b + 3 * bg.b);
  map[1] = findColor(&rgb);
  rgb.r = 0.5 * (color->r + bg.r);
  rgb.g = 0.5 * (color->g + bg.g);
  rgb.b = 0.5 * (color->b + bg.b);
  map[2] = findColor(&rgb);
  rgb.r = 0.25 * (3 * color->r + bg.r);
  rgb.g = 0.25 * (3 * color->g + bg.g);
  rgb.b = 0.25 * (3 * color->b + bg.b);
  map[3] = findColor(&rgb);
  map[4] = findColor(color);
  p = data;
  for (iy = 0; iy < t3Font->glyphH; ++iy) {
    for (ix = 0; ix < t3Font->glyphW; ++ix) {
      pixel = *p++;
      if (pixel > 0) {
      XPutPixel(image, ix, iy, map[pixel]);
      }
    }
  }
  XPutImage(display, pixmap, fillGC, image, x1, y1, x0, y0, w0, h0);
}

void XOutputDev::type3D0(GfxState *state, double wx, double wy) {
  t3GlyphStack->wx = wx;
  t3GlyphStack->wy = wy;
}

void XOutputDev::type3D1(GfxState *state, double wx, double wy,
                   double llx, double lly, double urx, double ury) {
  GfxColor fgColor;
  XGCValues gcValues;
  XRectangle rect;
  double *ctm;
  T3FontCache *t3Font;
  int i, j;

  // allocate a cache entry
  t3GlyphStack->cacheable = gTrue;
  t3Font = t3GlyphStack->cache;
  i = t3GlyphStack->cacheIdx;
  for (j = 0; j < t3Font->cacheAssoc; ++j) {
    if ((t3Font->cacheTags[i+j].mru & 0x7fff) == t3Font->cacheAssoc - 1) {
      t3Font->cacheTags[i+j].mru = 0x8000;
      t3Font->cacheTags[i+j].code = t3GlyphStack->code;
      t3GlyphStack->cacheTag = &t3Font->cacheTags[i+j];
      t3GlyphStack->cacheData = t3Font->cacheData + (i+j) * t3Font->glyphSize;
    } else {
      ++t3Font->cacheTags[i+j].mru;
    }
  }
  t3GlyphStack->wx = wx;
  t3GlyphStack->wy = wy;
  t3GlyphStack->cacheTag->wx = wx;
  t3GlyphStack->cacheTag->wy = wy;

  // prepare to rasterize the glyph
  //~ do we need to handle both fill and stroke color?
  state->getFillRGB(&t3GlyphStack->color);
  if (reverseVideo) {
    t3GlyphStack->color.r = 1 - t3GlyphStack->color.r;
    t3GlyphStack->color.g = 1 - t3GlyphStack->color.g;
    t3GlyphStack->color.b = 1 - t3GlyphStack->color.b;
  }
  fgColor.c[0] = reverseVideo ? 1 : 0;
  state->setFillColorSpace(new GfxDeviceGrayColorSpace());
  state->setFillColor(&fgColor);
  state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
  state->setStrokeColor(&fgColor);
  t3GlyphStack->origPixmapW = pixmapW;
  t3GlyphStack->origPixmapH = pixmapH;
  t3GlyphStack->origPixmap = pixmap;
  t3GlyphStack->origStrokeGC = strokeGC;
  t3GlyphStack->origFillGC = fillGC;
  t3GlyphStack->origClipRegion = clipRegion;
  pixmapW = t3GlyphStack->cache->glyphW;
  pixmapH = t3GlyphStack->cache->glyphH;
  pixmap = t3GlyphStack->cache->pixmap;
  gcValues.foreground = BlackPixel(display, screenNum);
  gcValues.background = WhitePixel(display, screenNum);
  gcValues.line_width = 0;
  gcValues.line_style = LineSolid;
  strokeGC = XCreateGC(display, pixmap,
                   GCForeground | GCBackground | GCLineWidth | GCLineStyle,
                   &gcValues);
  updateLineAttrs(state, gTrue);
  gcValues.foreground = WhitePixel(display, screenNum);
  fillGC = XCreateGC(display, pixmap,
                 GCForeground | GCBackground | GCLineWidth | GCLineStyle,
                 &gcValues);
  XFillRectangle(display, pixmap, fillGC, 0, 0, pixmapW, pixmapH);
  XSetForeground(display, fillGC, BlackPixel(display, screenNum));
  clipRegion = XCreateRegion();
  rect.x = rect.y = 0;
  rect.width = pixmapW;
  rect.height = pixmapH;
  XUnionRectWithRegion(&rect, clipRegion, clipRegion);
  XSetRegion(display, strokeGC, clipRegion);
  XSetRegion(display, fillGC, clipRegion);
  ctm = state->getCTM();
  t3GlyphStack->origCTM4 = ctm[4];
  t3GlyphStack->origCTM5 = ctm[5];
  state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3], 
            -t3GlyphStack->cache->glyphX, -t3GlyphStack->cache->glyphY);
}

inline Gulong XOutputDev::findColor(GfxRGB *x, GfxRGB *err) {
  double gray;
  int r, g, b;
  Gulong pixel;

  if (trueColor) {
    r = xoutRound(x->r * rMul);
    g = xoutRound(x->g * gMul);
    b = xoutRound(x->b * bMul);
    pixel = ((Gulong)r << rShift) +
            ((Gulong)g << gShift) +
            ((Gulong)b << bShift);
    err->r = x->r - (double)r / rMul;
    err->g = x->g - (double)g / gMul;
    err->b = x->b - (double)b / bMul;
  } else if (numColors == 1) {
    gray = 0.299 * x->r + 0.587 * x->g + 0.114 * x->b;
    if (gray < 0.5) {
      pixel = colors[0];
      err->r = x->r;
      err->g = x->g;
      err->b = x->b;
    } else {
      pixel = colors[1];
      err->r = x->r - 1;
      err->g = x->g - 1;
      err->b = x->b - 1;
    }
  } else {
    r = xoutRound(x->r * (numColors - 1));
    g = xoutRound(x->g * (numColors - 1));
    b = xoutRound(x->b * (numColors - 1));
    pixel = colors[(r * numColors + g) * numColors + b];
    err->r = x->r - (double)r / (numColors - 1);
    err->g = x->g - (double)g / (numColors - 1); 
    err->b = x->b - (double)b / (numColors - 1);
  }
  return pixel;
}

Gulong XOutputDev::findColor(GfxRGB *rgb) {
  int r, g, b;
  double gray;
  Gulong pixel;

  if (trueColor) {
    r = xoutRound(rgb->r * rMul);
    g = xoutRound(rgb->g * gMul);
    b = xoutRound(rgb->b * bMul);
    pixel = ((Gulong)r << rShift) +
            ((Gulong)g << gShift) +
            ((Gulong)b << bShift);
  } else if (numColors == 1) {
    gray = 0.299 * rgb->r + 0.587 * rgb->g + 0.114 * rgb->b;
    if (gray < 0.5)
      pixel = colors[0];
    else
      pixel = colors[1];
  } else {
    r = xoutRound(rgb->r * (numColors - 1));
    g = xoutRound(rgb->g * (numColors - 1));
    b = xoutRound(rgb->b * (numColors - 1));
#if 0 // this makes things worse as often as better
    // even a very light color shouldn't map to white
    if (r == numColors - 1 && g == numColors - 1 && b == numColors - 1) {
      if (color->getR() < 0.95)
      --r;
      if (color->getG() < 0.95)
      --g;
      if (color->getB() < 0.95)
      --b;
    }
#endif
    pixel = colors[(r * numColors + g) * numColors + b];
  }
  return pixel;
}

void XOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
                         int width, int height, GBool invert,
                         GBool inlineImg) {
  ImageStream *imgStr;
  XImage *image;
  double *ctm;
  GBool rot;
  double xScale, yScale, xShear, yShear;
  int tx, ty, scaledWidth, scaledHeight, xSign, ySign;
  int ulx, uly, llx, lly, urx, ury, lrx, lry;
  int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1;
  int bx0, by0, bx1, by1, bw, bh;
  int cx0, cy0, cx1, cy1, cw, ch;
  int yp, yq, yt, yStep, lastYStep;
  int xp, xq, xt, xStep, xSrc;
  GfxRGB rgb;
  Guchar *pixBuf;
  int imgPix;
  double alpha;
  XColor xcolor;
  Gulong lastPixel;
  GfxRGB rgb2;
  double r0, g0, b0, r1, g1, b1;
  Gulong pix;
  Guchar *p;
  int x, y, x1, y1, x2, y2;
  int n, m, i, j;

  // get CTM, check for singular matrix
  ctm = state->getCTM();
  if (fabs(ctm[0] * ctm[3] - ctm[1] * ctm[2]) < 0.000001) {
    error(-1, "Singular CTM in drawImage");
    if (inlineImg) {
      j = height * ((width + 7) / 8);
      str->reset();
      for (i = 0; i < j; ++i) {
      str->getChar();
      }
      str->close();
    }
    return;
  }

  // compute scale, shear, rotation, translation parameters
  rot = fabs(ctm[1]) > fabs(ctm[0]);
  if (rot) {
    xScale = -ctm[1];
    yScale = -ctm[2] + (ctm[0] * ctm[3]) / ctm[1];
    xShear = ctm[3] / yScale;
    yShear = -ctm[0] / ctm[1];
  } else {
    xScale = ctm[0];
    yScale = -ctm[3] + (ctm[1] * ctm[2]) / ctm[0];
    xShear = -ctm[2] / yScale;
    yShear = ctm[1] / ctm[0];
  }
  tx = xoutRound(ctm[2] + ctm[4]);
  ty = xoutRound(ctm[3] + ctm[5]);
  // use ceil() to avoid gaps between "striped" images
  scaledWidth = (int)ceil(fabs(xScale));
  xSign = (xScale < 0) ? -1 : 1;
  scaledHeight = (int)ceil(fabs(yScale));
  ySign = (yScale < 0) ? -1 : 1;

  // compute corners in device space
  ulx1 = 0;
  uly1 = 0;
  urx1 = xSign * (scaledWidth - 1);
  ury1 = xoutRound(yShear * urx1);
  llx1 = xoutRound(xShear * ySign * (scaledHeight - 1));
  lly1 = ySign * (scaledHeight - 1) + xoutRound(yShear * llx1);
  lrx1 = xSign * (scaledWidth - 1) +
           xoutRound(xShear * ySign * (scaledHeight - 1));
  lry1 = ySign * (scaledHeight - 1) + xoutRound(yShear * lrx1);
  if (rot) {
    ulx = tx + uly1;    uly = ty - ulx1;
    urx = tx + ury1;    ury = ty - urx1;
    llx = tx + lly1;    lly = ty - llx1;
    lrx = tx + lry1;    lry = ty - lrx1;
  } else {
    ulx = tx + ulx1;    uly = ty + uly1;
    urx = tx + urx1;    ury = ty + ury1;
    llx = tx + llx1;    lly = ty + lly1;
    lrx = tx + lrx1;    lry = ty + lry1;
  }

  // bounding box:
  //   (bx0, by0) = upper-left corner
  //   (bx1, by1) = lower-right corner
  //   (bw, bh) = size
  bx0 = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx
                                  : (llx < lrx) ? llx : lrx
                : (urx < llx) ? (urx < lrx) ? urx : lrx
                                  : (llx < lrx) ? llx : lrx;
  bx1 = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx
                                  : (llx > lrx) ? llx : lrx
                : (urx > llx) ? (urx > lrx) ? urx : lrx
                                  : (llx > lrx) ? llx : lrx;
  by0 = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry
                                  : (lly < lry) ? lly : lry
                : (ury < lly) ? (ury < lry) ? ury : lry
                                  : (lly < lry) ? lly : lry;
  by1 = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry
                                  : (lly > lry) ? lly : lry
                : (ury > lly) ? (ury > lry) ? ury : lry
                                  : (lly > lry) ? lly : lry;
  bw = bx1 - bx0 + 1;
  bh = by1 - by0 + 1;

  // Bounding box clipped to pixmap, i.e., "valid" rectangle:
  //   (cx0, cy0) = upper-left corner of valid rectangle in Pixmap
  //   (cx1, cy1) = upper-left corner of valid rectangle in XImage
  //   (cw, ch) = size of valid rectangle
  // These values will be used to transfer the XImage from/to the
  // Pixmap.
  cw = (bx1 >= pixmapW) ? pixmapW - bx0 : bw;
  if (bx0 < 0) {
    cx0 = 0;
    cx1 = -bx0;
    cw += bx0;
  } else {
    cx0 = bx0;
    cx1 = 0;
  }
  ch = (by1 >= pixmapH) ? pixmapH - by0 : bh;
  if (by0 < 0) {
    cy0 = 0;
    cy1 = -by0;
    ch += by0;
  } else {
    cy0 = by0;
    cy1 = 0;
  }

  // check for tiny (zero width or height) images
  // and off-page images
  if (scaledWidth <= 0 || scaledHeight <= 0 || cw <= 0 || ch <= 0) {
    if (inlineImg) {
      j = height * ((width + 7) / 8);
      str->reset();
      for (i = 0; i < j; ++i) {
      str->getChar();
      }
      str->close();
    }
    return;
  }

  // compute Bresenham parameters for x and y scaling
  yp = height / scaledHeight;
  yq = height % scaledHeight;
  xp = width / scaledWidth;
  xq = width % scaledWidth;

  // allocate pixel buffer
  pixBuf = (Guchar *)gmalloc((yp + 1) * width * sizeof(Guchar));

  // allocate XImage and read from page pixmap
  image = XCreateImage(display, visual, depth, ZPixmap, 0, NULL, bw, bh, 8, 0);
  image->data = (char *)gmalloc(bh * image->bytes_per_line);
  XGetSubImage(display, pixmap, cx0, cy0, cw, ch, (1 << depth) - 1, ZPixmap,
             image, cx1, cy1);

  // get mask color
  state->getFillRGB(&rgb);
  if (reverseVideo) {
    rgb.r = 1 - rgb.r;
    rgb.g = 1 - rgb.g;
    rgb.b = 1 - rgb.b;
  }
  r0 = rgb.r;
  g0 = rgb.g;
  b0 = rgb.b;

  // initialize background color
  // (the specific pixel value doesn't matter here, as long as
  // r1,g1,b1 correspond correctly to lastPixel)
  xcolor.pixel = lastPixel = 0;
  XQueryColor(display, colormap, &xcolor);
  r1 = (double)xcolor.red / 65535.0;
  g1 = (double)xcolor.green / 65535.0;
  b1 = (double)xcolor.blue / 65535.0;

  // initialize the image stream
  imgStr = new ImageStream(str, width, 1, 1);
  imgStr->reset();

  // init y scale Bresenham
  yt = 0;
  lastYStep = 1;

  for (y = 0; y < scaledHeight; ++y) {

    // y scale Bresenham
    yStep = yp;
    yt += yq;
    if (yt >= scaledHeight) {
      yt -= scaledHeight;
      ++yStep;
    }

    // read row(s) from image
    n = (yp > 0) ? yStep : lastYStep;
    if (n > 0) {
      p = pixBuf;
      for (i = 0; i < n; ++i) {
      memcpy(p, imgStr->getLine(), width);
      if (invert) {
        for (j = 0; j < width; ++j) {
          p[j] ^= 1;
        }
      }
      p += width;
      }
    }
    lastYStep = yStep;

    // init x scale Bresenham
    xt = 0;
    xSrc = 0;

    for (x = 0; x < scaledWidth; ++x) {

      // x scale Bresenham
      xStep = xp;
      xt += xq;
      if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
      }

      // x shear
      x1 = xSign * x + xoutRound(xShear * ySign * y);

      // y shear
      y1 = ySign * y + xoutRound(yShear * x1);

      // rotation
      if (rot) {
      x2 = y1;
      y2 = -x1;
      } else {
      x2 = x1;
      y2 = y1;
      }

      // compute the filtered pixel at (x,y) after the
      // x and y scaling operations
      n = yStep > 0 ? yStep : 1;
      m = xStep > 0 ? xStep : 1;
      p = pixBuf + xSrc;
      imgPix = 0;
      for (i = 0; i < n; ++i) {
      for (j = 0; j < m; ++j) {
        imgPix += *p++;
      }
      p += width - m;
      }

      // x scale Bresenham
      xSrc += xStep;

      // blend image pixel with background
      alpha = (double)imgPix / (double)(n * m);
      xcolor.pixel = XGetPixel(image, tx + x2 - bx0, ty + y2 - by0);
      if (xcolor.pixel != lastPixel) {
      XQueryColor(display, colormap, &xcolor);
      r1 = (double)xcolor.red / 65535.0;
      g1 = (double)xcolor.green / 65535.0;
      b1 = (double)xcolor.blue / 65535.0;
      lastPixel = xcolor.pixel;
      }
      rgb2.r = r0 * (1 - alpha) + r1 * alpha;
      rgb2.g = g0 * (1 - alpha) + g1 * alpha;
      rgb2.b = b0 * (1 - alpha) + b1 * alpha;
      pix = findColor(&rgb2);

      // set pixel
      XPutPixel(image, tx + x2 - bx0, ty + y2 - by0, pix);
    }
  }

  // blit the image into the pixmap
  XPutImage(display, pixmap, fillGC, image, cx1, cy1, cx0, cy0, cw, ch);

  // free memory
  delete imgStr;
  gfree(pixBuf);
  gfree(image->data);
  image->data = NULL;
  XDestroyImage(image);
}

void XOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
                     int width, int height, GfxImageColorMap *colorMap,
                     int *maskColors, GBool inlineImg) {
  ImageStream *imgStr;
  XImage *image;
  int nComps, nVals, nBits;
  GBool dither;
  double *ctm;
  GBool rot;
  double xScale, yScale, xShear, yShear;
  int tx, ty, scaledWidth, scaledHeight, xSign, ySign;
  int ulx, uly, llx, lly, urx, ury, lrx, lry;
  int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1;
  int bx0, by0, bx1, by1, bw, bh;
  int cx0, cy0, cx1, cy1, cw, ch;
  int yp, yq, yt, yStep, lastYStep;
  int xp, xq, xt, xStep, xSrc;
  GfxRGB *pixBuf;
  Guchar *alphaBuf;
  Guchar pixBuf2[gfxColorMaxComps];
  GfxRGB color2, err, errRight;
  GfxRGB *errDown;
  double r0, g0, b0, alpha, mul;
  Gulong pix;
  GfxRGB *p;
  Guchar *q, *p2;
  GBool oneBitMode;
  GfxRGB oneBitRGB[2];
  int x, y, x1, y1, x2, y2;
  int n, m, i, j, k;

  // image parameters
  nComps = colorMap->getNumPixelComps();
  nVals = width * nComps;
  nBits = colorMap->getBits();
  dither = nComps > 1 || nBits > 1;

  // get CTM, check for singular matrix
  ctm = state->getCTM();
  if (fabs(ctm[0] * ctm[3] - ctm[1] * ctm[2]) < 0.000001) {
    error(-1, "Singular CTM in drawImage");
    if (inlineImg) {
      str->reset();
      j = height * ((nVals * nBits + 7) / 8);
      for (i = 0; i < j; ++i) {
      str->getChar();
      }
      str->close();
    }
    return;
  }

  // compute scale, shear, rotation, translation parameters
  rot = fabs(ctm[1]) > fabs(ctm[0]);
  if (rot) {
    xScale = -ctm[1];
    yScale = -ctm[2] + (ctm[0] * ctm[3]) / ctm[1];
    xShear = ctm[3] / yScale;
    yShear = -ctm[0] / ctm[1];
  } else {
    xScale = ctm[0];
    yScale = -ctm[3] + (ctm[1] * ctm[2]) / ctm[0];
    xShear = -ctm[2] / yScale;
    yShear = ctm[1] / ctm[0];
  }
  tx = xoutRound(ctm[2] + ctm[4]);
  ty = xoutRound(ctm[3] + ctm[5]);
  if (xScale < 0) {
    // this is the right edge which needs to be (left + width - 1)
    --tx;
  }
  if (yScale < 0) {
    // this is the bottom edge which needs to be (top + height - 1)
    --ty;
  }
  // use ceil() to avoid gaps between "striped" images
  scaledWidth = (int)ceil(fabs(xScale));
  xSign = (xScale < 0) ? -1 : 1;
  scaledHeight = (int)ceil(fabs(yScale));
  ySign = (yScale < 0) ? -1 : 1;

  // compute corners in device space
  ulx1 = 0;
  uly1 = 0;
  urx1 = xSign * (scaledWidth - 1);
  ury1 = xoutRound(yShear * urx1);
  llx1 = xoutRound(xShear * ySign * (scaledHeight - 1));
  lly1 = ySign * (scaledHeight - 1) + xoutRound(yShear * llx1);
  lrx1 = xSign * (scaledWidth - 1) +
           xoutRound(xShear * ySign * (scaledHeight - 1));
  lry1 = ySign * (scaledHeight - 1) + xoutRound(yShear * lrx1);
  if (rot) {
    ulx = tx + uly1;    uly = ty - ulx1;
    urx = tx + ury1;    ury = ty - urx1;
    llx = tx + lly1;    lly = ty - llx1;
    lrx = tx + lry1;    lry = ty - lrx1;
  } else {
    ulx = tx + ulx1;    uly = ty + uly1;
    urx = tx + urx1;    ury = ty + ury1;
    llx = tx + llx1;    lly = ty + lly1;
    lrx = tx + lrx1;    lry = ty + lry1;
  }

  // bounding box:
  //   (bx0, by0) = upper-left corner
  //   (bx1, by1) = lower-right corner
  //   (bw, bh) = size
  bx0 = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx
                                  : (llx < lrx) ? llx : lrx
                : (urx < llx) ? (urx < lrx) ? urx : lrx
                                  : (llx < lrx) ? llx : lrx;
  bx1 = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx
                                  : (llx > lrx) ? llx : lrx
                : (urx > llx) ? (urx > lrx) ? urx : lrx
                                  : (llx > lrx) ? llx : lrx;
  by0 = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry
                                  : (lly < lry) ? lly : lry
                : (ury < lly) ? (ury < lry) ? ury : lry
                                  : (lly < lry) ? lly : lry;
  by1 = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry
                                  : (lly > lry) ? lly : lry
                : (ury > lly) ? (ury > lry) ? ury : lry
                                  : (lly > lry) ? lly : lry;
  bw = bx1 - bx0 + 1;
  bh = by1 - by0 + 1;

  // Bounding box clipped to pixmap, i.e., "valid" rectangle:
  //   (cx0, cy0) = upper-left corner of valid rectangle in Pixmap
  //   (cx1, cy1) = upper-left corner of valid rectangle in XImage
  //   (cw, ch) = size of valid rectangle
  // These values will be used to transfer the XImage from/to the
  // Pixmap.
  cw = (bx1 >= pixmapW) ? pixmapW - bx0 : bw;
  if (bx0 < 0) {
    cx0 = 0;
    cx1 = -bx0;
    cw += bx0;
  } else {
    cx0 = bx0;
    cx1 = 0;
  }
  ch = (by1 >= pixmapH) ? pixmapH - by0 : bh;
  if (by0 < 0) {
    cy0 = 0;
    cy1 = -by0;
    ch += by0;
  } else {
    cy0 = by0;
    cy1 = 0;
  }

  // check for tiny (zero width or height) images
  // and off-page images
  if (scaledWidth <= 0 || scaledHeight <= 0 || cw <= 0 || ch <= 0) {
    if (inlineImg) {
      str->reset();
      j = height * ((nVals * nBits + 7) / 8);
      for (i = 0; i < j; ++i)
      str->getChar();
      str->close();
    }
    return;
  }

  // compute Bresenham parameters for x and y scaling
  yp = height / scaledHeight;
  yq = height % scaledHeight;
  xp = width / scaledWidth;
  xq = width % scaledWidth;

  // allocate pixel buffer
  pixBuf = (GfxRGB *)gmalloc((yp + 1) * width * sizeof(GfxRGB));
  if (maskColors) {
    alphaBuf = (Guchar *)gmalloc((yp + 1) * width * sizeof(Guchar));
  } else {
    alphaBuf = NULL;
  }

  // allocate XImage
  image = XCreateImage(display, visual, depth, ZPixmap, 0, NULL, bw, bh, 8, 0);
  image->data = (char *)gmalloc(bh * image->bytes_per_line);

  // if the transform is anything other than a 0/90/180/270 degree
  // rotation/flip, or if there is color key masking, read the
  // backgound pixmap to fill in the corners
  if (!((ulx == llx && uly == ury) ||
      (uly == lly && ulx == urx)) ||
      maskColors) {
    XGetSubImage(display, pixmap, cx0, cy0, cw, ch, (1 << depth) - 1, ZPixmap,
             image, cx1, cy1);
  }

  // allocate error diffusion accumulators
  if (dither) {
    errDown = (GfxRGB *)gmalloc(bw * sizeof(GfxRGB));
    for (j = 0; j < bw; ++j) {
      errDown[j].r = errDown[j].g = errDown[j].b = 0;
    }
  } else {
    errDown = NULL;
  }

  // optimize the one-bit-deep image case
  if ((oneBitMode = nComps == 1 && nBits == 1)) {
    pixBuf2[0] = 0;
    colorMap->getRGB(pixBuf2, &oneBitRGB[0]);
    pixBuf2[0] = 1;
    colorMap->getRGB(pixBuf2, &oneBitRGB[1]);
  }

  // initialize the image stream
  imgStr = new ImageStream(str, width, nComps, nBits);
  imgStr->reset();

  // init y scale Bresenham
  yt = 0;
  lastYStep = 1;

  for (y = 0; y < scaledHeight; ++y) {

    // initialize error diffusion accumulator
    errRight.r = errRight.g = errRight.b = 0;

    // y scale Bresenham
    yStep = yp;
    yt += yq;
    if (yt >= scaledHeight) {
      yt -= scaledHeight;
      ++yStep;
    }

    // read row(s) from image
    n = (yp > 0) ? yStep : lastYStep;
    if (n > 0) {
      p = pixBuf;
      q = alphaBuf;
      for (i = 0; i < n; ++i) {
        p2 = imgStr->getLine();
        for (j = 0; j < width; ++j) {
          if (oneBitMode) {
            *p = oneBitRGB[*p2];
          } else {
            colorMap->getRGB(p2, p);
          }
          ++p;
          if (q) {
            *q = 1;
            for (k = 0; k < nComps; ++k) {
              if (p2[k] < maskColors[2*k] ||
                  p2[k] > maskColors[2*k]) {
                *q = 0;
                break;
              }
            }
            ++q;
          }
          p2 += nComps;
        }
      }
    }
    lastYStep = yStep;

    // init x scale Bresenham
    xt = 0;
    xSrc = 0;

    for (x = 0; x < scaledWidth; ++x) {

      // x scale Bresenham
      xStep = xp;
      xt += xq;
      if (xt >= scaledWidth) {
      xt -= scaledWidth;
      ++xStep;
      }

      // x shear
      x1 = xSign * x + xoutRound(xShear * ySign * y);

      // y shear
      y1 = ySign * y + xoutRound(yShear * x1);

      // rotation
      if (rot) {
      x2 = y1;
      y2 = -x1;
      } else {
      x2 = x1;
      y2 = y1;
      }

      // compute the filtered pixel at (x,y) after the
      // x and y scaling operations
      n = yStep > 0 ? yStep : 1;
      m = xStep > 0 ? xStep : 1;
      p = pixBuf + xSrc;
      r0 = g0 = b0 = 0;
      q = alphaBuf ? alphaBuf + xSrc : (Guchar *)NULL;
      alpha = 0;
      for (i = 0; i < n; ++i) {
      for (j = 0; j < m; ++j) {
        r0 += p->r;
        g0 += p->g;
        b0 += p->b;
        ++p;
        if (q) {
          alpha += *q++;
        }
      }
      p += width - m;
      }
      mul = 1 / (double)(n * m);
      r0 *= mul;
      g0 *= mul;
      b0 *= mul;
      alpha *= mul;

      // x scale Bresenham
      xSrc += xStep;

      // compute pixel
      if (dither) {
      color2.r = r0 + errRight.r + errDown[tx + x2 - bx0].r;
      if (color2.r > 1) {
        color2.r = 1;
      } else if (color2.r < 0) {
        color2.r = 0;
      }
      color2.g = g0 + errRight.g + errDown[tx + x2 - bx0].g;
      if (color2.g > 1) {
        color2.g = 1;
      } else if (color2.g < 0) {
        color2.g = 0;
      }
      color2.b = b0 + errRight.b + errDown[tx + x2 - bx0].b;
      if (color2.b > 1) {
        color2.b = 1;
      } else if (color2.b < 0) {
        color2.b = 0;
      }
      pix = findColor(&color2, &err);
      errRight.r = errDown[tx + x2 - bx0].r = err.r / 2;
      errRight.g = errDown[tx + x2 - bx0].g = err.g / 2;
      errRight.b = errDown[tx + x2 - bx0].b = err.b / 2;
      } else {
      color2.r = r0;
      color2.g = g0;
      color2.b = b0;
      pix = findColor(&color2, &err);
      }

      // set pixel
      //~ this should do a blend when 0 < alpha < 1
      if (alpha < 0.75) {
      XPutPixel(image, tx + x2 - bx0, ty + y2 - by0, pix);
      }
    }
  }

  // blit the image into the pixmap
  XPutImage(display, pixmap, fillGC, image, cx1, cy1, cx0, cy0, cw, ch);

  // free memory
  delete imgStr;
  gfree(pixBuf);
  if (maskColors) {
    gfree(alphaBuf);
  }
  gfree(image->data);
  image->data = NULL;
  XDestroyImage(image);
  gfree(errDown);
}

GBool XOutputDev::findText(Unicode *s, int len, GBool top, GBool bottom,
                     int *xMin, int *yMin, int *xMax, int *yMax) {
  double xMin1, yMin1, xMax1, yMax1;
  
  xMin1 = (double)*xMin;
  yMin1 = (double)*yMin;
  xMax1 = (double)*xMax;
  yMax1 = (double)*yMax;
  if (text->findText(s, len, top, bottom, &xMin1, &yMin1, &xMax1, &yMax1)) {
    *xMin = xoutRound(xMin1);
    *xMax = xoutRound(xMax1);
    *yMin = xoutRound(yMin1);
    *yMax = xoutRound(yMax1);
    return gTrue;
  }
  return gFalse;
}

GString *XOutputDev::getText(int xMin, int yMin, int xMax, int yMax) {
  return text->getText((double)xMin, (double)yMin,
                   (double)xMax, (double)yMax);
}

Generated by  Doxygen 1.6.0   Back to index