//========================================================================
//
// GfxState.cc
//
// Copyright 1996 Derek B. Noonburg
//
//========================================================================

#ifdef __GNUC__
#pragma implementation
#endif

#include <stddef.h>
#include <math.h>
#include <string.h> // for memcpy()
#include "gmem.h"
#include "Error.h"
#include "Object.h"
#include "GfxState.h"

//------------------------------------------------------------------------
// GfxColor
//------------------------------------------------------------------------

void GfxColor::setCMYK(double c, double m, double y, double k) {
  if ((r = 1 - (c + k)) < 0)
    r = 0;
  if ((g = 1 - (m + k)) < 0)
    g = 0;
  if ((b = 1 - (y + k)) < 0)
    b = 0;
}

//------------------------------------------------------------------------
// GfxColorSpace
//------------------------------------------------------------------------

GfxColorSpace::GfxColorSpace(Object *colorSpace) {
  Object csObj;
  Object obj, obj2;
  char *s;
  int x;
  int i, j;

  ok = gTrue;
  lookup = NULL;

  // check for Separation colorspace
  colorSpace->copy(&csObj);
  sepFunc = NULL;
  if (colorSpace->isArray()) {
    colorSpace->arrayGet(0, &obj);
    if (obj.isName("Separation")) {
      csObj.free();
      colorSpace->arrayGet(2, &csObj);
      sepFunc = new Function(colorSpace->arrayGet(3, &obj2));
      obj2.free();
      if (!sepFunc->isOk()) {
	delete sepFunc;
	sepFunc = NULL;
      }
    }
    obj.free();
  }

  // get mode
  indexed = gFalse;
  if (csObj.isName()) {
    setMode(&csObj);
  } else if (csObj.isArray()) {
    csObj.arrayGet(0, &obj);
    if (obj.isName("Indexed") || obj.isName("I")) {
      indexed = gTrue;
      setMode(csObj.arrayGet(1, &obj2));
      obj2.free();
    } else {
      setMode(&csObj);
    }
    obj.free();
  } else {
    goto err1;
  }
  if (!ok)
    return;

  // get lookup table for indexed colorspace
  if (indexed) {
    csObj.arrayGet(2, &obj);
    if (!obj.isInt())
      goto err2;
    indexHigh = obj.getInt();
    obj.free();
    lookup = (Guchar (*)[4])gmalloc((indexHigh + 1) * 4 * sizeof(Guchar));
    csObj.arrayGet(3, &obj);
    if (obj.isStream()) {
      obj.streamReset();
      for (i = 0; i <= indexHigh; ++i) {
	for (j = 0; j < numComps; ++j) {
	  if ((x = obj.streamGetChar()) == EOF)
	    goto err2;
	  lookup[i][j] = (Guchar)x;
	}
      }
    } else if (obj.isString()) {
      s = obj.getString()->getCString();
      for (i = 0; i <= indexHigh; ++i)
	for (j = 0; j < numComps; ++j)
	  lookup[i][j] = (Guchar)*s++;
    } else {
      goto err2;
    }
    obj.free();
  }

  csObj.free();
  return;

 err2:
  obj.free();
 err1:
  csObj.free();
  ok = gFalse;
}

GfxColorSpace::GfxColorSpace(GfxColorMode mode1) {
  sepFunc = NULL;
  mode = mode1;
  indexed = gFalse;
  switch (mode) {
  case colorGray: numComps = 1; break;
  case colorCMYK: numComps = 4; break;
  case colorRGB:  numComps = 3; break;
  }
  lookup = NULL;
  ok = gTrue;
}

GfxColorSpace::~GfxColorSpace() {
  if (sepFunc)
    delete sepFunc;
  gfree(lookup);
}

