static int ssh_timestamp_difference(struct ssh_timestamp *old,
    struct ssh_timestamp *new){
  long seconds, usecs, msecs;
  seconds = new->seconds - old->seconds;
  usecs = new->useconds - old->useconds;
  if (usecs < 0){
    seconds--;
    usecs += 1000000;
  }
  msecs = seconds * 1000 + usecs/1000;
  return msecs;
}


void Gfx::opLineTo(Object args[], int numArgs) {
  if (!state->isCurPt()) {
    error(getPos(), "No current point in lineto");
    return;
  }
  state->lineTo(args[0].getNum(), args[1].getNum());
}


void PreScanOutputDev::drawSoftMaskedImage(GfxState * /*state*/, Object * /*ref*/,
					   Stream * /*str*/,
					   int /*width*/, int /*height*/,
					   GfxImageColorMap *colorMap,
					   GBool /*interpolate*/,
					   Stream * /*maskStr*/,
					   int /*maskWidth*/, int /*maskHeight*/,
					   GfxImageColorMap * /*maskColorMap*/,
					   GBool /*maskInterpolate*/) {
  GfxColorSpace *colorSpace;

  colorSpace = colorMap->getColorSpace();
  if (colorSpace->getMode() == csIndexed) {
    colorSpace = ((GfxIndexedColorSpace *)colorSpace)->getBase();
  }
  if (colorSpace->getMode() != csDeviceGray &&
      colorSpace->getMode() != csCalGray) {
    gray = gFalse;
  }
  mono = gFalse;
  transparency = gTrue;
  gdi = gFalse;
}


void CairoOutputDev::updateAll(GfxState *state) {
  updateLineDash(state);
  updateLineJoin(state);
  updateLineCap(state);
  updateLineWidth(state);
  updateFlatness(state);
  updateMiterLimit(state);
  updateFillColor(state);
  updateStrokeColor(state);
  updateFillOpacity(state);
  updateStrokeOpacity(state);
  updateBlendMode(state);
  needFontUpdate = gTrue;
  if (text)
    text->updateFont(state);
}


void Gfx::opRectangle(Object args[], int numArgs) {
  double x, y, w, h;

  x = args[0].getNum();
  y = args[1].getNum();
  w = args[2].getNum();
  h = args[3].getNum();
  state->moveTo(x, y);
  state->lineTo(x + w, y);
  state->lineTo(x + w, y + h);
  state->lineTo(x, y + h);
  state->closePath();
}


void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
  Object obj;
  GfxColorSpace *colorSpace;
  GfxColor color;

  state->setStrokePattern(NULL);
  res->lookupColorSpace(args[0].getName(), &obj);
  if (obj.isNull()) {
    colorSpace = GfxColorSpace::parse(&args[0], this);
  } else {
    colorSpace = GfxColorSpace::parse(&obj, this);
  }
  obj.free();
  if (colorSpace) {
    state->setStrokeColorSpace(colorSpace);
    out->updateStrokeColorSpace(state);
    colorSpace->getDefaultColor(&color);
    state->setStrokeColor(&color);
    out->updateStrokeColor(state);
  } else {
    error(getPos(), "Bad color space (stroke)");
  }
}


GBool usesTransparency() { return transparency; }


void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
  double x0, y0, x1, y1, x2, y2;
  int i;

  if (out->useShadedFills( shading->getType()) && !contentIsHidden()) {
    if (out->gouraudTriangleShadedFill( state, shading))
      return;
  }

  // preallocate a path (speed improvements)
  state->moveTo(0., 0.);
  state->lineTo(1., 0.);
  state->lineTo(0., 1.);
  state->closePath();

  GfxState::ReusablePathIterator *reusablePath = state->getReusablePath();

  if (shading->isParameterized()) {
    // work with parameterized values:
    double color0, color1, color2;
    // a relative threshold:
    const double refineColorThreshold = gouraudParameterizedColorDelta *
                                        (shading->getParameterDomainMax() - shading->getParameterDomainMin());
    for (i = 0; i < shading->getNTriangles(); ++i) {
      shading->getTriangle(i, &x0, &y0, &color0,
                              &x1, &y1, &color1,
                              &x2, &y2, &color2);
      gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, refineColorThreshold, 0, shading, reusablePath);
    }

  } else {
    // this always produces output -- even for parameterized ranges.
    // But it ignores the parameterized color map (the function).
    //
    // Note that using this code in for parameterized shadings might be
    // correct in circumstances (namely if the function is linear in the actual
    // triangle), but in general, it will simply be wrong.
    GfxColor color0, color1, color2;
    for (i = 0; i < shading->getNTriangles(); ++i) {
      shading->getTriangle(i, &x0, &y0, &color0,
                              &x1, &y1, &color1,
                              &x2, &y2, &color2);
      gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, shading->getColorSpace()->getNComps(), 0, reusablePath);
    }
  }

  delete reusablePath;
}


virtual void updateFont(GfxState *state) { }


virtual void updateFillOverprint(GfxState * /*state*/) {}


void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) {
  int start, i;

  if (out->useShadedFills( shading->getType()) && !contentIsHidden()) {
    if (out->patchMeshShadedFill( state, shading))
      return;
  }

  if (shading->getNPatches() > 128) {
    start = 3;
  } else if (shading->getNPatches() > 64) {
    start = 2;
  } else if (shading->getNPatches() > 16) {
    start = 1;
  } else {
    start = 0;
  }
  /*
   * Parameterized shadings take one parameter [t_0,t_e]
   * and map it into the color space.
   *
   * Consequently, all color values are stored as doubles.
   *
   * These color values are interpreted as parameters for parameterized
   * shadings and as colorspace entities otherwise.
   *
   * The only difference is that color space entities are stored into
   * DOUBLE arrays, not into arrays of type GfxColorComp.
   */
  const int colorComps = shading->getColorSpace()->getNComps();
  double refineColorThreshold;
  if( shading->isParameterized() ) {
	  refineColorThreshold = gouraudParameterizedColorDelta *
		  (shading->getParameterDomainMax() - shading->getParameterDomainMin());

  } else {
	  refineColorThreshold = patchColorDelta;
  }

  for (i = 0; i < shading->getNPatches(); ++i) {
    fillPatch(shading->getPatch(i),
             colorComps,
             shading->isParameterized() ? 1 : colorComps,
             refineColorThreshold,
             start,
             shading);
  }
}


void Gfx::opEOClip(Object args[], int numArgs) {
  clip = clipEO;
}


void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
  ++ignoreUndef;
}


  virtual void beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/,
				      GfxColorSpace * /*blendingColorSpace*/,
				      GBool /*isolated*/, GBool /*knockout*/,
				      GBool /*forSoftMask*/) {}


void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) {
  Ref fontFileID;
  GooString *name;
  PSFontParam *fontParam;
  GooString *psName;
  char buf[16];
  GBool subst;
  UnicodeMap *uMap;
  char *charName;
  double xs, ys;
  int code;
  double w1, w2;
  double *fm;
  int i, j;
  DisplayFontParam *dfp;

  // check if font is already set up
  for (i = 0; i < fontIDLen; ++i) {
    if (fontIDs[i].num == font->getID()->num &&
	fontIDs[i].gen == font->getID()->gen) {
      return;
    }
  }

  // add entry to fontIDs list
  if (fontIDLen >= fontIDSize) {
    fontIDSize += 64;
    fontIDs = (Ref *)greallocn(fontIDs, fontIDSize, sizeof(Ref));
  }
  fontIDs[fontIDLen++] = *font->getID();

  xs = ys = 1;
  subst = gFalse;

  // check for resident 8-bit font
  if (font->getName() &&
      (fontParam = globalParams->getPSFont(font->getName()))) {
    psName = new GooString(fontParam->psFontName->getCString());

  // check for embedded Type 1 font
  } else if (globalParams->getPSEmbedType1() &&
	     font->getType() == fontType1 &&
	     font->getEmbeddedFontID(&fontFileID) &&
	     font->getEmbeddedFontName()) {
    psName = font->getEmbeddedFontName()->sanitizedName(gTrue /* ps mode */);
    setupEmbeddedType1Font(&fontFileID, psName);

  // check for embedded Type 1C font
  } else if (globalParams->getPSEmbedType1() &&
	     font->getType() == fontType1C &&
	     font->getEmbeddedFontID(&fontFileID) &&
	     font->getOrigName()) {
    // use the PDF font name because the embedded font name might
    // not include the subset prefix
    psName = font->getOrigName()->sanitizedName(gTrue /* ps mode */);
    setupEmbeddedType1CFont(font, &fontFileID, psName);

  // check for embedded OpenType - Type 1C font
  } else if (globalParams->getPSEmbedType1() &&
	     font->getType() == fontType1COT &&
	     font->getEmbeddedFontID(&fontFileID) &&
	     font->getOrigName()) {
    // use the PDF font name because the embedded font name might
    // not include the subset prefix
    psName = font->getOrigName()->sanitizedName(gTrue /* ps mode */);
    setupEmbeddedOpenTypeT1CFont(font, &fontFileID, psName);

  // check for external Type 1 font file
  } else if (globalParams->getPSEmbedType1() &&
	     font->getType() == fontType1 &&
	     font->getExtFontFile() &&
	     font->getName()) {
    // this assumes that the PS font name matches the PDF font name
    psName = font->getName()->copy();
    setupExternalType1Font(font->getExtFontFile(), psName);

  // check for embedded TrueType font
  } else if (globalParams->getPSEmbedTrueType() &&
	     (font->getType() == fontTrueType ||
	      font->getType() == fontTrueTypeOT) &&
	     font->getEmbeddedFontID(&fontFileID) &&
	     font->getEmbeddedFontName()) {
    psName = font->getEmbeddedFontName()->sanitizedName(gTrue /* ps mode */);
    setupEmbeddedTrueTypeFont(font, &fontFileID, psName);

  // check for external TrueType font file
  } else if (globalParams->getPSEmbedTrueType() &&
	     font->getType() == fontTrueType &&
	     font->getExtFontFile()) {
    psName = setupExternalTrueTypeFont(font);

  // check for embedded CID PostScript font
  } else if (globalParams->getPSEmbedCIDPostScript() &&
	     font->getType() == fontCIDType0C &&
	     font->getEmbeddedFontID(&fontFileID) &&
	     font->getEmbeddedFontName()) {
    psName = font->getEmbeddedFontName()->sanitizedName(gTrue /* ps mode */);
    setupEmbeddedCIDType0Font(font, &fontFileID, psName);

  // check for embedded CID TrueType font
  } else if (globalParams->getPSEmbedCIDTrueType() &&
	     (font->getType() == fontCIDType2 ||
	      font->getType() == fontCIDType2OT) &&
	     font->getEmbeddedFontID(&fontFileID) &&
	     font->getEmbeddedFontName()) {
    psName = font->getEmbeddedFontName()->sanitizedName(gTrue /* ps mode */);
    setupEmbeddedCIDTrueTypeFont(font, &fontFileID, psName, gTrue);

  // check for embedded OpenType - CID CFF font
  } else if (globalParams->getPSEmbedCIDPostScript() &&
	     font->getType() == fontCIDType0COT &&
	     font->getEmbeddedFontID(&fontFileID) &&
	     font->getEmbeddedFontName()) {
    psName = font->getEmbeddedFontName()->sanitizedName(gTrue /* ps mode */);
    setupEmbeddedOpenTypeCFFFont(font, &fontFileID, psName);

  // check for Type 3 font
  } else if (font->getType() == fontType3) {
    psName = GooString::format("T3_{0:d}_{1:d}",
			     font->getID()->num, font->getID()->gen);
    setupType3Font(font, psName, parentResDict);

  // check for external CID TrueType font file
  } else if (globalParams->getPSEmbedCIDTrueType() &&
	     font->getType() == fontCIDType2 &&
	     font->getExtFontFile()) {
    psName = setupExternalCIDTrueTypeFont(font, font->getExtFontFile());

  // do 8-bit font substitution
  } else if (!font->isCIDFont()) {
    subst = gTrue;
    name = font->getName();
    psName = NULL;
    if (name) {
      for (i = 0; psFonts[i]; ++i) {
	if (name->cmp(psFonts[i]) == 0) {
	  psName = new GooString(psFonts[i]);
	  break;
	}
      }
    }
    if (!psName) {
      if (substFonts) {
	if (font->isFixedWidth()) {
	  i = 8;
	} else if (font->isSerif()) {
	  i = 4;
	} else {
	  i = 0;
	}
	if (font->isBold()) {
	  i += 2;
	}
	if (font->isItalic()) {
	  i += 1;
	}
	psName = new GooString(psSubstFonts[i].psName);
	for (code = 0; code < 256; ++code) {
	  if ((charName = ((Gfx8BitFont *)font)->getCharName(code)) &&
	      charName[0] == 'm' && charName[1] == '\0') {
	    break;
	  }
	}
	if (code < 256) {
	  w1 = ((Gfx8BitFont *)font)->getWidth(code);
	} else {
	  w1 = 0;
	}
	w2 = psSubstFonts[i].mWidth;
	xs = w1 / w2;
	if (xs < 0.1) {
	  xs = 1;
	}
      } else {
	psName = new GooString(name);
	xs = 1;
      }
      if (font->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.
	ys = xs;
	fm = font->getFontMatrix();
	if (fm[0] != 0) {
	  ys *= fm[3] / fm[0];
	}
      } else {
	ys = 1;
      }
    }

  // do 16-bit font substitution
  } else if ((fontParam = globalParams->
	        getPSFont16(font->getName(),
			    ((GfxCIDFont *)font)->getCollection(),
			    font->getWMode()))) {
    subst = gTrue;
    psName = fontParam->psFontName->copy();
    if (font16EncLen >= font16EncSize) {
      font16EncSize += 16;
      font16Enc = (PSFont16Enc *)greallocn(font16Enc,
					   font16EncSize, sizeof(PSFont16Enc));
    }
    font16Enc[font16EncLen].fontID = *font->getID();
    font16Enc[font16EncLen].enc = fontParam->encoding->copy();
    if ((uMap = globalParams->getUnicodeMap(font16Enc[font16EncLen].enc))) {
      uMap->decRefCnt();
      ++font16EncLen;
    } else {
      error(-1, "Couldn't find Unicode map for 16-bit font encoding '%s'",
	    font16Enc[font16EncLen].enc->getCString());
    }

    // try the display font for embedding
  } else if (globalParams->getPSEmbedCIDTrueType() &&
	     ((GfxCIDFont *)font)->getCollection() &&
	     (dfp = globalParams->
	      getDisplayFont(font)) &&
	     dfp->kind == displayFontTT) {
    psName = setupExternalCIDTrueTypeFont(font, dfp->tt.fileName, dfp->tt.faceIndex);

  // give up - can't do anything with this font
  } else {
    error(-1, "Couldn't find a font to substitute for '%s' ('%s' character collection)",
	  font->getName() ? font->getName()->getCString() : "(unnamed)",
	  ((GfxCIDFont *)font)->getCollection()
	    ? ((GfxCIDFont *)font)->getCollection()->getCString()
	    : "(unknown)");
    return;
  }

  // generate PostScript code to set up the font
  if (font->isCIDFont()) {
    if (level == psLevel3 || level == psLevel3Sep) {
      writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n",
		 font->getID()->num, font->getID()->gen, psName,
		 font->getWMode());
    } else {
      writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n",
		 font->getID()->num, font->getID()->gen, psName,
		 font->getWMode());
    }
  } else {
    writePSFmt("/F{0:d}_{1:d} /{2:t} {3:.6g} {4:.6g}\n",
	       font->getID()->num, font->getID()->gen, psName, xs, ys);
    for (i = 0; i < 256; i += 8) {
      writePS((char *)((i == 0) ? "[ " : "  "));
      for (j = 0; j < 8; ++j) {
	if (font->getType() == fontTrueType &&
	    !subst &&
	    !((Gfx8BitFont *)font)->getHasEncoding()) {
	  sprintf(buf, "c%02x", i+j);
	  charName = buf;
	} else {
	  charName = ((Gfx8BitFont *)font)->getCharName(i+j);
	  // this is a kludge for broken PDF files that encode char 32
	  // as .notdef
	  if (i+j == 32 && charName && !strcmp(charName, ".notdef")) {
	    charName = "space";
	  }
	}
	writePS("/");
	writePSName(charName ? charName : (char *)".notdef");
	// the empty name is legal in PDF and PostScript, but PostScript
	// uses a double-slash (//...) for "immediately evaluated names",
	// so we need to add a space character here
	if (charName && !charName[0]) {
	  writePS(" ");
	}
      }
      writePS((i == 256-8) ? (char *)"]\n" : (char *)"\n");
    }
    writePS("pdfMakeFont\n");
  }

  delete psName;
}


void Gfx::opSetCharSpacing(Object args[], int numArgs) {
  state->setCharSpace(args[0].getNum());
  out->updateCharSpace(state);
}


DeviceNRecoder::DeviceNRecoder(Stream *strA, int widthA, int heightA,
			       GfxImageColorMap *colorMapA):
    FilterStream(strA) {
  width = widthA;
  height = heightA;
  colorMap = colorMapA;
  imgStr = NULL;
  pixelIdx = 0;
  bufIdx = gfxColorMaxComps;
  bufSize = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
              getAlt()->getNComps();
  func = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
           getTintTransformFunc();
}


SplashBitmap *getBitmap() { return bitmap; }


void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
				   int width, int height, GBool invert,
				   GBool interpolate, GBool inlineImg) {

  /* FIXME: Doesn't the image mask support any colorspace? */
  cairo_set_source (cairo, fill_pattern);

  /* work around a cairo bug when scaling 1x1 surfaces */
  if (width == 1 && height == 1) {
    ImageStream *imgStr;
    Guchar pix;
    int invert_bit;

    imgStr = new ImageStream(str, width, 1, 1);
    imgStr->reset();
    imgStr->getPixel(&pix);
    imgStr->close();
    delete imgStr;

    invert_bit = invert ? 1 : 0;
    if (pix ^ invert_bit)
      return;

    cairo_save (cairo);
    cairo_rectangle (cairo, 0., 0., width, height);
    cairo_fill (cairo);
    cairo_restore (cairo);
    if (cairo_shape) {
      cairo_save (cairo_shape);
      cairo_rectangle (cairo_shape, 0., 0., width, height);
      cairo_fill (cairo_shape);
      cairo_restore (cairo_shape);
    }
    return;
  }

  if (state->getFillColorSpace()->getMode() == csPattern)
    cairo_push_group_with_content (cairo, CAIRO_CONTENT_ALPHA);

  /* shape is 1.0 for painted areas, 0.0 for unpainted ones */

  cairo_matrix_t matrix;
  cairo_get_matrix (cairo, &matrix);
  //XXX: it is possible that we should only do sub pixel positioning if
  // we are rendering fonts */
  if (!printing && prescaleImages && matrix.xy == 0.0 && matrix.yx == 0.0) {
    drawImageMaskPrescaled(state, ref, str, width, height, invert, interpolate, inlineImg);
  } else {
    drawImageMaskRegular(state, ref, str, width, height, invert, interpolate, inlineImg);
  }

  if (state->getFillColorSpace()->getMode() == csPattern) {
    if (mask)
      cairo_pattern_destroy (mask);
    mask = cairo_pop_group (cairo);
  }
}


static inline GBool isSameGfxColor(const GfxColor &colorA, const GfxColor &colorB, Guint nComps, double delta) {
  for (Guint k = 0; k < nComps; ++k) {
    if (abs(colorA.c[k] - colorB.c[k]) > delta) {
      return false;
    }
  }
  return true;
}


void Gfx::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) {
  if (!state->isCurPt()) {
    //error(getPos(), "No path in closepath/stroke");
    return;
  }
  state->closePath();
  if (state->isPath() && !contentIsHidden()) {
    if (state->getStrokeColorSpace()->getMode() == csPattern) {
      doPatternStroke();
    } else {
      out->stroke(state);
    }
  }
  doEndPath();
}


PSOutCustomColor::~PSOutCustomColor() {
  delete name;
}


void PSOutputDev::restoreState(GfxState *state) {
  writePS("Q\n");
  --numSaves;
}


virtual GBool supportTextCSPattern(GfxState * /*state*/) { return gFalse; }


void PSOutputDev::updateTransfer(GfxState *state) {
  Function **funcs;
  int i;

  funcs = state->getTransfer();
  if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) {
    if (level >= psLevel2) {
      for (i = 0; i < 4; ++i) {
	cvtFunction(funcs[i]);
      }
      writePS("setcolortransfer\n");
    } else {
      cvtFunction(funcs[3]);
      writePS("settransfer\n");
    }
  } else if (funcs[0]) {
    cvtFunction(funcs[0]);
    writePS("settransfer\n");
  } else {
    writePS("{} settransfer\n");
  }
}


void PreScanOutputDev::setSoftMask(GfxState * /*state*/, double * /*bbox*/, GBool /*alpha*/,
			   Function * /*transferFunc*/, GfxColor * /*backdropColor*/)
{
  transparency = gTrue;
}


static void bubbleSort(double array[])
{
  for (int j = 0; j < 3; ++j) {
    int kk = j;
    for (int k = j + 1; k < 4; ++k) {
      if (array[k] < array[kk]) {
        kk = k;
      }
    }
    double tmp = array[j];
    array[j] = array[kk];
    array[kk] = tmp;
  }
}


virtual void updateLineCap(GfxState * /*state*/) {}


virtual void eoClip(GfxState * /*state*/) {}


GBool CairoOutputDev::axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading)
{
  return (shading->getExtend0() == shading->getExtend1());
}


  virtual GBool axialShadedSupportExtend(GfxState * /*state*/, GfxAxialShading * /*shading*/)
    { return gFalse; }


void Gfx::popStateGuard() {
    while (stackHeight > bottomGuard() && state->hasSaves())
	restoreState();
    stateGuards.pop_back();
}


GfxResources::~GfxResources() {
  if (fonts) {
    delete fonts;
  }
  xObjDict.free();
  colorSpaceDict.free();
  patternDict.free();
  shadingDict.free();
  gStateDict.free();
  propertiesDict.free();
}


  void setImageDrawDecideCbk(GBool (*cbk)(int img_id, void *data),
			     void *data) { imgDrawCbk = cbk; imgDrawCbkData = data; }


void PSOutputDev::endTextObject(GfxState *state) {
  if (haveCSPattern) {
    if (haveTextClip) {
      writePS("Tclip*\n");
      haveTextClip = gFalse;
      if (state->getFillColorSpace()->getMode() != csPattern) {
        double cxMin, cyMin, cxMax, cyMax;
        state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
        writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n",
                   cxMin, cyMin,
                   cxMax, cyMax);
        writePS("f*\n");
        restoreState(state);
        updateFillColor(state);
      }
    }
    haveCSPattern = gFalse;
  } else if (haveTextClip) {
    writePS("Tclip\n");
    haveTextClip = gFalse;
  }
}


virtual GBool needNonText() { return gTrue; }


void Gfx::opTextMove(Object args[], int numArgs) {
  double tx, ty;

  tx = state->getLineX() + args[0].getNum();
  ty = state->getLineY() + args[1].getNum();
  state->textMoveTo(tx, ty);
  out->updateTextPos(state);
}


void PSOutputDev::fill(GfxState *state) {
  doPath(state->getPath());
  writePS("f\n");
}


void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id,
					    GooString *psName) {
  char *fontBuf;
  int fontLen;
  FoFiType1C *ffT1C;
  int i;

  // check if font is already embedded
  for (i = 0; i < fontFileIDLen; ++i) {
    if (fontFileIDs[i].num == id->num &&
	fontFileIDs[i].gen == id->gen)
      return;
  }

  // add entry to fontFileIDs list
  if (fontFileIDLen >= fontFileIDSize) {
    fontFileIDSize += 64;
    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
  }
  fontFileIDs[fontFileIDLen++] = *id;

  // beginning comment
  writePSFmt("%%BeginResource: font {0:t}\n", psName);
  embFontList->append("%%+ font ");
  embFontList->append(psName->getCString());
  embFontList->append("\n");

  // convert it to a Type 0 font
  fontBuf = font->readEmbFontFile(xref, &fontLen);
  if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
    if (globalParams->getPSLevel() >= psLevel3) {
      // Level 3: use a CID font
      ffT1C->convertToCIDType0(psName->getCString(), outputFunc, outputStream);
    } else {
      // otherwise: use a non-CID composite font
      ffT1C->convertToType0(psName->getCString(), outputFunc, outputStream);
    }
    delete ffT1C;
  }
  gfree(fontBuf);

  // ending comment
  writePS("%%EndResource\n");
}


void CairoOutputDev::updateCTM(GfxState *state, double m11, double m12,
				double m21, double m22,
				double m31, double m32) {
  cairo_matrix_t matrix, invert_matrix;
  matrix.xx = m11;
  matrix.yx = m12;
  matrix.xy = m21;
  matrix.yy = m22;
  matrix.x0 = m31;
  matrix.y0 = m32;

  /* Make sure the matrix is invertible before setting it.
   * cairo will blow up if we give it a matrix that's not
   * invertible, so we need to check before passing it
   * to cairo_transform. Ignoring it is likely to give better
   * results than not rendering anything at all. See #14398
   *
   * Ideally, we could do the cairo_transform
   * and then check if anything went wrong and fix it then
   * instead of having to invert the matrix. */
  invert_matrix = matrix;
  if (cairo_matrix_invert(&invert_matrix)) {
    warning("matrix not invertible\n");
    return;
  }

  cairo_transform (cairo, &matrix);
  if (cairo_shape)
    cairo_transform (cairo_shape, &matrix);
  updateLineDash(state);
  updateLineJoin(state);
  updateLineCap(state);
  updateLineWidth(state);
}


GBool PSOutputDev::functionShadedFill(GfxState *state,
				     GfxFunctionShading *shading) {
  double x0, y0, x1, y1;
  double *mat;
  int i;

  if (level == psLevel2Sep || level == psLevel3Sep) {
    if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
      return gFalse;
    }
    processColors |= psProcessCMYK;
  }

  shading->getDomain(&x0, &y0, &x1, &y1);
  mat = shading->getMatrix();
  writePSFmt("/mat [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] def\n",
	     mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
  writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
  if (shading->getNFuncs() == 1) {
    writePS("/func ");
    cvtFunction(shading->getFunc(0));
    writePS("def\n");
  } else {
    writePS("/func {\n");
    for (i = 0; i < shading->getNFuncs(); ++i) {
      if (i < shading->getNFuncs() - 1) {
	writePS("2 copy\n");
      }
      cvtFunction(shading->getFunc(i));
      writePS("exec\n");
      if (i < shading->getNFuncs() - 1) {
	writePS("3 1 roll\n");
      }
    }
    writePS("} def\n");
  }
  writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} 0 funcSH\n", x0, y0, x1, y1);

  return gTrue;
}


void CairoOutputDev::eoClip(GfxState *state) {
  doPath (cairo, state, state->getPath());
  cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD);
  cairo_clip (cairo);
  LOG (printf ("clip-eo\n"));
  if (cairo_shape) {
    doPath (cairo_shape, state, state->getPath());
    cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_EVEN_ODD);
    cairo_clip (cairo_shape);
  }

}


void PSOutputDev::updateLineDash(GfxState *state) {
  double *dash;
  double start;
  int length, i;

  state->getLineDash(&dash, &length, &start);
  writePS("[");
  for (i = 0; i < length; ++i) {
    writePSFmt("{0:.6g}{1:w}",
	       dash[i] < 0 ? 0 : dash[i],
	       (i == length-1) ? 0 : 1);
  }
  writePSFmt("] {0:.6g} d\n", start);
}


Operator *Gfx::findOp(char *name) {
  int a, b, m, cmp;

  a = -1;
  b = numOps;
  // invariant: opTab[a] < name < opTab[b]
  while (b - a > 1) {
    m = (a + b) / 2;
    cmp = strcmp(opTab[m].name, name);
    if (cmp < 0)
      a = m;
    else if (cmp > 0)
      b = m;
    else
      a = b = m;
  }
  if (cmp != 0)
    return NULL;
  return &opTab[a];
}


double *getDefICTM() { return defICTM; }


void PSOutputDev::writePS(char *s) {
  if (t3String) {
    t3String->append(s);
  } else {
    (*outputFunc)(outputStream, s, strlen(s));
  }
}