GfxColorSpace::GfxColorSpace(GfxColorSpace *colorSpace) {
  int size;

  if (colorSpace->sepFunc)
    sepFunc = colorSpace->sepFunc->copy();
  else
    sepFunc = NULL;
  mode = colorSpace->mode;
  indexed = colorSpace->indexed;
  numComps = colorSpace->numComps;
  indexHigh = colorSpace->indexHigh;
  if (indexed) {
    size = (indexHigh + 1) * 4 * sizeof(Guchar);
    lookup = (Guchar (*)[4])gmalloc(size);
    memcpy(lookup, colorSpace->lookup, size);
  } else {
    lookup = NULL;
  }
  ok = gTrue;
}

void GfxColorSpace::setMode(Object *colorSpace) {
  Object obj;

  if (colorSpace->isName("DeviceGray") || colorSpace->isName("G")) {
    mode = colorGray;
    numComps = 1;
  } else if (colorSpace->isName("DeviceRGB") || colorSpace->isName("RGB")) {
    mode = colorRGB;
    numComps = 3;
  } else if (colorSpace->isName("DeviceCMYK") || colorSpace->isName("CMYK")) {
    mode = colorCMYK;
    numComps = 4;
  } else if (colorSpace->isArray()) {
    colorSpace->arrayGet(0, &obj);
    if (obj.isName("CalGray")) {
      mode = colorGray;
      numComps = 1;
    } else if (obj.isName("CalRGB")) {
      mode = colorRGB;
      numComps = 3;
    } else if (obj.isName("CalCMYK")) {
      mode = colorCMYK;
      numComps = 4;
    } else {
      ok = gFalse;
    }
    obj.free();
  } else {
    ok = gFalse;
  }
}

void GfxColorSpace::getColor(double x[4], GfxColor *color) {
  double y[4];
  Guchar *p;

  if (sepFunc) {
    sepFunc->transform(x, y);
  } else {
    y[0] = x[0];
    y[1] = x[1];
    y[2] = x[2];
    y[3] = x[3];
  }
  if (indexed) {
    p = lookup[(int)(y[0] + 0.5)];
    switch (mode) {
    case colorGray:
      color->setGray(p[0] / 255.0);
      break;
    case colorCMYK:
      color->setCMYK(p[0] / 255.0, p[1] / 255.0, p[2] / 255.0, p[3] / 255.0);
      break;
    case colorRGB:
      color->setRGB(p[0] / 255.0, p[1] / 255.0, p[2] / 255.0);
      break;
    }
  } else {
    switch (mode) {
    case colorGray:
      color->setGray(y[0]);
      break;
    case colorCMYK:
      color->setCMYK(y[0], y[1], y[2], y[3]);
      break;
    case colorRGB:
      color->setRGB(y[0], y[1], y[2]);
      break;
    }
  }
}

//------------------------------------------------------------------------
// Function
//------------------------------------------------------------------------