void Gfx::opFillStroke(Object args[], int numArgs) {
  if (!state->isCurPt()) {
    //error(getPos(), "No path in fill/stroke");
    return;
  }
  if (state->isPath() && !contentIsHidden()) {
    if (state->getFillColorSpace()->getMode() == csPattern) {
      doPatternFill(gFalse);
    } else {
      out->fill(state);
    }
    if (state->getStrokeColorSpace()->getMode() == csPattern) {
      doPatternStroke();
    } else {
      out->stroke(state);
    }
  }
  doEndPath();
}


void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
					  int width, int height,
					  GfxImageColorMap *colorMap,
					  GBool interpolate,
					  Stream *maskStr,
					  int maskWidth, int maskHeight,
					  GBool maskInvert, GBool maskInterpolate)
{
  cairo_t *cr;
  cairo_surface_t *surface;
  double x1, y1, x2, y2;
  double *ctm;
  double mat[6];
  CairoImage *image;

  ctm = state->getCTM();

  mat[0] = ctm[0];
  mat[1] = ctm[1];
  mat[2] = -ctm[2];
  mat[3] = -ctm[3];
  mat[4] = ctm[2] + ctm[4];
  mat[5] = ctm[3] + ctm[5];
  x1 = mat[4];
  y1 = mat[5];
  x2 = x1 + width;
  y2 = y1 + height;

  image = new CairoImage (x1, y1, x2, y2);
  saveImage (image);

  if (imgDrawCbk && imgDrawCbk (numImages - 1, imgDrawCbkData)) {
    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
    cr = cairo_create (surface);
    setCairo (cr);
    cairo_translate (cr, 0, height);
    cairo_scale (cr, width, -height);

    CairoOutputDev::drawMaskedImage(state, ref, str, width, height, colorMap, interpolate,
				    maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
    image->setImage (surface);

    setCairo (NULL);
    cairo_surface_destroy (surface);
    cairo_destroy (cr);
  }
}


static inline int splashRound(SplashCoord x) {
  return (int)floor(x + 0.5);
}


void Gfx::opShFill(Object args[], int numArgs) {
  GfxShading *shading;
  GfxPath *savedPath;
  double xMin, yMin, xMax, yMax;

  if (!(shading = res->lookupShading(args[0].getName(), this))) {
    return;
  }

  // save current graphics state
  savedPath = state->getPath()->copy();
  saveState();

  // clip to bbox
  if (shading->getHasBBox()) {
    shading->getBBox(&xMin, &yMin, &xMax, &yMax);
    state->moveTo(xMin, yMin);
    state->lineTo(xMax, yMin);
    state->lineTo(xMax, yMax);
    state->lineTo(xMin, yMax);
    state->closePath();
    state->clip();
    out->clip(state);
    state->clearPath();
  }

  // set the color space
  state->setFillColorSpace(shading->getColorSpace()->copy());
  out->updateFillColorSpace(state);

#if 1 //~tmp: turn off anti-aliasing temporarily
  GBool vaa = out->getVectorAntialias();
  if (vaa) {
    out->setVectorAntialias(gFalse);
  }
#endif

  // do shading type-specific operations
  switch (shading->getType()) {
  case 1:
    doFunctionShFill((GfxFunctionShading *)shading);
    break;
  case 2:
    doAxialShFill((GfxAxialShading *)shading);
    break;
  case 3:
    doRadialShFill((GfxRadialShading *)shading);
    break;
  case 4:
  case 5:
    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
    break;
  case 6:
  case 7:
    doPatchMeshShFill((GfxPatchMeshShading *)shading);
    break;
  }

#if 1 //~tmp: turn off anti-aliasing temporarily
  if (vaa) {
    out->setVectorAntialias(gTrue);
  }
#endif

  // restore graphics state
  restoreState();
  state->setPath(savedPath);

  delete shading;
}


void PSOutputDev::clipToStrokePath(GfxState *state) {
  doPath(state->getPath());
  writePS("Ws\n");
}


virtual void updateFlatness(GfxState *state) { }


void DeviceNRecoder::reset() {
  imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
			   colorMap->getBits());
  imgStr->reset();
}


virtual void updateFillColorStop(GfxState * /*state*/, double /*offset*/) {}


void Gfx::opFill(Object args[], int numArgs) {
  if (!state->isCurPt()) {
    //error(getPos(), "No path in fill");
    return;
  }
  if (state->isPath() && !contentIsHidden()) {
    if (state->getFillColorSpace()->getMode() == csPattern) {
      doPatternFill(gFalse);
    } else {
      out->fill(state);
    }
  }
  doEndPath();
}


virtual void updateAlphaIsShape(GfxState * /*state*/) {}


GBool PreScanOutputDev::tilingPatternFill(GfxState *state, Catalog *catalog, Object *str,
					double *pmat, int paintType, Dict *resDict,
					double *mat, double *bbox,
					int x0, int y0, int x1, int y1,
					double xStep, double yStep) {
  PDFRectangle box;
  Gfx *gfx;
  box.x1 = bbox[0]; box.y1 = bbox[1];
  box.x2 = bbox[2]; box.y2 = bbox[3];
  gfx = new Gfx(xref, this, resDict, catalog, &box, NULL);
  gfx->display(str);
  delete gfx;
  return gTrue;
}


virtual void updateLineCap(GfxState *state) { }


static bool snippet_generate(struct snippet_context *ctx,
			     const unsigned char *data, size_t size)
{
	size_t i, count;
	struct snippet_data *target = &ctx->snippet;

	if (ctx->html2text != NULL) {
		buffer_set_used_size(ctx->plain_output, 0);
		mail_html2text_more(ctx->html2text, data, size,
				    ctx->plain_output);
		data = ctx->plain_output->data;
		size = ctx->plain_output->used;
	}

	/* message-decoder should feed us only valid and complete
	   UTF-8 input */

	for (i = 0; i < size; i += count) {
		count = 1;
		switch (ctx->state) {
		case SNIPPET_STATE_NEWLINE:
			if (data[i] == '>') {
				ctx->state = SNIPPET_STATE_QUOTED;
				i++;
				target = &ctx->quoted_snippet;
			} else {
				ctx->state = SNIPPET_STATE_NORMAL;
				target = &ctx->snippet;
			}
			/* fallthrough */
		case SNIPPET_STATE_NORMAL:
		case SNIPPET_STATE_QUOTED:
			snippet_add_content(ctx, target, CONST_PTR_OFFSET(data, i),
					    size-i, &count);
			/* break here if we have enough non-quoted data,
			   quoted data does not need to break here as it's
			   only used if the actual snippet is left empty. */
			if (ctx->snippet.chars_left == 0)
				return FALSE;
			break;
		}
	}
	return TRUE;
}


ResetAnsiState(p)
struct win *p;
{
  p->w_state = LIT;
  p->w_StringType = NONE;
}


strlen_onscreen(unsigned char *c, unsigned char *end)
{
  int len = 0;
  while (*c && (!end || c < end))
    {
      int v, dec = 0;
      do
	{
	  v = FromUtf8(*c++, &dec);
	  if (v == -2)
	    c--;
	}
      while (v < 0 && (!end || c < end));
      if (!utf8_iscomb(v))
        {
          if (utf8_isdouble(v))
            len++;
          len++;
        }
    }

  return len;
}


ResizeRegions(arg, flags)
char *arg;
int flags;
{
  struct canvas *cv;
  int diff, l;
  int gflag = 0, abs = 0, percent = 0;
  int orient = 0;

  ASSERT(display);
  if (!*arg)
    return;
  if (D_forecv->c_slorient == SLICE_UNKN)
    {
      Msg(0, "resize: need more than one region");
      return;
    }
  gflag = flags & RESIZE_FLAG_L ? 0 : 1;
  orient |= flags & RESIZE_FLAG_H ? SLICE_HORI : 0;
  orient |= flags & RESIZE_FLAG_V ? SLICE_VERT : 0;
  if (orient == 0)
    orient = D_forecv->c_slorient;
  l = strlen(arg);
  if (*arg == '=')
    {
      /* make all regions the same height */
      struct canvas *cv = gflag ? &D_canvas : D_forecv->c_slback;
      if (cv->c_slperp->c_slorient & orient)
	EqualizeCanvas(cv->c_slperp, gflag);
      /* can't use cv->c_slorient directly as it can be D_canvas */
      if ((cv->c_slperp->c_slorient ^ (SLICE_HORI ^ SLICE_VERT)) & orient)
        {
	  if (cv->c_slback)
	    {
	      cv = cv->c_slback;
	      EqualizeCanvas(cv->c_slperp, gflag);
	    }
	  else
	   EqualizeCanvas(cv, gflag);
        }
      ResizeCanvas(cv);
      RecreateCanvasChain();
      RethinkDisplayViewports();
      ResizeLayersToCanvases();
      return;
    }
  if (!strcmp(arg, "min") || !strcmp(arg, "0"))
    {
      abs = 2;
      diff = 0;
    }
  else if (!strcmp(arg, "max") || !strcmp(arg, "_"))
    {
      abs = 2;
      diff = 1;
    }
  else
    {
      if (l > 0 && arg[l - 1] == '%')
	percent = 1000;
      if (*arg == '+')
	diff = atoi(arg + 1);
      else if (*arg == '-')
	diff = -atoi(arg + 1);
      else
	{
	  diff = atoi(arg);		/* +1 because of caption line */
	  if (diff < 0)
	    diff = 0;
	  abs = diff == 0 ? 2 : 1;
	}
    }
  if (!abs && !diff)
    return;
  if (percent)
    diff = diff * percent / 100;
  cv = D_forecv;
  if (cv->c_slorient & orient)
    ChangeCanvasSize(cv, abs, diff, gflag, percent);
  if (cv->c_slback->c_slorient & orient)
    ChangeCanvasSize(cv->c_slback, abs, diff, gflag, percent);

  ResizeCanvas(&D_canvas);
  RecreateCanvasChain();
  RethinkDisplayViewports();
  ResizeLayersToCanvases();
  return;

#if 0

  if (siz + diff < 1)
    diff = 1 - siz;
  if (siz + diff > dsize - (nreg - 1) * 2 - 1)
    diff = dsize - (nreg - 1) * 2 - 1 - siz;
  if (diff == 0 || siz + diff < 1)
    return;

  if (diff < 0)
    {
      if (D_forecv->c_next)
	{
	  D_forecv->c_ye += diff;
	  D_forecv->c_next->c_ys += diff;
	  D_forecv->c_next->c_yoff += diff;
	}
      else
	{
	  for (cv = D_cvlist; cv; cv = cv->c_next)
	    if (cv->c_next == D_forecv)
	      break;
	  ASSERT(cv);
	  cv->c_ye -= diff;
	  D_forecv->c_ys -= diff;
	  D_forecv->c_yoff -= diff;
	}
    }
  else
    {
      int s, i = 0, found = 0, di = diff, d2;
      s = dsize - (nreg - 1) * 2 - 1 - siz;
      for (cv = D_cvlist; cv; i = cv->c_ye + 2, cv = cv->c_next)
	{
	  if (cv == D_forecv)
	    {
	      cv->c_ye = i + (cv->c_ye - cv->c_ys) + diff;
	      cv->c_yoff -= cv->c_ys - i;
	      cv->c_ys = i;
	      found = 1;
	      continue;
	    }
	  s -= cv->c_ye - cv->c_ys;
	  if (!found)
	    {
	      if (s >= di)
		continue;
	      d2 = di - s;
	    }
	  else
	    d2 = di > cv->c_ye - cv->c_ys ? cv->c_ye - cv->c_ys : di;
	  di -= d2;
	  cv->c_ye = i + (cv->c_ye - cv->c_ys) - d2;
	  cv->c_yoff -= cv->c_ys - i;
	  cv->c_ys = i;
        }
    }
  RethinkDisplayViewports();
  ResizeLayersToCanvases();
#endif
}


RemoveStatus()
{
  struct display *olddisplay;
  struct layer *oldflayer;
  int where;

  if (!display)
    return;
  if (!(where = D_status))
    return;

  debug("RemoveStatus\n");
  if (D_status_obuffree >= 0)
    {
      D_obuflen = D_status_obuflen;
      D_obuffree = D_status_obuffree;
      D_status_obuffree = -1;
    }
  D_status = 0;
  D_status_obufpos = 0;
  D_status_bell = 0;
  evdeq(&D_statusev);
  olddisplay = display;
  oldflayer = flayer;
  if (where == STATUS_ON_WIN)
    {
      if (captionalways || (D_canvas.c_slperp && D_canvas.c_slperp->c_slnext))
	{
	  GotoPos(0, STATLINE);
	  RefreshLine(STATLINE, 0, D_status_len - 1, 0);
	  GotoPos(D_status_lastx, D_status_lasty);
	}
    }
  else
    RefreshHStatus();
  flayer = D_forecv ? D_forecv->c_layer : 0;
  if (flayer)
    LaySetCursor();
  display = olddisplay;
  flayer = oldflayer;
}


FinitTerm()
{
  ASSERT(display);
#ifdef BLANKER_PRG
  KillBlanker();
#endif
  if (D_tcinited)
    {
      ResizeDisplay(D_defwidth, D_defheight);
      InsertMode(0);
      ChangeScrollRegion(0, D_height - 1);
      KeypadMode(0);
      CursorkeysMode(0);
      CursorVisibility(0);
      if (D_mousetrack)
	D_mousetrack = 0;
      MouseMode(0);
      ExtMouseMode(0);
      SetRendition(&mchar_null);
      SetFlow(FLOW_NOW);
#ifdef MAPKEYS
      AddCStr(D_KE);
      AddCStr(D_CCE);
#endif
      if (D_hstatus)
	ShowHStatus((char *)0);
#ifdef RXVT_OSC
      ClearAllXtermOSC();
#endif
      D_x = D_y = -1;
      GotoPos(0, D_height - 1);
      AddChar('\r');
      AddChar('\n');
      AddCStr(D_TE);
    }
  Flush(3);
}


ClearLineRegion(from, to)
int from, to;
{
  register int y = curr->w_y;
  LClearArea(&curr->w_layer, from, y, to, y, CURR_BCE, 1);
  MClearArea(curr, from, y, to, y, CURR_BCE);
  RestorePosRendition();
}


ins_reg_fn(buf, len, data)
char *buf;
int len;
char *data;	/* dummy */
{
  struct plop *pp = plop_tab + (int)(unsigned char)*buf;

  if (len)
    {
      *buf = 0;
      return;
    }
  if (!fore)
    return;	/* Input() should not call us w/o fore, but you never know... */
  if (*buf == '.')
    Msg(0, "ins_reg_fn: Warning: pasting real register '.'!");
  if (pp->buf)
    {
      MakePaster(&fore->w_paster, pp->buf, pp->len, 0);
      return;
    }
  Msg(0, "Empty register.");
}


Activate(norefresh)
int norefresh;
{
  debug1("Activate(%d)\n", norefresh);
  if (display == 0)
    return;
  if (D_status)
    {
      Msg(0, "%s", "");	/* wait till mintime (keep gcc quiet) */
      RemoveStatus();
    }

  if (MayResizeLayer(D_forecv->c_layer))
    ResizeLayer(D_forecv->c_layer, D_forecv->c_xe - D_forecv->c_xs + 1, D_forecv->c_ye - D_forecv->c_ys + 1, display);

  fore = D_fore;
  if (fore)
    {
      /* XXX ? */
      if (fore->w_monitor != MON_OFF)
	fore->w_monitor = MON_ON;
      fore->w_bell = BELL_ON;
      WindowChanged(fore, 'f');

#if 0
      if (ResizeDisplay(fore->w_width, fore->w_height))
	{
	  debug2("Cannot resize from (%d,%d)", D_width, D_height);
	  debug2(" to (%d,%d) -> resize window\n", fore->w_width, fore->w_height);
	  DoResize(D_width, D_height);
	}
#endif
    }
  Redisplay(norefresh + all_norefresh);
}


MInsChar(p, c, x, y)
struct win *p;
struct mchar *c;
int x, y;
{
  int n;
  struct mline *ml;

  ASSERT(x >= 0 && x < p->w_width);
  MFixLine(p, y, c);
  ml = p->w_mlines + y;
  n = p->w_width - x - 1;
  MKillDwRight(p, ml, x);
  if (n > 0)
    {
      MKillDwRight(p, ml, p->w_width - 1);
      bcopy_mline(ml, x, x + 1, n);
    }
  copy_mchar2mline(c, ml, x);
#ifdef DW_CHARS
  if (c->mbcs)
    {
      if (--n > 0)
        {
          MKillDwRight(p, ml, p->w_width - 1);
	  bcopy_mline(ml, x + 1, x + 2, n);
	}
      copy_mchar2mline(c, ml, x + 1);
      ml->image[x + 1] = c->mbcs;
# ifdef UTF8
      if (p->w_encoding != UTF8)
	ml->font[x + 1] |= 0x80;
      else if (p->w_encoding == UTF8 && c->mbcs)
	{
	  ml->font[x + 1] = c->mbcs;
	  ml->fontx[x + 1] = 0;
	}
# else
      ml->font[x + 1] |= 0x80;
# endif
    }
#endif
}


CheckArgNum(nr, args)
int nr;
char **args;
{
  int i, n;
  static char *argss[] = {"no", "one", "two", "three", "four", "OOPS"};
  static char *orformat[] =
    {
      "%s: %s: %s argument%s required",
      "%s: %s: %s or %s argument%s required",
      "%s: %s: %s, %s or %s argument%s required",
      "%s: %s: %s, %s, %s or %s argument%s required"
    };

  n = comms[nr].flags & ARGS_MASK;
  for (i = 0; args[i]; i++)
    ;
  if (comms[nr].flags & ARGS_ORMORE)
    {
      if (i < n)
	{
	  Msg(0, "%s: %s: at least %s argument%s required",
	      rc_name, comms[nr].name, argss[n], n != 1 ? "s" : "");
	  return -1;
	}
    }
  else if ((comms[nr].flags & ARGS_PLUS1) &&
           (comms[nr].flags & ARGS_PLUS2) &&
	   (comms[nr].flags & ARGS_PLUS3))
    {
      if (i != n && i != n + 1 && i != n + 2 && i != n + 3)
        {
	  Msg(0, orformat[3], rc_name, comms[nr].name, argss[n],
	      argss[n + 1], argss[n + 2], argss[n + 3], "");
	  return -1;
	}
    }
  else if ((comms[nr].flags & ARGS_PLUS1) &&
           (comms[nr].flags & ARGS_PLUS2))
    {
      if (i != n && i != n + 1 && i != n + 2)
	{
	  Msg(0, orformat[2], rc_name, comms[nr].name, argss[n],
	      argss[n + 1], argss[n + 2], "");
          return -1;
	}
    }
  else if ((comms[nr].flags & ARGS_PLUS1) &&
           (comms[nr].flags & ARGS_PLUS3))
    {
      if (i != n && i != n + 1 && i != n + 3)
        {
	  Msg(0, orformat[2], rc_name, comms[nr].name, argss[n],
	      argss[n + 1], argss[n + 3], "");
	  return -1;
	}
    }
  else if ((comms[nr].flags & ARGS_PLUS2) &&
           (comms[nr].flags & ARGS_PLUS3))
    {
      if (i != n && i != n + 2 && i != n + 3)
        {
	  Msg(0, orformat[2], rc_name, comms[nr].name, argss[n],
	      argss[n + 2], argss[n + 3], "");
	  return -1;
	}
    }
  else if (comms[nr].flags & ARGS_PLUS1)
    {
      if (i != n && i != n + 1)
        {
	  Msg(0, orformat[1], rc_name, comms[nr].name, argss[n],
	      argss[n + 1], n != 0 ? "s" : "");
	  return -1;
	}
    }
  else if (comms[nr].flags & ARGS_PLUS2)
    {
      if (i != n && i != n + 2)
        {
	  Msg(0, orformat[1], rc_name, comms[nr].name, argss[n],
	      argss[n + 2], "s");
	  return -1;
	}
    }
  else if (comms[nr].flags & ARGS_PLUS3)
    {
      if (i != n && i != n + 3)
        {
	  Msg(0, orformat[1], rc_name, comms[nr].name, argss[n],
	      argss[n + 3], "");
	  return -1;
	}
    }
  else if (i != n)
    {
      Msg(0, orformat[0], rc_name, comms[nr].name, argss[n], n != 1 ? "s" : "");
      return -1;
    }
  return i;
}


GotoPos(x2, y2)
int x2, y2;
{
  register int dy, dx, x1, y1;
  register int costx, costy;
  register int m;
  register char *s;
  int CMcost;
  enum move_t xm = M_NONE, ym = M_NONE;

  if (!display)
    return;

  x1 = D_x;
  y1 = D_y;

  if (x1 == D_width)
    {
      if (D_CLP && D_AM)
        x1 = -1;	/* don't know how the terminal treats this */
      else
        x1--;
    }
  if (x2 == D_width)
    x2--;
  dx = x2 - x1;
  dy = y2 - y1;
  if (dy == 0 && dx == 0)
    return;
  debug2("GotoPos (%d,%d)", x1, y1);
  debug2(" -> (%d,%d)\n", x2, y2);
  if (!D_MS)		/* Safe to move ? */
    SetRendition(&mchar_null);
  if (y1 < 0			/* don't know the y position */
      || (y2 > D_bot && y1 <= D_bot)	/* have to cross border */
      || (y2 < D_top && y1 >= D_top))	/* of scrollregion ?    */
    {
    DoCM:
      if (D_HO && !x2 && !y2)
        AddCStr(D_HO);
      else
        AddCStr(tgoto(D_CM, x2, y2));
      D_x = x2;
      D_y = y2;
      return;
    }

  /* some scrollregion implementations don't allow movements
   * away from the region. sigh.
   */
  if ((y1 > D_bot && y2 > y1) || (y1 < D_top && y2 < y1))
    goto DoCM;

  /* Calculate CMcost */
  if (D_HO && !x2 && !y2)
    s = D_HO;
  else
    s = tgoto(D_CM, x2, y2);
  CMcost = CalcCost(s);

  /* Calculate the cost to move the cursor to the right x position */
  costx = EXPENSIVE;
  if (x1 >= 0)	/* relativ x positioning only if we know where we are */
    {
      if (dx > 0)
	{
	  if (D_CRI && (dx > 1 || !D_ND))
	    {
	      costx = CalcCost(tgoto(D_CRI, 0, dx));
	      xm = M_CRI;
	    }
	  if ((m = D_NDcost * dx) < costx)
	    {
	      costx = m;
	      xm = M_RI;
	    }
	  /* Speedup: dx <= LayRewrite() */
	  if (dx < costx && (m = CallRewrite(y1, x1, x2 - 1, 0)) < costx)
	    {
	      costx = m;
	      xm = M_RW;
	    }
	}
      else if (dx < 0)
	{
	  if (D_CLE && (dx < -1 || !D_BC))
	    {
	      costx = CalcCost(tgoto(D_CLE, 0, -dx));
	      xm = M_CLE;
	    }
	  if ((m = -dx * D_LEcost) < costx)
	    {
	      costx = m;
	      xm = M_LE;
	    }
	}
      else
	costx = 0;
    }
  /* Speedup: LayRewrite() >= x2 */
  if (x2 + D_CRcost < costx && (m = (x2 ? CallRewrite(y1, 0, x2 - 1, 0) : 0) + D_CRcost) < costx)
    {
      costx = m;
      xm = M_CR;
    }

  /* Check if it is already cheaper to do CM */
  if (costx >= CMcost)
    goto DoCM;

  /* Calculate the cost to move the cursor to the right y position */
  costy = EXPENSIVE;
  if (dy > 0)
    {
      if (D_CDO && dy > 1)	/* DO & NL are always != 0 */
	{
	  costy = CalcCost(tgoto(D_CDO, 0, dy));
	  ym = M_CDO;
	}
      if ((m = dy * ((x2 == 0) ? D_NLcost : D_DOcost)) < costy)
	{
	  costy = m;
	  ym = M_DO;
	}
    }
  else if (dy < 0)
    {
      if (D_CUP && (dy < -1 || !D_UP))
	{
	  costy = CalcCost(tgoto(D_CUP, 0, -dy));
	  ym = M_CUP;
	}
      if ((m = -dy * D_UPcost) < costy)
	{
	  costy = m;
	  ym = M_UP;
	}
    }
  else
    costy = 0;

  /* Finally check if it is cheaper to do CM */
  if (costx + costy >= CMcost)
    goto DoCM;

  switch (xm)
    {
    case M_LE:
      while (dx++ < 0)
	AddCStr(D_BC);
      break;
    case M_CLE:
      AddCStr2(D_CLE, -dx);
      break;
    case M_RI:
      while (dx-- > 0)
	AddCStr(D_ND);
      break;
    case M_CRI:
      AddCStr2(D_CRI, dx);
      break;
    case M_CR:
      AddCStr(D_CR);
      D_x = 0;
      x1 = 0;
      /* FALLTHROUGH */
    case M_RW:
      if (x1 < x2)
	(void) CallRewrite(y1, x1, x2 - 1, 1);
      break;
    default:
      break;
    }

  switch (ym)
    {
    case M_UP:
      while (dy++ < 0)
	AddCStr(D_UP);
      break;
    case M_CUP:
      AddCStr2(D_CUP, -dy);
      break;
    case M_DO:
      s =  (x2 == 0) ? D_NL : D_DO;
      while (dy-- > 0)
	AddCStr(s);
      break;
    case M_CDO:
      AddCStr2(D_CDO, dy);
      break;
    default:
      break;
    }
  D_x = x2;
  D_y = y2;
}


RestoreCursor(cursor)
struct cursor *cursor;
{
  if (!cursor->on)
    return;
  LGotoPos(&curr->w_layer, cursor->x, cursor->y);
  curr->w_x = cursor->x;
  curr->w_y = cursor->y;
  curr->w_rend = cursor->Rend;
#ifdef FONT
  bcopy((char *) cursor->Charsets, (char *) curr->w_charsets,
	4 * sizeof(int));
  curr->w_Charset = cursor->Charset;
  curr->w_CharsetR = cursor->CharsetR;
  curr->w_ss = 0;
  curr->w_FontL = curr->w_charsets[curr->w_Charset];
  curr->w_FontR = curr->w_charsets[curr->w_CharsetR];
#endif
  LSetRendition(&curr->w_layer, &curr->w_rend);
}


BackSpace()
{
  if (curr->w_x > 0)
    {
      curr->w_x--;
    }
  else if (curr->w_wrap && curr->w_y > 0)
    {
      curr->w_x = cols - 1;
      curr->w_y--;
    }
  LGotoPos(&curr->w_layer, curr->w_x, curr->w_y);
}


CursorVisibility(v)
int v;
{
  if (display && D_curvis != v)
    {
      if (D_curvis)
	AddCStr(D_VE);		/* do this always, just to be safe */
      D_curvis = 0;
      if (v == -1 && D_VI)
	AddCStr(D_VI);
      else if (v == 1 && D_VS)
	AddCStr(D_VS);
      else
	return;
      D_curvis = v;
    }
}


DefRestore()
{
  LAY_DISPLAYS(flayer, InsertMode(0));
  /* ChangeScrollRegion(0, D_height - 1); */
  LKeypadMode(flayer, 0);
  LCursorkeysMode(flayer, 0);
  LCursorVisibility(flayer, 0);
  LMouseMode(flayer, 0);
  LSetRendition(flayer, &mchar_null);
  LSetFlow(flayer, nwin_default.flowflag & FLOW_NOW);
}


ClearAllXtermOSC()
{
  int i;
  for (i = 4; i >= 0; i--)
    SetXtermOSC(i, 0);
  if (D_xtermosc[0])
    AddStr("\033[23;" WT_FLAG "t");	/* unstack titles (xterm patch #251) */
}


DoAddChar(c)
int c;
{
  /* this is for ESC-sequences only (AddChar is a macro) */
  AddChar(c);
  return c;
}


MoreWindows()
{
  char *m = "No other window.";
  if (windows && (fore == 0 || windows->w_next))
    return 1;
  if (fore == 0)
    {
      Msg(0, "No window available");
      return 0;
    }
  Msg(0, m, fore->w_number);	/* other arg for nethack */
  return 0;
}