Function::Function(Object *funcObj) {
  Stream *str;
  Dict *dict;
  int nSamples, sampleBits;
  double sampleMul;
  Object obj1, obj2;
  Guint buf, bitMask;
  int bits;
  int s;
  int i;

  ok = gFalse;
  samples = NULL;

  if (!funcObj->isStream()) {
    error(-1, "Expected function dictionary");
    goto err3;
  }
  str = funcObj->getStream();
  dict = str->getDict();

  //----- FunctionType
  if (!dict->lookup("FunctionType", &obj1)->isInt() ||
      obj1.getInt() != 0) {
    error(-1, "Unknown function type");
    goto err2;
  }
  obj1.free();

  //----- Domain
  if (!dict->lookup("Domain", &obj1)->isArray()) {
    error(-1, "Function is missing domain");
    goto err2;
  }
  m = obj1.arrayGetLength() / 2;
  if (m > 4) {
    error(-1, "Functions with more than 1 input are unsupported");
    goto err2;
  }
  for (i = 0; i < m; ++i) {
    obj1.arrayGet(2*i, &obj2);
    if (!obj2.isNum()) {
      error(-1, "Illegal value in function domain array");
      goto err1;
    }
    domain[i][0] = obj2.getNum();
    obj2.free();
    obj1.arrayGet(2*i+1, &obj2);
    if (!obj2.isNum()) {
      error(-1, "Illegal value in function domain array");
      goto err1;
    }
    domain[i][1] = obj2.getNum();
    obj2.free();
  }
  obj1.free();

  //----- Range
  if (!dict->lookup("Range", &obj1)->isArray()) {
    error(-1, "Function is missing range");
    goto err2;
  }
  n = obj1.arrayGetLength() / 2;
  if (n > 4) {
    error(-1, "Functions with more than 4 outputs are unsupported");
    goto err2;
  }
  for (i = 0; i < n; ++i) {
    obj1.arrayGet(2*i, &obj2);
    if (!obj2.isNum()) {
      error(-1, "Illegal value in function range array");
      goto err1;
    }
    range[i][0] = obj2.getNum();
    obj2.free();
    obj1.arrayGet(2*i+1, &obj2);
    if (!obj2.isNum()) {
      error(-1, "Illegal value in function range array");
      goto err1;
    }
    range[i][1] = obj2.getNum();
    obj2.free();
  }
  obj1.free();

  //----- Size
  if (!dict->lookup("Size", &obj1)->isArray() ||
      obj1.arrayGetLength() != m) {
    error(-1, "Function has missing or invalid size array");
    goto err2;
  }
  for (i = 0; i < m; ++i) {
    obj1.arrayGet(i, &obj2);
    if (!obj2.isInt()) {
      error(-1, "Illegal value in function size array");
      goto err1;
    }
    sampleSize[i] = obj2.getInt();
    obj2.free();
  }
  obj1.free();

  //----- BitsPerSample
  if (!dict->lookup("BitsPerSample", &obj1)->isInt()) {
    error(-1, "Function has missing or invalid BitsPerSample");
    goto err2;
  }
  sampleBits = obj1.getInt();
  sampleMul = 1.0 / (double)((1 << sampleBits) - 1);
  obj1.free();

  //----- Encode
  if (dict->lookup("Encode", &obj1)->isArray() &&
      obj1.arrayGetLength() == 2*m) {
    for (i = 0; i < m; ++i) {
      obj1.arrayGet(2*i, &obj2);
      if (!obj2.isNum()) {
	error(-1, "Illegal value in function encode array");
	goto err1;
      }
      encode[i][0] = obj2.getNum();
      obj2.free();
      obj1.arrayGet(2*i+1, &obj2);
      if (!obj2.isNum()) {
	error(-1, "Illegal value in function encode array");
	goto err1;
      }
      encode[i][1] = obj2.getNum();
      obj2.free();
    }
  } else {
    for (i = 0; i < m; ++i) {
      encode[i][0] = 0;
      encode[i][1] = sampleSize[i] - 1;
    }
  }
  obj1.free();

  //----- Decode
  if (dict->lookup("Decode", &obj1)->isArray() &&
      obj1.arrayGetLength() == 2*n) {
    for (i = 0; i < n; ++i) {
      obj1.arrayGet(2*i, &obj2);
      if (!obj2.isNum()) {
	error(-1, "Illegal value in function decode array");
	goto err1;
      }
      decode[i][0] = obj2.getNum();
      obj2.free();
      obj1.arrayGet(2*i+1, &obj2);
      if (!obj2.isNum()) {
	error(-1, "Illegal value in function decode array");
	goto err1;
      }
      decode[i][1] = obj2.getNum();
      obj2.free();
    }
  } else {
    for (i = 0; i < n; ++i) {
      decode[i][0] = range[i][0];
      decode[i][1] = range[i][1];
    }
  }
  obj1.free();

  //----- samples
  nSamples = n;
  for (i = 0; i < m; ++i)
    nSamples *= sampleSize[i];
  samples = (double *)gmalloc(nSamples * sizeof(double));
  buf = 0;
  bits = 0;
  bitMask = (1 << sampleBits) - 1;
  str->reset();
  for (i = 0; i < nSamples; ++i) {
    if (sampleBits == 8) {
      s = str->getChar();
    } else if (sampleBits == 16) {
      s = str->getChar();
      s = (s << 8) + str->getChar();
    } else if (sampleBits == 32) {
      s = str->getChar();
      s = (s << 8) + str->getChar();
      s = (s << 8) + str->getChar();
      s = (s << 8) + str->getChar();
    } else {
      while (bits < sampleBits) {
	buf = (buf << 8) | (str->getChar() & 0xff);
	bits += 8;
      }
      s = (buf >> (bits - sampleBits)) & bitMask;
      bits -= sampleBits;
    }
    samples[i] = (double)s * sampleMul;
  }

  ok = gTrue;
  return;

 err1:
  obj2.free();
 err2:
  obj1.free();
 err3:
  return;
}