MakeDisplay(uname, utty, term, fd, pid, Mode)
char *uname, *utty, *term;
int fd, pid;
struct mode *Mode;
{
  struct acluser **u;
  struct baud_values *b;

  if (!*(u = FindUserPtr(uname)) && UserAdd(uname, (char *)0, u))
    return 0;	/* could not find or add user */

#ifdef MULTI
  if ((display = (struct display *)calloc(1, sizeof(*display))) == 0)
    return 0;
#else
  if (displays)
    return 0;
  bzero((char *)&TheDisplay, sizeof(TheDisplay));
  display = &TheDisplay;
#endif
  display->d_next = displays;
  displays = display;
  D_flow = 1;
  D_nonblock = defnonblock;
  D_userfd = fd;
  D_readev.fd = D_writeev.fd = fd;
  D_readev.type  = EV_READ;
  D_writeev.type = EV_WRITE;
  D_readev.data = D_writeev.data = (char *)display;
  D_readev.handler  = disp_readev_fn;
  D_writeev.handler = disp_writeev_fn;
  evenq(&D_readev);
  D_writeev.condpos = &D_obuflen;
  D_writeev.condneg = &D_obuffree;
  evenq(&D_writeev);
  D_statusev.type = EV_TIMEOUT;
  D_statusev.data = (char *)display;
  D_statusev.handler = disp_status_fn;
  D_hstatusev.type = EV_TIMEOUT;
  D_hstatusev.data = (char *)display;
  D_hstatusev.handler = disp_hstatus_fn;
  D_blockedev.type = EV_TIMEOUT;
  D_blockedev.data = (char *)display;
  D_blockedev.handler = disp_blocked_fn;
  D_blockedev.condpos = &D_obuffree;
  D_blockedev.condneg = &D_obuflenmax;
  D_hstatusev.handler = disp_hstatus_fn;
#ifdef MAPKEYS
  D_mapev.type = EV_TIMEOUT;
  D_mapev.data = (char *)display;
  D_mapev.handler = disp_map_fn;
#endif
  D_idleev.type = EV_TIMEOUT;
  D_idleev.data = (char *)display;
  D_idleev.handler = disp_idle_fn;
#ifdef BLANKER_PRG
  D_blankerev.type = EV_READ;
  D_blankerev.data = (char *)display;
  D_blankerev.handler = disp_blanker_fn;
  D_blankerev.fd = -1;
#endif
  D_OldMode = *Mode;
  D_status_obuffree = -1;
  Resize_obuf();  /* Allocate memory for buffer */
  D_obufmax = defobuflimit;
  D_obuflenmax = D_obuflen - D_obufmax;
#ifdef AUTO_NUKE
  D_auto_nuke = defautonuke;
#endif
  D_obufp = D_obuf;
  D_printfd = -1;
  D_userpid = pid;

#ifdef POSIX
  if ((b = lookup_baud((int)cfgetospeed(&D_OldMode.tio))))
    D_dospeed = b->idx;
#else
# ifdef TERMIO
  if ((b = lookup_baud(D_OldMode.tio.c_cflag & CBAUD)))
    D_dospeed = b->idx;
# else
  D_dospeed = (short)D_OldMode.m_ttyb.sg_ospeed;
# endif
#endif
  debug1("New displays ospeed = %d\n", D_dospeed);

  strncpy(D_usertty, utty, sizeof(D_usertty) - 1);
  D_usertty[sizeof(D_usertty) - 1] = 0;
  strncpy(D_termname, term, MAXTERMLEN);
  D_termname[MAXTERMLEN] = 0;
  D_user = *u;
  D_processinput = ProcessInput;
  D_mousetrack = defmousetrack;
  return display;
}


RAW_PUTCHAR(c)
int c;
{
  ASSERT(display);

#ifdef FONT
# ifdef UTF8
  if (D_encoding == UTF8)
    {
      c = (c & 255) | (unsigned char)D_rend.font << 8 | (unsigned char)D_rend.fontx << 16;
#  ifdef DW_CHARS
      if (D_mbcs)
	{
	  c = D_mbcs;
	  if (D_x == D_width)
	    D_x += D_AM ? 1 : -1;
	  D_mbcs = 0;
	}
      else if (utf8_isdouble(c))
	{
	  D_mbcs = c;
	  D_x++;
	  return;
	}
#  endif
      if (c < 32)
	{
	  AddCStr2(D_CS0, '0');
	  AddChar(c + 0x5f);
	  AddCStr(D_CE0);
	  goto addedutf8;
	}
      if (c < 0x80)
	{
	  if (D_xtable && D_xtable[(int)(unsigned char)D_rend.font] && D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c])
	    AddStr(D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c]);
	  else
	    AddChar(c);
	}
      else
	AddUtf8(c);
      goto addedutf8;
    }
# endif
# ifdef DW_CHARS
  if (is_dw_font(D_rend.font))
    {
      int t = c;
      if (D_mbcs == 0)
	{
	  D_mbcs = c;
	  D_x++;
	  return;
	}
      D_x--;
      if (D_x == D_width - 1)
	D_x += D_AM ? 1 : -1;
      c = D_mbcs;
      D_mbcs = t;
    }
# endif
# if defined(ENCODINGS) && defined(DW_CHARS)
  if (D_encoding)
    c = PrepareEncodedChar(c);
# endif
# ifdef DW_CHARS
  kanjiloop:
# endif
  if (D_xtable && D_xtable[(int)(unsigned char)D_rend.font] && D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c])
    AddStr(D_xtable[(int)(unsigned char)D_rend.font][(int)(unsigned char)c]);
  else
    AddChar(D_rend.font != '0' ? c : D_c0_tab[(int)(unsigned char)c]);
#else /* FONT */
    AddChar(c);
#endif /* FONT */

#ifdef UTF8
addedutf8:
#endif
  if (++D_x >= D_width)
    {
      if (D_AM == 0)
        D_x = D_width - 1;
      else if (!D_CLP || D_x > D_width)
	{
	  D_x -= D_width;
	  if (D_y < D_height-1 && D_y != D_bot)
	    D_y++;
	}
    }
#ifdef DW_CHARS
  if (D_mbcs)
    {
      c = D_mbcs;
      D_mbcs = 0;
      goto kanjiloop;
    }
#endif
}


PUTCHAR(c)
int c;
{
  ASSERT(display);
  if (D_insert && D_x < D_width - 1)
    InsertMode(0);
  RAW_PUTCHAR(c);
}


SetEscape(u, e, me)
struct acluser *u;
int e, me;
{
  if (u)
    {
      u->u_Esc = e;
      u->u_MetaEsc = me;
    }
  else
    {
      if (users)
	{
	  if (DefaultEsc >= 0)
	    ClearAction(&ktab[DefaultEsc]);
	  if (DefaultMetaEsc >= 0)
	    ClearAction(&ktab[DefaultMetaEsc]);
	}
      DefaultEsc = e;
      DefaultMetaEsc = me;
      if (users)
	{
	  if (DefaultEsc >= 0)
	    {
	      ClearAction(&ktab[DefaultEsc]);
	      ktab[DefaultEsc].nr = RC_OTHER;
	    }
	  if (DefaultMetaEsc >= 0)
	    {
	      ClearAction(&ktab[DefaultMetaEsc]);
	      ktab[DefaultMetaEsc].nr = RC_META;
	    }
	}
    }
}


CompileKeys(s, sl, array)
char *s;
int sl;
unsigned char *array;
{
  int i;
  unsigned char key, value;

  if (sl == 0)
    {
      for (i = 0; i < 256; i++)
        array[i] = i;
      return 0;
    }
  debug1("CompileKeys: '%s'\n", s);
  while (sl)
    {
      key = *(unsigned char *)s++;
      if (*s != '=' || sl < 3)
	return -1;
      sl--;
      do
	{
	  s++;
	  sl -= 2;
	  value = *(unsigned char *)s++;
	  array[value] = key;
	}
      while (*s == '=' && sl >= 2);
      if (sl == 0)
	break;
      if (*s++ != ':')
	return -1;
      sl--;
    }
  return 0;
}


MagickPrivate void XQueryPosition(Display *display,const Window window,int *x,
  int *y)
{
  int
    x_root,
    y_root;

  unsigned int
    mask;

  Window
    root_window;

  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  assert(display != (Display *) NULL);
  assert(window != (Window) NULL);
  assert(x != (int *) NULL);
  assert(y != (int *) NULL);
  (void) XQueryPointer(display,window,&root_window,&root_window,&x_root,&y_root,
    x,y,&mask);
}


MagickPrivate MagickBooleanType XRenderImage(Image *image,
  const DrawInfo *draw_info,const PointInfo *offset,TypeMetric *metrics,
  ExceptionInfo *exception)
{
  const char
    *client_name;

  DrawInfo
    cache_info;

  Display
    *display;

  ImageInfo
    *image_info;

  MagickBooleanType
    status;

  size_t
    height,
    width;

  XAnnotateInfo
    annotate_info;

  XFontStruct
    *font_info;

  XPixelInfo
    pixel;

  XResourceInfo
    resource_info;

  XrmDatabase
    resource_database;

  XStandardColormap
    *map_info;

  XVisualInfo
    *visual_info;

  /*
    Open X server connection.
  */
  display=XOpenDisplay(draw_info->server_name);
  if (display == (Display *) NULL)
    {
      ThrowXWindowException(XServerError,"UnableToOpenXServer",
        draw_info->server_name);
      return(MagickFalse);
    }
  /*
    Get user defaults from X resource database.
  */
  (void) XSetErrorHandler(XError);
  image_info=AcquireImageInfo();
  client_name=GetClientName();
  resource_database=XGetResourceDatabase(display,client_name);
  XGetResourceInfo(image_info,resource_database,client_name,&resource_info);
  resource_info.close_server=MagickFalse;
  resource_info.colormap=PrivateColormap;
  resource_info.font=AcquireString(draw_info->font);
  resource_info.background_color=AcquireString("#ffffffffffff");
  resource_info.foreground_color=AcquireString("#000000000000");
  map_info=XAllocStandardColormap();
  visual_info=(XVisualInfo *) NULL;
  font_info=(XFontStruct *) NULL;
  pixel.pixels=(unsigned long *) NULL;
  if (map_info == (XStandardColormap *) NULL)
    {
      ThrowXWindowException(ResourceLimitError,"MemoryAllocationFailed",
        image->filename);
      return(MagickFalse);
    }
  /*
    Initialize visual info.
  */
  visual_info=XBestVisualInfo(display,map_info,&resource_info);
  if (visual_info == (XVisualInfo *) NULL)
    {
      XFreeResources(display,visual_info,map_info,&pixel,font_info,
        &resource_info,(XWindowInfo *) NULL);
      ThrowXWindowException(XServerError,"UnableToGetVisual",image->filename);
      return(MagickFalse);
    }
  map_info->colormap=(Colormap) NULL;
  /*
    Initialize Standard Colormap info.
  */
  XGetMapInfo(visual_info,XDefaultColormap(display,visual_info->screen),
    map_info);
  XGetPixelInfo(display,visual_info,map_info,&resource_info,(Image *) NULL,
    &pixel);
  pixel.annotate_context=XDefaultGC(display,visual_info->screen);
  /*
    Initialize font info.
  */
  font_info=XBestFont(display,&resource_info,MagickFalse);
  if (font_info == (XFontStruct *) NULL)
    {
      XFreeResources(display,visual_info,map_info,&pixel,font_info,
        &resource_info,(XWindowInfo *) NULL);
      ThrowXWindowException(XServerError,"UnableToLoadFont",draw_info->font);
      return(MagickFalse);
    }
  cache_info=(*draw_info);
  /*
    Initialize annotate info.
  */
  XGetAnnotateInfo(&annotate_info);
  annotate_info.stencil=ForegroundStencil;
  if (cache_info.font != draw_info->font)
    {
      /*
        Type name has changed.
      */
      (void) XFreeFont(display,font_info);
      (void) CloneString(&resource_info.font,draw_info->font);
      font_info=XBestFont(display,&resource_info,MagickFalse);
      if (font_info == (XFontStruct *) NULL)
        {
          ThrowXWindowException(XServerError,"UnableToLoadFont",
            draw_info->font);
          return(MagickFalse);
        }
    }
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(AnnotateEvent,GetMagickModule(),
      "Font %s; pointsize %g",draw_info->font != (char *) NULL ?
      draw_info->font : "none",draw_info->pointsize);
  cache_info=(*draw_info);
  annotate_info.font_info=font_info;
  annotate_info.text=(char *) draw_info->text;
  annotate_info.width=(unsigned int) XTextWidth(font_info,draw_info->text,(int)
    strlen(draw_info->text));
  annotate_info.height=(unsigned int) font_info->ascent+font_info->descent;
  metrics->pixels_per_em.x=(double) font_info->max_bounds.width;
  metrics->pixels_per_em.y=(double) font_info->ascent+font_info->descent;
  metrics->ascent=(double) font_info->ascent+4;
  metrics->descent=(double) (-font_info->descent);
  metrics->width=annotate_info.width/ExpandAffine(&draw_info->affine);
  metrics->height=(double) font_info->ascent+font_info->descent;
  metrics->max_advance=(double) font_info->max_bounds.width;
  metrics->bounds.x1=0.0;
  metrics->bounds.y1=metrics->descent;
  metrics->bounds.x2=metrics->ascent+metrics->descent;
  metrics->bounds.y2=metrics->ascent+metrics->descent;
  metrics->underline_position=(-2.0);
  metrics->underline_thickness=1.0;
  if (draw_info->render == MagickFalse)
    return(MagickTrue);
  if (draw_info->fill.alpha == TransparentAlpha)
    return(MagickTrue);
  /*
    Render fill color.
  */
  width=annotate_info.width;
  height=annotate_info.height;
  if ((fabs(draw_info->affine.rx) >= MagickEpsilon) ||
      (fabs(draw_info->affine.ry) >= MagickEpsilon))
    {
      if ((fabs(draw_info->affine.sx-draw_info->affine.sy) < MagickEpsilon) &&
          (fabs(draw_info->affine.rx+draw_info->affine.ry) < MagickEpsilon))
        annotate_info.degrees=(double) (180.0/MagickPI)*
          atan2(draw_info->affine.rx,draw_info->affine.sx);
    }
  (void) FormatLocaleString(annotate_info.geometry,MagickPathExtent,
    "%.20gx%.20g%+.20g%+.20g",(double) width,(double) height,
    ceil(offset->x-0.5),ceil(offset->y-metrics->ascent-metrics->descent+
    draw_info->interline_spacing-0.5));
  pixel.pen_color.red=ScaleQuantumToShort(
    ClampToQuantum(draw_info->fill.red));
  pixel.pen_color.green=ScaleQuantumToShort(
    ClampToQuantum(draw_info->fill.green));
  pixel.pen_color.blue=ScaleQuantumToShort(
    ClampToQuantum(draw_info->fill.blue));
  status=XAnnotateImage(display,&pixel,&annotate_info,image,exception);
  if (status == 0)
    {
      ThrowXWindowException(ResourceLimitError,"MemoryAllocationFailed",
        image->filename);
      return(MagickFalse);
    }
  return(MagickTrue);
}


MagickPrivate void XGetAnnotateInfo(XAnnotateInfo *annotate_info)
{
  /*
    Initialize annotate structure.
  */
  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  assert(annotate_info != (XAnnotateInfo *) NULL);
  annotate_info->x=0;
  annotate_info->y=0;
  annotate_info->width=0;
  annotate_info->height=0;
  annotate_info->stencil=ForegroundStencil;
  annotate_info->degrees=0.0;
  annotate_info->font_info=(XFontStruct *) NULL;
  annotate_info->text=(char *) NULL;
  *annotate_info->geometry='\0';
  annotate_info->previous=(XAnnotateInfo *) NULL;
  annotate_info->next=(XAnnotateInfo *) NULL;
  (void) XSupportsLocale();
  (void) XSetLocaleModifiers("");
}


MagickPrivate void XBestPixel(Display *display,const Colormap colormap,
  XColor *colors,unsigned int number_colors,XColor *color)
{
  MagickBooleanType
    query_server;

  PixelInfo
    pixel;

  double
    min_distance;

  register double
    distance;

  register int
    i,
    j;

  Status
    status;

  /*
    Find closest representation for the requested RGB color.
  */
  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  assert(display != (Display *) NULL);
  assert(color != (XColor *) NULL);
  status=XAllocColor(display,colormap,color);
  if (status != False)
    return;
  query_server=colors == (XColor *) NULL ? MagickTrue : MagickFalse;
  if (query_server != MagickFalse)
    {
      /*
        Read X server colormap.
      */
      colors=(XColor *) AcquireQuantumMemory(number_colors,sizeof(*colors));
      if (colors == (XColor *) NULL)
        {
          ThrowXWindowException(ResourceLimitError,"MemoryAllocationFailed",
            "...");
          return;
        }
      for (i=0; i < (int) number_colors; i++)
        colors[i].pixel=(size_t) i;
      if (number_colors > 256)
        number_colors=256;
      (void) XQueryColors(display,colormap,colors,(int) number_colors);
    }
  min_distance=3.0*((double) QuantumRange+1.0)*((double)
    QuantumRange+1.0);
  j=0;
  for (i=0; i < (int) number_colors; i++)
  {
    pixel.red=colors[i].red-(double) color->red;
    distance=pixel.red*pixel.red;
    if (distance > min_distance)
      continue;
    pixel.green=colors[i].green-(double) color->green;
    distance+=pixel.green*pixel.green;
    if (distance > min_distance)
      continue;
    pixel.blue=colors[i].blue-(double) color->blue;
    distance+=pixel.blue*pixel.blue;
    if (distance > min_distance)
      continue;
    min_distance=distance;
    color->pixel=colors[i].pixel;
    j=i;
  }
  (void) XAllocColor(display,colormap,&colors[j]);
  if (query_server != MagickFalse)
    colors=(XColor *) RelinquishMagickMemory(colors);
}


MagickPrivate MagickBooleanType XGetWindowColor(Display *display,
  XWindows *windows,char *name,ExceptionInfo *exception)
{
  int
    x,
    y;

  PixelInfo
    pixel;

  RectangleInfo
    crop_info;

  Status
    status;

  Window
    child,
    client_window,
    root_window,
    target_window;

  XColor
    color;

  XImage
    *ximage;

  XWindowAttributes
    window_attributes;

  /*
    Choose a pixel from the X server.
  */
  assert(display != (Display *) NULL);
  assert(name != (char *) NULL);
  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",name);
  *name='\0';
  target_window=XSelectWindow(display,&crop_info);
  if (target_window == (Window) NULL)
    return(MagickFalse);
  root_window=XRootWindow(display,XDefaultScreen(display));
  client_window=target_window;
  if (target_window != root_window)
    {
      unsigned int
        d;

      /*
        Get client window.
      */
      status=XGetGeometry(display,target_window,&root_window,&x,&x,&d,&d,&d,&d);
      if (status != False)
        {
          client_window=XClientWindow(display,target_window);
          target_window=client_window;
        }
    }
  /*
    Verify window is viewable.
  */
  status=XGetWindowAttributes(display,target_window,&window_attributes);
  if ((status == False) || (window_attributes.map_state != IsViewable))
    return(MagickFalse);
  /*
    Get window X image.
  */
  (void) XTranslateCoordinates(display,root_window,target_window,
    (int) crop_info.x,(int) crop_info.y,&x,&y,&child);
  ximage=XGetImage(display,target_window,x,y,1,1,AllPlanes,ZPixmap);
  if (ximage == (XImage *) NULL)
    return(MagickFalse);
  color.pixel=XGetPixel(ximage,0,0);
  XDestroyImage(ximage);
  /*
    Match color against the color database.
  */
  (void) XQueryColor(display,window_attributes.colormap,&color);
  pixel.red=(double) ScaleShortToQuantum(color.red);
  pixel.green=(double) ScaleShortToQuantum(color.green);
  pixel.blue=(double) ScaleShortToQuantum(color.blue);
  pixel.alpha=(MagickRealType) OpaqueAlpha;
  (void) QueryColorname(windows->image.image,&pixel,X11Compliance,name,
    exception);
  return(MagickTrue);
}


MagickPrivate void XCheckRefreshWindows(Display *display,XWindows *windows)
{
  Window
    id;

  XEvent
    event;

  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  assert(display != (Display *) NULL);
  assert(windows != (XWindows *) NULL);
  XDelay(display,SuspendTime);
  id=windows->command.id;
  while (XCheckTypedWindowEvent(display,id,Expose,&event) != MagickFalse)
    (void) XCommandWidget(display,windows,(char const **) NULL,&event);
  id=windows->image.id;
  while (XCheckTypedWindowEvent(display,id,Expose,&event) != MagickFalse)
    XRefreshWindow(display,&windows->image,&event);
  XDelay(display,SuspendTime << 1);
  id=windows->command.id;
  while (XCheckTypedWindowEvent(display,id,Expose,&event) != MagickFalse)
    (void) XCommandWidget(display,windows,(char const **) NULL,&event);
  id=windows->image.id;
  while (XCheckTypedWindowEvent(display,id,Expose,&event) != MagickFalse)
    XRefreshWindow(display,&windows->image,&event);
}


MagickPrivate int XCheckDefineCursor(Display *display,Window window,
  Cursor cursor)
{
  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  assert(display != (Display *) NULL);
  if (window == XRootWindow(display,XDefaultScreen(display)))
    return(0);
  return(XDefineCursor(display,window,cursor));
}


MagickPrivate MagickBooleanType XMakeImage(Display *display,
  const XResourceInfo *resource_info,XWindowInfo *window,Image *image,
  unsigned int width,unsigned int height,ExceptionInfo *exception)
{
#define CheckOverflowException(length,width,height) \
  (((height) != 0) && ((length)/((size_t) height) != ((size_t) width)))

  int
    depth,
    format;

  size_t
    length;

  XImage
    *matte_image,
    *ximage;

  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
  assert(display != (Display *) NULL);
  assert(resource_info != (XResourceInfo *) NULL);
  assert(window != (XWindowInfo *) NULL);
  assert(width != 0);
  assert(height != 0);
  if ((window->width == 0) || (window->height == 0))
    return(MagickFalse);
  /*
    Apply user transforms to the image.
  */
  (void) XCheckDefineCursor(display,window->id,window->busy_cursor);
  (void) XFlush(display);
  depth=(int) window->depth;
  if (window->destroy)
    window->image=DestroyImage(window->image);
  window->image=image;
  window->destroy=MagickFalse;
  if (window->image != (Image *) NULL)
    {
      if (window->crop_geometry != (char *) NULL)
        {
          Image
            *crop_image;

          RectangleInfo
            crop_info;

          /*
            Crop image.
          */
          window->image->page.x=0;
          window->image->page.y=0;
          (void) ParsePageGeometry(window->image,window->crop_geometry,
            &crop_info,exception);
          crop_image=CropImage(window->image,&crop_info,exception);
          if (crop_image != (Image *) NULL)
            {
              if (window->image != image)
                window->image=DestroyImage(window->image);
              window->image=crop_image;
              window->destroy=MagickTrue;
            }
        }
      if ((width != (unsigned int) window->image->columns) ||
          (height != (unsigned int) window->image->rows))
        {
          Image
            *resize_image;

          /*
            Resize image.
          */
          resize_image=NewImageList();
          if ((window->pixel_info->colors == 0) &&
              (window->image->rows > (unsigned long) XDisplayHeight(display,window->screen)) &&
              (window->image->columns > (unsigned long) XDisplayWidth(display,window->screen)))
              resize_image=ResizeImage(window->image,width,height,
                image->filter,exception);
          else
            {
              if (window->image->storage_class == PseudoClass)
                resize_image=SampleImage(window->image,width,height,
                  exception);
              else
                resize_image=ThumbnailImage(window->image,width,height,
                  exception);
            }
          if (resize_image != (Image *) NULL)
            {
              if (window->image != image)
                window->image=DestroyImage(window->image);
              window->image=resize_image;
              window->destroy=MagickTrue;
            }
        }
      width=(unsigned int) window->image->columns;
      assert((size_t) width == window->image->columns);
      height=(unsigned int) window->image->rows;
      assert((size_t) height == window->image->rows);
    }
  /*
    Create X image.
  */
  ximage=(XImage *) NULL;
  format=(depth == 1) ? XYBitmap : ZPixmap;
#if defined(MAGICKCORE_HAVE_SHARED_MEMORY)
  if (window->shared_memory != MagickFalse)
    {
      XShmSegmentInfo
        *segment_info;

      segment_info=(XShmSegmentInfo *) window->segment_info;
      segment_info[1].shmid=(-1);
      segment_info[1].shmaddr=(char *) NULL;
      ximage=XShmCreateImage(display,window->visual,(unsigned int) depth,format,
        (char *) NULL,&segment_info[1],width,height);
      if (ximage == (XImage *) NULL)
        window->shared_memory=MagickFalse;
      else
        {
          length=(size_t) ximage->bytes_per_line*ximage->height;
          if (CheckOverflowException(length,ximage->bytes_per_line,ximage->height))
            window->shared_memory=MagickFalse;
        }
      if (window->shared_memory != MagickFalse)
        segment_info[1].shmid=shmget(IPC_PRIVATE,length,IPC_CREAT | 0777);
      if (window->shared_memory != MagickFalse)
        segment_info[1].shmaddr=(char *) shmat(segment_info[1].shmid,0,0);
      if (segment_info[1].shmid < 0)
        window->shared_memory=MagickFalse;
      if (window->shared_memory != MagickFalse)
        (void) shmctl(segment_info[1].shmid,IPC_RMID,0);
      else
        {
          if (ximage != (XImage *) NULL)
            XDestroyImage(ximage);
          ximage=(XImage *) NULL;
          if (segment_info[1].shmaddr)
            {
              (void) shmdt(segment_info[1].shmaddr);
              segment_info[1].shmaddr=(char *) NULL;
            }
          if (segment_info[1].shmid >= 0)
            {
              (void) shmctl(segment_info[1].shmid,IPC_RMID,0);
              segment_info[1].shmid=(-1);
            }
        }
    }
#endif
  /*
    Allocate X image pixel data.
  */
#if defined(MAGICKCORE_HAVE_SHARED_MEMORY)
  if (window->shared_memory)
    {
      Status
        status;

      XShmSegmentInfo
        *segment_info;

      (void) XSync(display,MagickFalse);
      xerror_alert=MagickFalse;
      segment_info=(XShmSegmentInfo *) window->segment_info;
      ximage->data=segment_info[1].shmaddr;
      segment_info[1].readOnly=MagickFalse;
      status=XShmAttach(display,&segment_info[1]);
      if (status != False)
        (void) XSync(display,MagickFalse);
      if ((status == False) || (xerror_alert != MagickFalse))
        {
          window->shared_memory=MagickFalse;
          if (status != False)
            XShmDetach(display,&segment_info[1]);
          ximage->data=NULL;
          XDestroyImage(ximage);
          ximage=(XImage *) NULL;
          if (segment_info[1].shmid >= 0)
            {
              if (segment_info[1].shmaddr != NULL)
                (void) shmdt(segment_info[1].shmaddr);
              (void) shmctl(segment_info[1].shmid,IPC_RMID,0);
              segment_info[1].shmid=(-1);
              segment_info[1].shmaddr=(char *) NULL;
            }
        }
    }
#endif
  if (window->shared_memory == MagickFalse)
    ximage=XCreateImage(display,window->visual,(unsigned int) depth,format,0,
      (char *) NULL,width,height,XBitmapPad(display),0);
  if (ximage == (XImage *) NULL)
    {
      /*
        Unable to create X image.
      */
      (void) XCheckDefineCursor(display,window->id,window->cursor);
      return(MagickFalse);
    }
  length=(size_t) ximage->bytes_per_line*ximage->height;
  if (IsEventLogging())
    {
      (void) LogMagickEvent(X11Event,GetMagickModule(),"XImage:");
      (void) LogMagickEvent(X11Event,GetMagickModule(),"  width, height: %dx%d",
        ximage->width,ximage->height);
      (void) LogMagickEvent(X11Event,GetMagickModule(),"  format: %d",
        ximage->format);
      (void) LogMagickEvent(X11Event,GetMagickModule(),"  byte order: %d",
        ximage->byte_order);
      (void) LogMagickEvent(X11Event,GetMagickModule(),
        "  bitmap unit, bit order, pad: %d %d %d",ximage->bitmap_unit,
        ximage->bitmap_bit_order,ximage->bitmap_pad);
      (void) LogMagickEvent(X11Event,GetMagickModule(),"  depth: %d",
        ximage->depth);
      (void) LogMagickEvent(X11Event,GetMagickModule(),"  bytes per line: %d",
        ximage->bytes_per_line);
      (void) LogMagickEvent(X11Event,GetMagickModule(),"  bits per pixel: %d",
        ximage->bits_per_pixel);
      (void) LogMagickEvent(X11Event,GetMagickModule(),
        "  red, green, blue masks: 0x%lx 0x%lx 0x%lx",ximage->red_mask,
        ximage->green_mask,ximage->blue_mask);
    }
  if (window->shared_memory == MagickFalse)
    {
      if (ximage->format == XYBitmap)
        {
          ximage->data=(char *) AcquireQuantumMemory((size_t)
            ximage->bytes_per_line,(size_t) ximage->depth*ximage->height);
          if (ximage->data != (char *) NULL)
            (void) memset(ximage->data,0,(size_t)
              ximage->bytes_per_line*ximage->depth*ximage->height);
        }
      else
        {
          ximage->data=(char *) AcquireQuantumMemory((size_t)
            ximage->bytes_per_line,(size_t) ximage->height);
          if (ximage->data != (char *) NULL)
            (void) memset(ximage->data,0,(size_t)
              ximage->bytes_per_line*ximage->height);
        }
    }
  if (ximage->data == (char *) NULL)
    {
      /*
        Unable to allocate pixel data.
      */
      XDestroyImage(ximage);
      ximage=(XImage *) NULL;
      (void) XCheckDefineCursor(display,window->id,window->cursor);
      return(MagickFalse);
    }
  if (window->ximage != (XImage *) NULL)
    {
      /*
        Destroy previous X image.
      */
      length=(size_t) window->ximage->bytes_per_line*window->ximage->height;
#if defined(MAGICKCORE_HAVE_SHARED_MEMORY)
      if (window->segment_info != (XShmSegmentInfo *) NULL)
        {
          XShmSegmentInfo
            *segment_info;

          segment_info=(XShmSegmentInfo *) window->segment_info;
          if (segment_info[0].shmid >= 0)
            {
              (void) XSync(display,MagickFalse);
              (void) XShmDetach(display,&segment_info[0]);
              (void) XSync(display,MagickFalse);
              if (segment_info[0].shmaddr != (char *) NULL)
                (void) shmdt(segment_info[0].shmaddr);
              (void) shmctl(segment_info[0].shmid,IPC_RMID,0);
              segment_info[0].shmid=(-1);
              segment_info[0].shmaddr=(char *) NULL;
              window->ximage->data=(char *) NULL;
          }
        }
#endif
      if (window->ximage->data != (char *) NULL)
        free(window->ximage->data);
      window->ximage->data=(char *) NULL;
      XDestroyImage(window->ximage);
      window->ximage=(XImage *) NULL;
    }
#if defined(MAGICKCORE_HAVE_SHARED_MEMORY)
  if (window->segment_info != (XShmSegmentInfo *) NULL)
    {
      XShmSegmentInfo
        *segment_info;

      segment_info=(XShmSegmentInfo *) window->segment_info;
      segment_info[0]=segment_info[1];
    }
#endif
  window->ximage=ximage;
  matte_image=(XImage *) NULL;
  if ((window->shape != MagickFalse) && (window->image != (Image *) NULL))
    if ((window->image->alpha_trait != UndefinedPixelTrait) &&
        ((int) width <= XDisplayWidth(display,window->screen)) &&
        ((int) height <= XDisplayHeight(display,window->screen)))
      {
        /*
          Create matte image.
        */
        matte_image=XCreateImage(display,window->visual,1,XYBitmap,0,
          (char *) NULL,width,height,XBitmapPad(display),0);
        if (IsEventLogging())
          {
            (void) LogMagickEvent(X11Event,GetMagickModule(),"Matte Image:");
            (void) LogMagickEvent(X11Event,GetMagickModule(),
              "  width, height: %dx%d",matte_image->width,matte_image->height);
          }
        if (matte_image != (XImage *) NULL)
          {
            /*
              Allocate matte image pixel data.
            */
            matte_image->data=(char *) malloc((size_t)
              matte_image->bytes_per_line*matte_image->depth*
              matte_image->height);
            if (matte_image->data == (char *) NULL)
              {
                XDestroyImage(matte_image);
                matte_image=(XImage *) NULL;
              }
          }
      }
  if (window->matte_image != (XImage *) NULL)
    {
      /*
        Free matte image.
      */
      if (window->matte_image->data != (char *) NULL)
        free(window->matte_image->data);
      window->matte_image->data=(char *) NULL;
      XDestroyImage(window->matte_image);
      window->matte_image=(XImage *) NULL;
    }
  window->matte_image=matte_image;
  if (window->matte_pixmap != (Pixmap) NULL)
    {
      (void) XFreePixmap(display,window->matte_pixmap);
      window->matte_pixmap=(Pixmap) NULL;
#if defined(MAGICKCORE_HAVE_SHAPE)
      if (window->shape != MagickFalse)
        XShapeCombineMask(display,window->id,ShapeBounding,0,0,None,ShapeSet);
#endif
    }
  window->stasis=MagickFalse;
  /*
    Convert pixels to X image data.
  */
  if (window->image != (Image *) NULL)
    {
      if ((ximage->byte_order == LSBFirst) || ((ximage->format == XYBitmap) &&
          (ximage->bitmap_bit_order == LSBFirst)))
        XMakeImageLSBFirst(resource_info,window,window->image,ximage,
          matte_image,exception);
      else
        XMakeImageMSBFirst(resource_info,window,window->image,ximage,
          matte_image,exception);
    }
  if (window->matte_image != (XImage *) NULL)
    {
      /*
        Create matte pixmap.
      */
      window->matte_pixmap=XCreatePixmap(display,window->id,width,height,1);
      if (window->matte_pixmap != (Pixmap) NULL)
        {
          GC
            graphics_context;

          XGCValues
            context_values;

          /*
            Copy matte image to matte pixmap.
          */
          context_values.background=0;
          context_values.foreground=1;
          graphics_context=XCreateGC(display,window->matte_pixmap,
            (size_t) (GCBackground | GCForeground),&context_values);
          (void) XPutImage(display,window->matte_pixmap,graphics_context,
            window->matte_image,0,0,0,0,width,height);
          (void) XFreeGC(display,graphics_context);
#if defined(MAGICKCORE_HAVE_SHAPE)
          if (window->shape != MagickFalse)
            XShapeCombineMask(display,window->id,ShapeBounding,0,0,
              window->matte_pixmap,ShapeSet);
#endif
        }
      }
  (void) XMakePixmap(display,resource_info,window);
  /*
    Restore cursor.
  */
  (void) XCheckDefineCursor(display,window->id,window->cursor);
  return(MagickTrue);
}


reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
{
  pid_t ppid = getpid ();
  char uid[16];
  char gid[16];
  char **argv;
  int pid;
  char *cwd = getcwd (NULL, 0);
  sigset_t sigset, oldsigset;

  if (cwd == NULL)
    {
      fprintf (stderr, "error getting current working directory: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }

  sprintf (uid, "%d", geteuid ());
  sprintf (gid, "%d", getegid ());

  argv = get_cmd_line_args (ppid);
  if (argv == NULL)
    {
      fprintf (stderr, "cannot read argv: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }

  pid = fork ();
  if (pid < 0)
    fprintf (stderr, "cannot fork: %s\n", strerror (errno));

  if (pid)
    {
      /* We passed down these fds, close them.  */
      int f;
      for (f = 3; f < open_files_max_fd; f++)
        if (open_files_set == NULL || FD_ISSET (f % FD_SETSIZE, &(open_files_set[f / FD_SETSIZE])))
          close (f);
      return pid;
    }

  if (sigfillset (&sigset) < 0)
    {
      fprintf (stderr, "cannot fill sigset: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }
  if (sigdelset (&sigset, SIGCHLD) < 0)
    {
      fprintf (stderr, "cannot sigdelset(SIGCHLD): %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }
  if (sigdelset (&sigset, SIGTERM) < 0)
    {
      fprintf (stderr, "cannot sigdelset(SIGTERM): %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }
  if (sigprocmask (SIG_BLOCK, &sigset, &oldsigset) < 0)
    {
      fprintf (stderr, "cannot block signals: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }

  setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1);
  setenv ("_CONTAINERS_ROOTLESS_UID", uid, 1);
  setenv ("_CONTAINERS_ROOTLESS_GID", gid, 1);

  if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0)
    {
      fprintf (stderr, "cannot prctl(PR_SET_PDEATHSIG): %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }

  if (setns (userns, 0) < 0)
    {
      fprintf (stderr, "cannot setns: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }
  close (userns);

  if (mountns >= 0 && setns (mountns, 0) < 0)
    {
      fprintf (stderr, "cannot setns: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }
  close (mountns);

  if (syscall_setresgid (0, 0, 0) < 0)
    {
      fprintf (stderr, "cannot setresgid: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }

  if (syscall_setresuid (0, 0, 0) < 0)
    {
      fprintf (stderr, "cannot setresuid: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }

  if (chdir (cwd) < 0)
    {
      fprintf (stderr, "cannot chdir: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }
  free (cwd);

  if (pause_pid_file_path && pause_pid_file_path[0] != '\0')
    {
      /* We ignore errors here as we didn't create the namespace anyway.  */
      create_pause_process (pause_pid_file_path, argv);
    }
  if (sigprocmask (SIG_SETMASK, &oldsigset, NULL) < 0)
    {
      fprintf (stderr, "cannot block signals: %s\n", strerror (errno));
      _exit (EXIT_FAILURE);
    }

  execvp (argv[0], argv);

  _exit (EXIT_FAILURE);
}


static int ssl_security_cert_sig(SSL *s, SSL_CTX *ctx, X509 *x, int op)
{
    /* Lookup signature algorithm digest */
    int secbits, nid, pknid;
    /* Don't check signature if self signed */
    if ((X509_get_extension_flags(x) & EXFLAG_SS) != 0)
        return 1;
    if (!X509_get_signature_info(x, &nid, &pknid, &secbits, NULL))
        secbits = -1;
    /* If digest NID not defined use signature NID */
    if (nid == NID_undef)
        nid = pknid;
    if (s)
        return ssl_security(s, op, secbits, nid, x);
    else
        return ssl_ctx_security(ctx, op, secbits, nid, x);
}


int tls1_set_sigalgs_list(CERT *c, const char *str, int client)
{
    sig_cb_st sig;
    sig.sigalgcnt = 0;
    if (!CONF_parse_list(str, ':', 1, sig_cb, &sig))
        return 0;
    if (c == NULL)
        return 1;
    return tls1_set_raw_sigalgs(c, sig.sigalgs, sig.sigalgcnt, client);
}


static const SIGALG_LOOKUP *tls1_lookup_sigalg(uint16_t sigalg)
{
    size_t i;
    const SIGALG_LOOKUP *s;

    for (i = 0, s = sigalg_lookup_tbl; i < OSSL_NELEM(sigalg_lookup_tbl);
         i++, s++) {
        if (s->sigalg == sigalg)
            return s;
    }
    return NULL;
}


int tls12_copy_sigalgs(SSL *s, WPACKET *pkt,
                       const uint16_t *psig, size_t psiglen)
{
    size_t i;
    int rv = 0;

    for (i = 0; i < psiglen; i++, psig++) {
        const SIGALG_LOOKUP *lu = tls1_lookup_sigalg(*psig);

        if (!tls12_sigalg_allowed(s, SSL_SECOP_SIGALG_SUPPORTED, lu))
            continue;
        if (!WPACKET_put_bytes_u16(pkt, *psig))
            return 0;
        /*
         * If TLS 1.3 must have at least one valid TLS 1.3 message
         * signing algorithm: i.e. neither RSA nor SHA1/SHA224
         */
        if (rv == 0 && (!SSL_IS_TLS13(s)
            || (lu->sig != EVP_PKEY_RSA
                && lu->hash != NID_sha1
                && lu->hash != NID_sha224)))
            rv = 1;
    }
    if (rv == 0)
        SSLerr(SSL_F_TLS12_COPY_SIGALGS, SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM);
    return rv;
}


DH *ssl_get_auto_dh(SSL *s)
{
    int dh_secbits = 80;
    if (s->cert->dh_tmp_auto == 2)
        return DH_get_1024_160();
    if (s->s3.tmp.new_cipher->algorithm_auth & (SSL_aNULL | SSL_aPSK)) {
        if (s->s3.tmp.new_cipher->strength_bits == 256)
            dh_secbits = 128;
        else
            dh_secbits = 80;
    } else {
        if (s->s3.tmp.cert == NULL)
            return NULL;
        dh_secbits = EVP_PKEY_security_bits(s->s3.tmp.cert->privatekey);
    }

    if (dh_secbits >= 128) {
        DH *dhp = DH_new();
        BIGNUM *p, *g;
        if (dhp == NULL)
            return NULL;
        g = BN_new();
        if (g == NULL || !BN_set_word(g, 2)) {
            DH_free(dhp);
            BN_free(g);
            return NULL;
        }
        if (dh_secbits >= 192)
            p = BN_get_rfc3526_prime_8192(NULL);
        else
            p = BN_get_rfc3526_prime_3072(NULL);
        if (p == NULL || !DH_set0_pqg(dhp, p, NULL, g)) {
            DH_free(dhp);
            BN_free(p);
            BN_free(g);
            return NULL;
        }
        return dhp;
    }
    if (dh_secbits >= 112)
        return DH_get_2048_224();
    return DH_get_1024_160();
}


int tls1_set_raw_sigalgs(CERT *c, const uint16_t *psigs, size_t salglen,
                     int client)
{
    uint16_t *sigalgs;

    if ((sigalgs = OPENSSL_malloc(salglen * sizeof(*sigalgs))) == NULL) {
        SSLerr(SSL_F_TLS1_SET_RAW_SIGALGS, ERR_R_MALLOC_FAILURE);
        return 0;
    }
    memcpy(sigalgs, psigs, salglen * sizeof(*sigalgs));

    if (client) {
        OPENSSL_free(c->client_sigalgs);
        c->client_sigalgs = sigalgs;
        c->client_sigalgslen = salglen;
    } else {
        OPENSSL_free(c->conf_sigalgs);
        c->conf_sigalgs = sigalgs;
        c->conf_sigalgslen = salglen;
    }

    return 1;
}


int tls_group_allowed(SSL *s, uint16_t group, int op)
{
    const TLS_GROUP_INFO *ginfo = tls1_group_id_lookup(group);
    unsigned char gtmp[2];

    if (ginfo == NULL)
        return 0;
#ifdef OPENSSL_NO_EC2M
    if (ginfo->flags & TLS_GROUP_CURVE_CHAR2)
        return 0;
#endif
#ifdef OPENSSL_NO_DH
    if (ginfo->flags & TLS_GROUP_FFDHE)
        return 0;
#endif
    gtmp[0] = group >> 8;
    gtmp[1] = group & 0xff;
    return ssl_security(s, op, ginfo->secbits, ginfo->nid, (void *)gtmp);
}


int ssl_set_client_disabled(SSL *s)
{
    s->s3.tmp.mask_a = 0;
    s->s3.tmp.mask_k = 0;
    ssl_set_sig_mask(&s->s3.tmp.mask_a, s, SSL_SECOP_SIGALG_MASK);
    if (ssl_get_min_max_version(s, &s->s3.tmp.min_ver,
                                &s->s3.tmp.max_ver, NULL) != 0)
        return 0;
#ifndef OPENSSL_NO_PSK
    /* with PSK there must be client callback set */
    if (!s->psk_client_callback) {
        s->s3.tmp.mask_a |= SSL_aPSK;
        s->s3.tmp.mask_k |= SSL_PSK;
    }
#endif                          /* OPENSSL_NO_PSK */
#ifndef OPENSSL_NO_SRP
    if (!(s->srp_ctx.srp_Mask & SSL_kSRP)) {
        s->s3.tmp.mask_a |= SSL_aSRP;
        s->s3.tmp.mask_k |= SSL_kSRP;
    }
#endif
    return 1;
}


uint8_t SSL_SESSION_get_max_fragment_length(const SSL_SESSION *session)
{
    return session->ext.max_fragment_len_mode;
}


int tls12_check_peer_sigalg(SSL *s, uint16_t sig, EVP_PKEY *pkey)
{
    const uint16_t *sent_sigs;
    const EVP_MD *md = NULL;
    char sigalgstr[2];
    size_t sent_sigslen, i, cidx;
    int pkeyid = -1;
    const SIGALG_LOOKUP *lu;
    int secbits = 0;

    /*
     * TODO(3.0) Remove this when we adapted this function for provider
     * side keys.  We know that EVP_PKEY_get0() downgrades an EVP_PKEY
     * to contain a legacy key.
     *
     * THIS IS TEMPORARY
     */
    EVP_PKEY_get0(pkey);
    if (EVP_PKEY_id(pkey) == EVP_PKEY_NONE)
        return 0;

    pkeyid = EVP_PKEY_id(pkey);
    /* Should never happen */
    if (pkeyid == -1)
        return -1;
    if (SSL_IS_TLS13(s)) {
        /* Disallow DSA for TLS 1.3 */
        if (pkeyid == EVP_PKEY_DSA) {
            SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS12_CHECK_PEER_SIGALG,
                     SSL_R_WRONG_SIGNATURE_TYPE);
            return 0;
        }
        /* Only allow PSS for TLS 1.3 */
        if (pkeyid == EVP_PKEY_RSA)
            pkeyid = EVP_PKEY_RSA_PSS;
    }
    lu = tls1_lookup_sigalg(sig);
    /*
     * Check sigalgs is known. Disallow SHA1/SHA224 with TLS 1.3. Check key type
     * is consistent with signature: RSA keys can be used for RSA-PSS
     */
    if (lu == NULL
        || (SSL_IS_TLS13(s) && (lu->hash == NID_sha1 || lu->hash == NID_sha224))
        || (pkeyid != lu->sig
        && (lu->sig != EVP_PKEY_RSA_PSS || pkeyid != EVP_PKEY_RSA))) {
        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS12_CHECK_PEER_SIGALG,
                 SSL_R_WRONG_SIGNATURE_TYPE);
        return 0;
    }
    /* Check the sigalg is consistent with the key OID */
    if (!ssl_cert_lookup_by_nid(EVP_PKEY_id(pkey), &cidx)
            || lu->sig_idx != (int)cidx) {
        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS12_CHECK_PEER_SIGALG,
                 SSL_R_WRONG_SIGNATURE_TYPE);
        return 0;
    }

#ifndef OPENSSL_NO_EC
    if (pkeyid == EVP_PKEY_EC) {

        /* Check point compression is permitted */
        if (!tls1_check_pkey_comp(s, pkey)) {
            SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
                     SSL_F_TLS12_CHECK_PEER_SIGALG,
                     SSL_R_ILLEGAL_POINT_COMPRESSION);
            return 0;
        }

        /* For TLS 1.3 or Suite B check curve matches signature algorithm */
        if (SSL_IS_TLS13(s) || tls1_suiteb(s)) {
            int curve = evp_pkey_get_EC_KEY_curve_nid(pkey);

            if (lu->curve != NID_undef && curve != lu->curve) {
                SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
                         SSL_F_TLS12_CHECK_PEER_SIGALG, SSL_R_WRONG_CURVE);
                return 0;
            }
        }
        if (!SSL_IS_TLS13(s)) {
            /* Check curve matches extensions */
            if (!tls1_check_group_id(s, tls1_get_group_id(pkey), 1)) {
                SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
                         SSL_F_TLS12_CHECK_PEER_SIGALG, SSL_R_WRONG_CURVE);
                return 0;
            }
            if (tls1_suiteb(s)) {
                /* Check sigalg matches a permissible Suite B value */
                if (sig != TLSEXT_SIGALG_ecdsa_secp256r1_sha256
                    && sig != TLSEXT_SIGALG_ecdsa_secp384r1_sha384) {
                    SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE,
                             SSL_F_TLS12_CHECK_PEER_SIGALG,
                             SSL_R_WRONG_SIGNATURE_TYPE);
                    return 0;
                }
            }
        }
    } else if (tls1_suiteb(s)) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_F_TLS12_CHECK_PEER_SIGALG,
                 SSL_R_WRONG_SIGNATURE_TYPE);
        return 0;
    }
#endif

    /* Check signature matches a type we sent */
    sent_sigslen = tls12_get_psigalgs(s, 1, &sent_sigs);
    for (i = 0; i < sent_sigslen; i++, sent_sigs++) {
        if (sig == *sent_sigs)
            break;
    }
    /* Allow fallback to SHA1 if not strict mode */
    if (i == sent_sigslen && (lu->hash != NID_sha1
        || s->cert->cert_flags & SSL_CERT_FLAGS_CHECK_TLS_STRICT)) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_F_TLS12_CHECK_PEER_SIGALG,
                 SSL_R_WRONG_SIGNATURE_TYPE);
        return 0;
    }
    if (!tls1_lookup_md(s->ctx, lu, &md)) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_F_TLS12_CHECK_PEER_SIGALG,
                 SSL_R_UNKNOWN_DIGEST);
        return 0;
    }
    /*
     * Make sure security callback allows algorithm. For historical
     * reasons we have to pass the sigalg as a two byte char array.
     */
    sigalgstr[0] = (sig >> 8) & 0xff;
    sigalgstr[1] = sig & 0xff;
    secbits = sigalg_security_bits(s->ctx, lu);
    if (secbits == 0 ||
        !ssl_security(s, SSL_SECOP_SIGALG_CHECK, secbits,
                      md != NULL ? EVP_MD_type(md) : NID_undef,
                      (void *)sigalgstr)) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_F_TLS12_CHECK_PEER_SIGALG,
                 SSL_R_WRONG_SIGNATURE_TYPE);
        return 0;
    }
    /* Store the sigalg the peer uses */
    s->s3.tmp.peer_sigalg = lu;
    return 1;
}


size_t ssl_hmac_size(const SSL_HMAC *ctx)
{
    if (ctx->ctx != NULL)
        return EVP_MAC_size(ctx->ctx);
#ifndef OPENSSL_NO_DEPRECATED_3_0
    if (ctx->old_ctx != NULL)
        return HMAC_size(ctx->old_ctx);
#endif
    return 0;
}


void Context::onUpstreamConnectionClose(PeerType peer_type) {
  if (in_vm_context_created_ && wasm_->onUpstreamConnectionClose_) {
    wasm_->onUpstreamConnectionClose_(this, id_, static_cast<uint32_t>(peer_type));
  }
}


void Wasm::queueReady(uint32_t root_context_id, uint32_t token) {
  auto it = contexts_.find(root_context_id);
  if (it == contexts_.end() || !it->second->isRootContext()) {
    return;
  }
  it->second->onQueueReady(token);
}


Context* ContextOrEffectiveContext(Context* context) {
  if (effective_context_id_ == 0) {
    return context;
  }
  auto effective_context = context->wasm()->getContext(effective_context_id_);
  if (effective_context) {
    return effective_context;
  }
  // The effective_context_id_ no longer exists, revert to the true context.
  return context;
}


Http::FilterDataStatus Context::encodeData(Buffer::Instance& data, bool end_stream) {
  responseBodyBuffer_ = &data;
  auto result = onResponseBody(data.length(), end_stream);
  responseBodyBuffer_ = nullptr;
  return result;
}


Network::FilterStatus Context::onNewConnection() { return onNetworkNewConnection(); };


Word getSharedDataHandler(void* raw_context, Word key_ptr, Word key_size, Word value_ptr_ptr,
                          Word value_size_ptr, Word cas_ptr) {
  auto context = WASM_CONTEXT(raw_context);
  auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_);
  if (!key) {
    return wasmResultToWord(WasmResult::InvalidMemoryAccess);
  }
  std::pair<std::string, uint32_t> data;
  WasmResult result = context->getSharedData(key.value(), &data);
  if (result != WasmResult::Ok) {
    return wasmResultToWord(result);
  }
  if (!context->wasm()->copyToPointerSize(data.first, value_ptr_ptr.u64_, value_size_ptr.u64_)) {
    return wasmResultToWord(WasmResult::InvalidMemoryAccess);
  }
  if (!context->wasmVm()->setMemory(cas_ptr.u64_, sizeof(uint32_t), &data.second)) {
    return wasmResultToWord(WasmResult::InvalidMemoryAccess);
  }
  return wasmResultToWord(WasmResult::Ok);
}


absl::string_view root_id() const { return plugin_->root_id_; }


WasmResult serializeValue(Filters::Common::Expr::CelValue value, std::string* result) {
  using Filters::Common::Expr::CelValue;
  switch (value.type()) {
  case CelValue::Type::kMessage:
    if (value.MessageOrDie() != nullptr && value.MessageOrDie()->SerializeToString(result)) {
      return WasmResult::Ok;
    }
    return WasmResult::SerializationFailure;
  case CelValue::Type::kString:
    result->assign(value.StringOrDie().value().data(), value.StringOrDie().value().size());
    return WasmResult::Ok;
  case CelValue::Type::kBytes:
    result->assign(value.BytesOrDie().value().data(), value.BytesOrDie().value().size());
    return WasmResult::Ok;
  case CelValue::Type::kInt64: {
    auto out = value.Int64OrDie();
    result->assign(reinterpret_cast<const char*>(&out), sizeof(int64_t));
    return WasmResult::Ok;
  }
  case CelValue::Type::kUint64: {
    auto out = value.Uint64OrDie();
    result->assign(reinterpret_cast<const char*>(&out), sizeof(uint64_t));
    return WasmResult::Ok;
  }
  case CelValue::Type::kDouble: {
    auto out = value.DoubleOrDie();
    result->assign(reinterpret_cast<const char*>(&out), sizeof(double));
    return WasmResult::Ok;
  }
  case CelValue::Type::kBool: {
    auto out = value.BoolOrDie();
    result->assign(reinterpret_cast<const char*>(&out), sizeof(bool));
    return WasmResult::Ok;
  }
  case CelValue::Type::kDuration: {
    auto out = value.DurationOrDie();
    result->assign(reinterpret_cast<const char*>(&out), sizeof(absl::Duration));
    return WasmResult::Ok;
  }
  case CelValue::Type::kTimestamp: {
    auto out = value.TimestampOrDie();
    result->assign(reinterpret_cast<const char*>(&out), sizeof(absl::Time));
    return WasmResult::Ok;
  }
  case CelValue::Type::kMap: {
    ProtobufWkt::Value out;
    if (!exportValue(value, &out)) {
      return WasmResult::SerializationFailure;
    }
    if (!out.struct_value().SerializeToString(result)) {
      return WasmResult::SerializationFailure;
    }
    return WasmResult::Ok;
  }
  case CelValue::Type::kList: {
    ProtobufWkt::Value out;
    if (!exportValue(value, &out)) {
      return WasmResult::SerializationFailure;
    }
    if (!out.list_value().SerializeToString(result)) {
      return WasmResult::SerializationFailure;
    }
    return WasmResult::Ok;
  }
  default:
    return WasmResult::SerializationFailure;
  }
  return WasmResult::SerializationFailure;
}


  bool isHistogramMetricId(uint32_t metric_id) {
    return (metric_id & kMetricTypeMask) == kMetricTypeHistogram;
  }


Context* Wasm::start() {
  auto root_id = plugin_->root_id_;
  auto it = root_contexts_.find(root_id);
  if (it != root_contexts_.end()) {
    it->second->onStart(root_id, vm_configuration());
    return it->second.get();
  }
  auto context = std::make_unique<Context>(this, root_id, plugin_);
  auto context_ptr = context.get();
  root_contexts_[root_id] = std::move(context);
  context_ptr->onStart(root_id, vm_configuration());
  return context_ptr;
};


Word setEffectiveContextHandler(void* raw_context, Word context_id) {
  auto context = WASM_CONTEXT(raw_context);
  uint32_t cid = static_cast<uint32_t>(context_id.u64_);
  auto c = context->wasm()->getContext(cid);
  if (!c) {
    return wasmResultToWord(WasmResult::BadArgument);
  }
  effective_context_id_ = cid;
  return wasmResultToWord(WasmResult::Ok);
}