Function::Function(Function *func) {
  int nSamples, i;

  m = func->m;
  n = func->n;
  memcpy(domain, func->domain, sizeof(domain));
  memcpy(range, func->range, sizeof(range));
  memcpy(sampleSize, func->sampleSize, sizeof(sampleSize));
  memcpy(encode, func->encode, sizeof(encode));
  memcpy(decode, func->decode, sizeof(decode));

  nSamples = n;
  for (i = 0; i < m; ++i)
    nSamples *= sampleSize[i];
  samples = (double *)gmalloc(nSamples * sizeof(double));
  memcpy(samples, func->samples, nSamples * sizeof(double));

  ok = gTrue;
}

Function::~Function() {
  if (samples)
    gfree(samples);
}

void Function::transform(double *in, double *out) {
  double e[4];
  double s;
  double x0, x1;
  int e0, e1;
  double efrac;
  int i;

  // map input values into sample array
  for (i = 0; i < m; ++i) {
    e[i] = ((in[i] - domain[i][0]) / (domain[i][1] - domain[i][0])) *
	   (encode[i][1] - encode[i][0]) + encode[i][0];
    if (e[i] < 0)
      e[i] = 0;
    else if (e[i] > sampleSize[i] - 1)
      e[i] = sampleSize[i] - 1;
  }

  for (i = 0; i < n; ++i) {

    // m-linear interpolation
    // (only m=1 is currently supported)
    e0 = (int)floor(e[0]);
    e1 = (int)ceil(e[0]);
    efrac = e[0] - e0;
    x0 = samples[e0 * n + i];
    x1 = samples[e1 * n + i];
    s = (1 - efrac) * x0 + efrac * x1;

    // map output values to range
    out[i] = s * (decode[i][1] - decode[i][0]) + decode[i][0];
    if (out[i] < range[i][0])
      out[i] = range[i][0];
    else if (out[i] > range[i][1])
      out[i] = range[i][1];
  }
}

//------------------------------------------------------------------------
// GfxImageColorMap
//------------------------------------------------------------------------

GfxImageColorMap::GfxImageColorMap(int bits1, Object *decode,
				   GfxColorSpace *colorSpace1) {
  GfxColor color;
  double x[4];
  int maxPixel;
  Object obj;
  int i, j;

  ok = gTrue;

  // bits per component and colorspace
  bits = bits1;
  maxPixel = (1 << bits) - 1;
  colorSpace = colorSpace1;
  mode = colorSpace->getMode();

  // get decode map
  if (decode->isNull()) {
    if (colorSpace->isIndexed()) {
      indexed = gTrue;
      numComps = 1;
      decodeLow[0] = 0;
      decodeRange[0] = maxPixel;
    } else {
      indexed = gFalse;
      numComps = colorSpace->getNumPixelComps();
      for (i = 0; i < numComps; ++i) {
	decodeLow[i] = 0;
	decodeRange[i] = 1;
      }
    }
  } else if (decode->isArray()) {
    numComps = decode->arrayGetLength() / 2;
    if (numComps != colorSpace->getNumPixelComps())
      goto err1;
    indexed = colorSpace->isIndexed();
    for (i = 0; i < numComps; ++i) {
      decode->arrayGet(2*i, &obj);
      if (!obj.isNum())
	goto err2;
      decodeLow[i] = obj.getNum();
      obj.free();
      decode->arrayGet(2*i+1, &obj);
      if (!obj.isNum())
	goto err2;
      decodeRange[i] = obj.getNum() - decodeLow[i];
      obj.free();
    }
  } else {
    goto err1;
  }

  // construct lookup table
  lookup = (double (*)[4])gmalloc((maxPixel + 1) * 4 * sizeof(double));
  if (indexed) {
    for (i = 0; i <= maxPixel; ++i) {
      x[0] = (double)i;
      colorSpace->getColor(x, &color);
      lookup[i][0] = color.getR();
      lookup[i][1] = color.getG();
      lookup[i][2] = color.getB();
    }
  } else {
    for (i = 0; i <= maxPixel; ++i)
      for (j = 0; j < numComps; ++j)
	lookup[i][j] = decodeLow[j] + (i * decodeRange[j]) / maxPixel;
  }

  return;

 err2:
  obj.free();
 err1:
  ok = gFalse;
}

GfxImageColorMap::~GfxImageColorMap() {
  delete colorSpace;
  gfree(lookup);
}

void GfxImageColorMap::getColor(Guchar x[4], GfxColor *color) {
  double *p;

  if (indexed) {
    p = lookup[x[0]];
    color->setRGB(p[0], p[1], p[2]);
  } else {
    switch (mode) {
    case colorGray:
      color->setGray(lookup[x[0]][0]);
      break;
    case colorCMYK:
      color->setCMYK(lookup[x[0]][0], lookup[x[1]][1],
		     lookup[x[2]][2], lookup[x[3]][3]);
      break;
    case colorRGB:
      color->setRGB(lookup[x[0]][0], lookup[x[1]][1], lookup[x[2]][2]);
      break;
    }
  }
}

//------------------------------------------------------------------------
// GfxSubpath and GfxPath
//------------------------------------------------------------------------

GfxSubpath::GfxSubpath(double x1, double y1) {
  size = 16;
  x = (double *)gmalloc(size * sizeof(double));
  y = (double *)gmalloc(size * sizeof(double));
  curve = (GBool *)gmalloc(size * sizeof(GBool));
  n = 1;
  x[0] = x1;
  y[0] = y1;
  curve[0] = gFalse;
}

GfxSubpath::~GfxSubpath() {
  gfree(x);
  gfree(y);
  gfree(curve);
}

// Used for copy().
GfxSubpath::GfxSubpath(GfxSubpath *subpath) {
  size = subpath->size;
  n = subpath->n;
  x = (double *)gmalloc(size * sizeof(double));
  y = (double *)gmalloc(size * sizeof(double));
  curve = (GBool *)gmalloc(size * sizeof(GBool));
  memcpy(x, subpath->x, n * sizeof(double));
  memcpy(y, subpath->y, n * sizeof(double));
  memcpy(curve, subpath->curve, n * sizeof(GBool));
}

void GfxSubpath::lineTo(double x1, double y1) {
  if (n >= size) {
    size += 16;
    x = (double *)grealloc(x, size * sizeof(double));
    y = (double *)grealloc(y, size * sizeof(double));
    curve = (GBool *)grealloc(curve, size * sizeof(GBool));
  }
  x[n] = x1;
  y[n] = y1;
  curve[n] = gFalse;
  ++n;
}

void GfxSubpath::curveTo(double x1, double y1, double x2, double y2,
			 double x3, double y3) {
  if (n+3 > size) {
    size += 16;
    x = (double *)grealloc(x, size * sizeof(double));
    y = (double *)grealloc(y, size * sizeof(double));
    curve = (GBool *)grealloc(curve, size * sizeof(GBool));
  }
  x[n] = x1;
  y[n] = y1;
  x[n+1] = x2;
  y[n+1] = y2;
  x[n+2] = x3;
  y[n+2] = y3;
  curve[n] = curve[n+1] = gTrue;
  curve[n+2] = gFalse;
  n += 3;
}

GfxPath::GfxPath() {
  justMoved = gFalse;
  size = 16;
  n = 0;
  subpaths = (GfxSubpath **)gmalloc(size * sizeof(GfxSubpath *));
}

GfxPath::~GfxPath() {
  int i;

  for (i = 0; i < n; ++i)
    delete subpaths[i];
  gfree(subpaths);
}

// Used for copy().
GfxPath::GfxPath(GBool justMoved1, double firstX1, double firstY1,
		 GfxSubpath **subpaths1, int n1, int size1) {
  int i;

  justMoved = justMoved1;
  firstX = firstX1;
  firstY = firstY1;
  size = size1;
  n = n1;
  subpaths = (GfxSubpath **)gmalloc(size * sizeof(GfxSubpath *));
  for (i = 0; i < n; ++i)
    subpaths[i] = subpaths1[i]->copy();
}

void GfxPath::moveTo(double x, double y) {
  justMoved = gTrue;
  firstX = x;
  firstY = y;
}

void GfxPath::lineTo(double x, double y) {
  if (justMoved) {
    if (n >= size) {
      size += 16;
      subpaths = (GfxSubpath **)
		   grealloc(subpaths, size * sizeof(GfxSubpath *));
    }
    subpaths[n] = new GfxSubpath(firstX, firstY);
    ++n;
    justMoved = gFalse;
  }
  subpaths[n-1]->lineTo(x, y);
}

void GfxPath::curveTo(double x1, double y1, double x2, double y2,
	     double x3, double y3) {
  if (justMoved) {
    if (n >= size) {
      size += 16;
      subpaths = (GfxSubpath **)
		   grealloc(subpaths, size * sizeof(GfxSubpath *));
    }
    subpaths[n] = new GfxSubpath(firstX, firstY);
    ++n;
    justMoved = gFalse;
  }
  subpaths[n-1]->curveTo(x1, y1, x2, y2, x3, y3);
}


//------------------------------------------------------------------------
// GfxState
//------------------------------------------------------------------------

GfxState::GfxState(int dpi, double px1a, double py1a, double px2a, double py2a,
		   int rotate, GBool upsideDown) {
  double k;

  px1 = px1a;
  py1 = py1a;
  px2 = px2a;
  py2 = py2a;
  k = (double)dpi / 72.0;
  if (rotate == 90) {
    ctm[0] = 0;
    ctm[1] = upsideDown ? k : -k;
    ctm[2] = k;
    ctm[3] = 0;
    ctm[4] = -k * py1;
    ctm[5] = k * (upsideDown ? -px1 : px2);
    pageWidth = (int)(k * (py2 - py1));
    pageHeight = (int)(k * (px2 - px1));
  } else if (rotate == 180) {
    ctm[0] = -k;
    ctm[1] = 0;
    ctm[2] = 0;
    ctm[3] = upsideDown ? k : -k;
    ctm[4] = k * px2;
    ctm[5] = k * (upsideDown ? -py1 : py2);
    pageWidth = (int)(k * (px2 - px1));
    pageHeight = (int)(k * (py2 - py1));
  } else if (rotate == 270) {
    ctm[0] = 0;
    ctm[1] = upsideDown ? -k : k;
    ctm[2] = -k;
    ctm[3] = 0;
    ctm[4] = k * py2;
    ctm[5] = k * (upsideDown ? px2 : -px1);
    pageWidth = (int)(k * (py2 - py1));
    pageHeight = (int)(k * (px2 - px1));
  } else {
    ctm[0] = k;
    ctm[1] = 0;
    ctm[2] = 0;
    ctm[3] = upsideDown ? -k : k;
    ctm[4] = -k * px1;
    ctm[5] = k * (upsideDown ? py2 : -py1);
    pageWidth = (int)(k * (px2 - px1));
    pageHeight = (int)(k * (py2 - py1));
  }

  fillColorSpace = new GfxColorSpace(colorGray);
  strokeColorSpace = new GfxColorSpace(colorGray);
  fillColor.setGray(0);
  strokeColor.setGray(0);

  lineWidth = 1;
  lineDash = NULL;
  lineDashLength = 0;
  lineDashStart = 0;
  flatness = 0;
  lineJoin = 0;
  lineCap = 0;
  miterLimit = 10;

  font = NULL;
  fontSize = 0;
  textMat[0] = 1; textMat[1] = 0;
  textMat[2] = 0; textMat[3] = 1;
  textMat[4] = 0; textMat[5] = 0;
  charSpace = 0;
  wordSpace = 0;
  horizScaling = 1;
  leading = 0;
  rise = 0;
  render = 0;

  path = new GfxPath();
  curX = curY = 0;
  lineX = lineY = 0;

  saved = NULL;
}

GfxState::~GfxState() {
  if (fillColorSpace)
    delete fillColorSpace;
  if (strokeColorSpace)
    delete strokeColorSpace;
  gfree(lineDash);
  delete path;
  if (saved)
    delete saved;
}

// Used for copy();
GfxState::GfxState(GfxState *state) {
  memcpy(this, state, sizeof(GfxState));
  if (fillColorSpace)
    fillColorSpace = state->fillColorSpace->copy();
  if (strokeColorSpace)
    strokeColorSpace = state->strokeColorSpace->copy();
  if (lineDashLength > 0) {
    lineDash = (double *)gmalloc(lineDashLength * sizeof(double));
    memcpy(lineDash, state->lineDash, lineDashLength * sizeof(double));
  }
  path = state->path->copy();
  saved = NULL;
}

double GfxState::transformWidth(double w) {
  double x, y;

  x = ctm[0] + ctm[2];
  y = ctm[1] + ctm[3];
  return w * sqrt(0.5 * (x * x + y * y));
}

double GfxState::getTransformedFontSize() {
  double x1, y1, x2, y2;

  x1 = textMat[2] * fontSize;
  y1 = textMat[3] * fontSize;
  x2 = ctm[0] * x1 + ctm[2] * y1;
  y2 = ctm[1] * x1 + ctm[3] * y1;
  return sqrt(x2 * x2 + y2 * y2);
}

void GfxState::getFontTransMat(double *m11, double *m12,
			       double *m21, double *m22) {
  *m11 = (textMat[0] * ctm[0] + textMat[1] * ctm[2]) * fontSize;
  *m12 = (textMat[0] * ctm[1] + textMat[1] * ctm[3]) * fontSize;
  *m21 = (textMat[2] * ctm[0] + textMat[3] * ctm[2]) * fontSize;
  *m22 = (textMat[2] * ctm[1] + textMat[3] * ctm[3]) * fontSize;
}

void GfxState::concatCTM(double a, double b, double c,
			 double d, double e, double f) {
  double a1 = ctm[0];
  double b1 = ctm[1];
  double c1 = ctm[2];
  double d1 = ctm[3];

  ctm[0] = a * a1 + b * c1;
  ctm[1] = a * b1 + b * d1;
  ctm[2] = c * a1 + d * c1;
  ctm[3] = c * b1 + d * d1;
  ctm[4] = e * a1 + f * c1 + ctm[4];
  ctm[5] = e * b1 + f * d1 + ctm[5];
}

void GfxState::setFillColorSpace(GfxColorSpace *colorSpace) {
  if (fillColorSpace)
    delete fillColorSpace;
  fillColorSpace = colorSpace;
}

void GfxState::setStrokeColorSpace(GfxColorSpace *colorSpace) {
  if (strokeColorSpace)
    delete strokeColorSpace;
  strokeColorSpace = colorSpace;
}

void GfxState::setLineDash(double *dash, int length, double start) {
  if (lineDash)
    gfree(lineDash);
  lineDash = dash;
  lineDashLength = length;
  lineDashStart = start;
}

void GfxState::clearPath() {
  delete path;
  path = new GfxPath();
}

void GfxState::textShift(double tx) {
  double dx, dy;

  textTransformDelta(tx, 0, &dx, &dy);
  curX += dx;
  curY += dy;
}

GfxState *GfxState::save() {
  GfxState *newState;

  newState = copy();
  newState->saved = this;
  return newState;
}

GfxState *GfxState::restore() {
  GfxState *oldState;

  if (saved) {
    oldState = saved;
    saved = NULL;
    delete this;
  } else {
    oldState = this;
  }
  return oldState;
}
