#region PDFsharp - A .NET library for processing PDF
//
// Authors:
//   Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)
//
// Copyright (c) 2005-2007 empira Software GmbH, Cologne (Germany)
//
// http://www.pdfsharp.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Diagnostics;
using System.Globalization;
using System.Collections;
using System.Text;
using System.IO;
#if Gdip
using System.Drawing;
using System.Drawing.Drawing2D;
#endif
#if Wpf
using System.Windows.Media;
#endif
using PdfSharp.Internal;
using PdfSharp.Pdf;
using PdfSharp.Fonts;
using PdfSharp.Fonts.TrueType;
using PdfSharp.Pdf.Internal;
using PdfSharp.Pdf.Advanced;

namespace PdfSharp.Drawing.Pdf
{
  /// <summary>
  /// Indicates whether we are within a BT/ET block.
  /// </summary>
  enum StreamMode
  {
    /// <summary>
    /// Graphic mode. This is default.
    /// </summary>
    Graphic,

    /// <summary>
    /// Text mode.
    /// </summary>
    Text,
  }

  [Flags]
  enum DirtyFlags
  {
    Ctm = 0x00000001,
    ClipPath = 0x00000002,
    LineWidth = 0x00000010,
    LineJoin = 0x00000020,
    MiterLimit = 0x00000040,
    StrokeFill = 0x00000070,
  }

  /// <summary>
  /// Represents a drawing surface for PdfPages.
  /// </summary>
  public class XGraphicsPdfRenderer : IXGraphicsRenderer
  {
    enum PathStart
    {
      MoveTo1st, LineTo1st, Ignore1st,
    }

    public XGraphicsPdfRenderer(PdfPage page, XGraphics gfx, XGraphicsPdfPageOptions options)
    {
      this.page = page;
      this.options = options;
#if MIGRADOC
      this.options = options & ~XGraphicsPdfPageOptions.PDFlibHack;
      pdflibHack = (options & XGraphicsPdfPageOptions.PDFlibHack) != 0;
#endif
      this.gfx = gfx;
      this.content = new StringBuilder();
      page.RenderContent.pdfRenderer = this;
      this.gfxState = new PdfGraphicsState(this);
    }
#if MIGRADOC
    bool pdflibHack;
#endif

    public XGraphicsPdfRenderer(XForm form, XGraphics gfx)
    {
      this.form = form;
      this.gfx = gfx;
      this.content = new StringBuilder();
      form.pdfRenderer = this;
      this.gfxState = new PdfGraphicsState(this);
    }

    /// <summary>
    /// Gets the content created by this renderer.
    /// </summary>
    string GetContent()
    {
      EndPage();
      return this.content.ToString();
    }

    public XGraphicsPdfPageOptions PageOptions
    {
      get { return this.options; }
    }

    public void Close()
    {
      if (this.page != null)
      {
        PdfContent content = page.RenderContent;
        content.CreateStream(PdfEncoders.RawEncoding.GetBytes(GetContent()));

        this.gfx = null;
        this.page.RenderContent.pdfRenderer = null;
        this.page.RenderContent = null;
        this.page = null;
      }
      else if (this.form != null)
      {
        this.form.pdfForm.CreateStream(PdfEncoders.RawEncoding.GetBytes(GetContent()));
        this.gfx = null;
        this.form.pdfRenderer = null;
        this.form = null;
      }
    }

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

    #region  Drawing

    //void SetPageLayout(down, point(0, 0), unit

    // ----- Clear --------------------------------------------------------------------------------

    public void Clear(XColor color)
    {
      if (!this.gfx.Transform.IsIdentity)
        throw new NotImplementedException("Transform must be identity to clear the canvas.");

      // TODO: this is implementation is bogus. Reset transformation to identity an then fill
      XBrush brush = new XSolidBrush(color);
      DrawRectangle(null, brush, 0, 0, Size.Width, Size.Height);
    }

    // ----- DrawLine -----------------------------------------------------------------------------

    /// <summary>
    /// Strokes a single connection of two points.
    /// </summary>
    public void DrawLine(XPen pen, double x1, double y1, double x2, double y2)
    {
      DrawLines(pen, new XPoint[2] { new XPoint(x1, y1), new XPoint(x2, y2) });
    }

    // ----- DrawLines ----------------------------------------------------------------------------

    /// <summary>
    /// Strokes a series of connected points.
    /// </summary>
    public void DrawLines(XPen pen, XPoint[] points)
    {
      if (pen == null)
        throw new ArgumentNullException("pen");
      if (points == null)
        throw new ArgumentNullException("points");

      int count = points.Length;
      if (count == 0)
        return;

      Realize(pen);

      AppendFormat("{0:0.###} {1:0.###} m\n", points[0].X, points[0].Y);
      for (int idx = 1; idx < count; idx++)
        AppendFormat("{0:0.###} {1:0.###} l\n", points[idx].X, points[idx].Y);
      this.content.Append("S\n");
    }

    // ----- DrawBezier ---------------------------------------------------------------------------

    public void DrawBezier(XPen pen, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
    {
      DrawBeziers(pen, new XPoint[4] { new XPoint(x1, y1), new XPoint(x2, y2), new XPoint(x3, y3), new XPoint(x4, y4) });
    }

    // ----- DrawBeziers --------------------------------------------------------------------------

    public void DrawBeziers(XPen pen, XPoint[] points)
    {
      if (pen == null)
        throw new ArgumentNullException("pen");
      if (points == null)
        throw new ArgumentNullException("points");

      int count = points.Length;
      if (count == 0)
        return;

      if ((count - 1) % 3 != 0)
        throw new ArgumentException("Invalid number of points for bezier curves. Number must fulfil 4+3n.", "points");

      Realize(pen);

      AppendFormat("{0:0.###} {1:0.###} m\n", points[0].X, points[0].Y);
      for (int idx = 1; idx < count; idx += 3)
        AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
          points[idx].X, points[idx].Y,
          points[idx + 1].X, points[idx + 1].Y,
          points[idx + 2].X, points[idx + 2].Y);

      AppendStrokeFill(pen, null, XFillMode.Alternate, false);
    }

    // ----- DrawCurve ----------------------------------------------------------------------------

    public void DrawCurve(XPen pen, XPoint[] points, double tension)
    {
      if (pen == null)
        throw new ArgumentNullException("pen");
      if (points == null)
        throw new ArgumentNullException("points");

      int count = points.Length;
      if (count == 0)
        return;
      if (count < 2)
        throw new ArgumentException("Not enough points", "points");

      // See http://pubpages.unh.edu/~cs770/a5/cardinal.html
      tension /= 3;

      Realize(pen);

      AppendFormat("{0:0.###} {1:0.###} m\n", points[0].X, points[0].Y);
      if (count == 2)
      {
        // Just draws a line...
        AppendCurveSegment(points[0], points[0], points[1], points[1], tension);
      }
      else
      {
        AppendCurveSegment(points[0], points[0], points[1], points[2], tension);
        for (int idx = 1; idx < count - 2; idx++)
          AppendCurveSegment(points[idx - 1], points[idx], points[idx + 1], points[idx + 2], tension);
        AppendCurveSegment(points[count - 3], points[count - 2], points[count - 1], points[count - 1], tension);
      }
      AppendStrokeFill(pen, null, XFillMode.Alternate, false);
    }

    // ----- DrawArc ------------------------------------------------------------------------------

    public void DrawArc(XPen pen, double x, double y, double width, double height, double startAngle, double sweepAngle)
    {
      if (pen == null)
        throw new ArgumentNullException("pen");

      Realize(pen);

      AppendPartialArc(x, y, width, height, startAngle, sweepAngle, PathStart.MoveTo1st);
      AppendStrokeFill(pen, null, XFillMode.Alternate, false);
    }

    // ----- DrawRectangle ------------------------------------------------------------------------

    public void DrawRectangle(XPen pen, XBrush brush, double x, double y, double width, double height)
    {
      if (pen == null && brush == null)
        throw new ArgumentNullException("pen and brush");

      Realize(pen, brush);
      AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} re\n", x, y, width, height);

      if (pen != null && brush != null)
        this.content.Append("B\n");
      else if (pen != null)
        this.content.Append("S\n");
      else
        this.content.Append("f\n");
    }

    // ----- DrawRectangles -----------------------------------------------------------------------

    public void DrawRectangles(XPen pen, XBrush brush, XRect[] rects)
    {
      int count = rects.Length;
      for (int idx = 0; idx < count; idx++)
      {
        XRect rect = rects[idx];
        DrawRectangle(pen, brush, rect.X, rect.Y, rect.Width, rect.Height);
      }
    }

    // ----- DrawRoundedRectangle -----------------------------------------------------------------

    public void DrawRoundedRectangle(XPen pen, XBrush brush, double x, double y, double width, double height, double ellipseWidth, double ellipseHeight)
    {
      XGraphicsPath path = new XGraphicsPath();
      path.AddRoundedRectangle(x, y, width, height, ellipseWidth, ellipseHeight);
      DrawPath(pen, brush, path);
    }

    // ----- DrawEllipse --------------------------------------------------------------------------

    public void DrawEllipse(XPen pen, XBrush brush, double x, double y, double width, double height)
    {
      Realize(pen, brush);

      // Useful information are here http://home.t-online.de/home/Robert.Rossmair/ellipse.htm
      // or here http://www.whizkidtech.redprince.net/bezier/circle/
      // Deeper but more difficult: http://www.tinaja.com/cubic01.asp
      // Petzold: 4/3 * tan(α / 4)
      const double κ = 0.552284749f;  // := 4/3 * (1 - cos(-π/4)) / sin(π/4)) <=> 4/3 * sqrt(2) - 1
      XRect rect = new XRect(x, y, width, height);
      double δx = rect.Width / 2;
      double δy = rect.Height / 2;
      double fx = δx * κ;
      double fy = δy * κ;
      double x0 = rect.X + δx;
      double y0 = rect.Y + δy;

      AppendFormat("{0:0.###} {1:0.###} m\n", x0 + δx, y0);
      AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
        x0 + δx, y0 + fy, x0 + fx, y0 + δy, x0, y0 + δy);
      AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
        x0 - fx, y0 + δy, x0 - δx, y0 + fy, x0 - δx, y0);
      AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
        x0 - δx, y0 - fy, x0 - fx, y0 - δy, x0, y0 - δy);
      AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
        x0 + fx, y0 - δy, x0 + δx, y0 - fy, x0 + δx, y0);
      AppendStrokeFill(pen, brush, XFillMode.Winding, true);
    }

    // ----- DrawPolygon --------------------------------------------------------------------------

    public void DrawPolygon(XPen pen, XBrush brush, XPoint[] points, XFillMode fillmode)
    {
      Realize(pen, brush);

      int count = points.Length;
      if (points.Length < 2)
        throw new ArgumentException("points", PSSR.PointArrayAtLeast(2));

      AppendFormat("{0:0.###} {1:0.###} m\n", points[0].X, points[0].Y);
      for (int idx = 1; idx < count; idx++)
        AppendFormat("{0:0.###} {1:0.###} l\n", points[idx].X, points[idx].Y);

      AppendStrokeFill(pen, brush, fillmode, true);
    }

    // ----- DrawPie ------------------------------------------------------------------------------

    public void DrawPie(XPen pen, XBrush brush, double x, double y, double width, double height,
      double startAngle, double sweepAngle)
    {
      Realize(pen, brush);

      AppendFormat("{0:0.###} {1:0.###} m\n", x + width / 2, y + height / 2);
      AppendPartialArc(x, y, width, height, startAngle, sweepAngle, PathStart.LineTo1st);
      AppendStrokeFill(pen, brush, XFillMode.Alternate, true);
    }

    // ----- DrawClosedCurve ----------------------------------------------------------------------

    public void DrawClosedCurve(XPen pen, XBrush brush, XPoint[] points, double tension, XFillMode fillmode)
    {
      int count = points.Length;
      if (count == 0)
        return;
      if (count < 2)
        throw new ArgumentException("Not enough points", "points");

      // Simply tried out. Not proofed why it is correct.
      tension /= 3;

      Realize(pen, brush);

      AppendFormat("{0:0.###} {1:0.###} m\n", points[0].X, points[0].Y);
      if (count == 2)
      {
        // Just draws a line...
        AppendCurveSegment(points[0], points[0], points[1], points[1], tension);
      }
      else
      {
        AppendCurveSegment(points[count - 1], points[0], points[1], points[2], tension);
        for (int idx = 1; idx < count - 2; idx++)
          AppendCurveSegment(points[idx - 1], points[idx], points[idx + 1], points[idx + 2], tension);
        AppendCurveSegment(points[count - 3], points[count - 2], points[count - 1], points[0], tension);
        AppendCurveSegment(points[count - 2], points[count - 1], points[0], points[1], tension);
      }
      AppendStrokeFill(pen, brush, fillmode, true);
    }

    // ----- DrawPath -----------------------------------------------------------------------------

    public void DrawPath(XPen pen, XBrush brush, XGraphicsPath path)
    {
      if (pen == null && brush == null)
        throw new ArgumentNullException("pen and brush");

      Realize(pen, brush);
      AppendPath(path.gdipPath);
      AppendStrokeFill(pen, brush, path.FillMode, false);
    }

    // ----- DrawString ---------------------------------------------------------------------------

    public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFormat format)
    {
      Realize(font, brush, 0);

      double x = rect.X;
      double y = rect.Y;

      double lineSpace = font.GetHeight(this.gfx);
      //int cellSpace = font.cellSpace; // font.FontFamily.GetLineSpacing(font.Style);
      //int cellAscent = font.cellAscent; // font.FontFamily.GetCellAscent(font.Style);
      //int cellDescent = font.cellDescent; // font.FontFamily.GetCellDescent(font.Style);
      //double cyAscent = lineSpace * cellAscent / cellSpace;
      //double cyDescent = lineSpace * cellDescent / cellSpace;
      double cyAscent = lineSpace * font.cellAscent / font.cellSpace;
      double cyDescent = lineSpace * font.cellDescent / font.cellSpace;
      double width = this.gfx.MeasureString(s, font).Width;

      bool strikeout = (font.Style & XFontStyle.Strikeout) != 0;
      bool underline = (font.Style & XFontStyle.Underline) != 0;

      switch (format.Alignment)
      {
        case XStringAlignment.Near:
          // nothing to do
          break;

        case XStringAlignment.Center:
          x += (rect.Width - width) / 2;
          break;

        case XStringAlignment.Far:
          x += rect.Width - width;
          break;
      }
      if (Gfx.PageDirection == XPageDirection.Downwards)
      {
        switch (format.LineAlignment)
        {
          case XLineAlignment.Near:
            y += cyAscent;
            break;

          case XLineAlignment.Center:
            // TODO use CapHeight. PDFlib also uses 3/4 of ascent
            y += (cyAscent * 3 / 4) / 2 + rect.Height / 2;
            break;

          case XLineAlignment.Far:
            y += -cyDescent + rect.Height;
            break;

          case XLineAlignment.BaseLine:
            // nothing to do
            break;
        }
      }
      else
      {
        switch (format.LineAlignment)
        {
          case XLineAlignment.Near:
            y += cyDescent;
            break;

          case XLineAlignment.Center:
            // TODO use CapHeight. PDFlib also uses 3/4 of ascent
            y += -(cyAscent * 3 / 4) / 2 + rect.Height / 2;
            break;

          case XLineAlignment.Far:
            y += -cyAscent + rect.Height;
            break;

          case XLineAlignment.BaseLine:
            // nothing to do
            break;
        }
      }

      PdfFont realizedFont = this.gfxState.realizedFont;
      Debug.Assert(realizedFont != null);
      realizedFont.AddChars(s);
      if (font.Unicode)
      {
        TrueTypeDescriptor descriptor = ((PdfType0Font)realizedFont).FontDescriptor.descriptor;
        string s2 = "";
        for (int idx = 0; idx < s.Length; idx++)
        {
          char ch = s[idx];
          int glyphID = descriptor.CharCodeToGlyphIndex(ch);
          s2 += (char)glyphID;
        }
        s = s2;

        byte[] bytes = PdfEncoders.RawUnicodeEncoding.GetBytes(s);
        bytes = PdfEncoders.FormatStringLiteral(bytes, true, false, true, null);
        string text = PdfEncoders.RawEncoding.GetString(bytes);
        XPoint pos = new XPoint(x, y);
        AdjustTextMatrix(ref pos);
        AppendFormat(
          "{0:0.###} {1:0.###} Td {2} Tj\n", pos.x, pos.y, text);
          //PdfEncoders.ToStringLiteral(s, PdfStringEncoding.RawEncoding, null));
      }
      else
      {
        byte[] bytes = PdfEncoders.WinAnsiEncoding.GetBytes(s);
        XPoint pos = new XPoint(x, y);
        AdjustTextMatrix(ref pos);
        AppendFormat(
          "{0:0.###} {1:0.###} Td {2} Tj\n", pos.x, pos.y,
          PdfEncoders.ToStringLiteral(bytes, false, null));
      }

      // The world wanted this feature...
      if (strikeout || underline)
      {
        double underlineThickness = lineSpace * realizedFont.FontDescriptor.descriptor.UnderlineThickness / font.cellSpace;
        double strikeoutThickness = underlineThickness * 0.8;
        XColor color = XColors.Black;
        
        if (brush is XSolidBrush)
          color = ((XSolidBrush)brush).Color;

        if (underline)
        {
          // UnderlinePosition is a negative value
          y -= lineSpace * realizedFont.FontDescriptor.descriptor.UnderlinePosition / font.cellSpace;
          DrawLine(new XPen(color, underlineThickness), x, y, x + width, y);
        }

        if (strikeout)
        {
          // I don't know how to calculate the strikeout position
          y -= cyAscent * 0.5;
          DrawLine(new XPen(color, strikeoutThickness), x, y, x + width, y);
        }
      }
    }

    // ----- DrawImage ----------------------------------------------------------------------------

    //public void DrawImage(Image image, Point point);
    //public void DrawImage(Image image, PointF point);
    //public void DrawImage(Image image, Point[] destPoints);
    //public void DrawImage(Image image, PointF[] destPoints);
    //public void DrawImage(Image image, Rectangle rect);
    //public void DrawImage(Image image, RectangleF rect);
    //public void DrawImage(Image image, int x, int y);
    //public void DrawImage(Image image, float x, float y);
    //public void DrawImage(Image image, Point[] destPoints, Rectangle srcRect, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, Rectangle destRect, Rectangle srcRect, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, PointF[] destPoints, RectangleF srcRect, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, int x, int y, Rectangle srcRect, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, float x, float y, RectangleF srcRect, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, Point[] destPoints, Rectangle srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr);
    //public void DrawImage(Image image, PointF[] destPoints, RectangleF srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr);
    //public void DrawImage(Image image, int x, int y, int width, int height);
    //public void DrawImage(Image image, float x, float y, float width, float height);
    //public void DrawImage(Image image, Point[] destPoints, Rectangle srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr, DrawImageAbort callback);
    //public void DrawImage(Image image, PointF[] destPoints, RectangleF srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr, DrawImageAbort callback);
    //public void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit);
    //public void DrawImage(Image image, Point[] destPoints, Rectangle srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr, DrawImageAbort callback, int callbackData);
    //public void DrawImage(Image image, PointF[] destPoints, RectangleF srcRect, GraphicsUnit srcUnit, ImageAttributes imageAttr, DrawImageAbort callback, int callbackData);
    //public void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttr);
    //public void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs);
    //public void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttr, DrawImageAbort callback);
    //public void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs, DrawImageAbort callback);
    //public void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs, DrawImageAbort callback, IntPtr callbackData);
    //public void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit, ImageAttributes

    public void DrawImage(XImage image, double x, double y, double width, double height)
    {
      string name = Realize(image);
      if (!(image is XForm))
      {
        if (this.gfx.PageDirection == XPageDirection.Downwards)
        {
          AppendFormat("q {2:0.###} 0 0 -{3:0.###} {0:0.###} {4:0.###} cm {5} Do Q\n",
            x, y, width, height, y + height, name);
        }
        else
        {
          AppendFormat("q {2:0.###} 0 0 {3:0.###} {0:0.###} {1:0.###} cm {4} Do Q\n",
            x, y, width, height, name);
        }
      }
      else
      {
        BeginPage();

        XForm form = (XForm)image;
        form.Finish();

        PdfFormXObject pdfForm = Owner.FormTable.GetForm(form);

        double cx = width / image.Width;
        double cy = height / image.Height;

        if (cx != 0 && cy != 0)
        {
          if (this.gfx.PageDirection == XPageDirection.Downwards)
          {
            AppendFormat("q {2:0.###} 0 0 -{3:0.###} {0:0.###} {4:0.###} cm 100 Tz {5} Do Q\n",
              x, y, cx, cy, y + height, name);
          }
          else
          {
            AppendFormat("q {2:0.###} 0 0 {3:0.###} {0:0.###} {1:0.###} cm {4} Do Q\n",
              x, y, cx, cy, name);
          }
        }
      }
    }

    // TODO: incomplete - srcRect not used
    public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit srcUnit)
    {
      double x = destRect.X;
      double y = destRect.Y;
      double width = destRect.Width;
      double height = destRect.Height;

      string name = Realize(image);
      if (!(image is XForm))
      {
        if (this.gfx.PageDirection == XPageDirection.Downwards)
        {
          AppendFormat("q {2:0.###} 0 0 -{3:0.###} {0:0.###} {4:0.###} cm {5} Do\nQ\n",
            x, y, width, height, y + height, name);
        }
        else
        {
          AppendFormat("q {2:0.###} 0 0 {3:0.###} {0:0.###} {1:0.###} cm {4} Do Q\n",
            x, y, width, height, name);
        }
      }
      else
      {
        BeginPage();

        XForm form = (XForm)image;
        form.Finish();

        PdfFormXObject pdfForm = Owner.FormTable.GetForm(form);

        double cx = width / image.Width;
        double cy = height / image.Height;

        if (cx != 0 && cy != 0)
        {
          if (this.gfx.PageDirection == XPageDirection.Downwards)
          {
            AppendFormat("q {2:0.###} 0 0 -{3:0.###} {0:0.###} {4:0.###} cm 100 Tz {5} Do Q\n",
              x, y, cx, cy, y + height, name);
          }
          else
          {
            AppendFormat("q {2:0.###} 0 0 {3:0.###} {0:0.###} {1:0.###} cm {4} Do Q\n",
              x, y, cx, cy, name);
          }
        }
      }
    }

    #endregion

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

    #region Save and Restore

    /// <summary>
    /// Clones the current graphics state and push it on a stack.
    /// </summary>
    public void Save(XGraphicsState state)
    {
      // Before saving, the current transformation matrix must be completely realized.
      BeginGraphic();
      RealizeTransform();
      // Associate the XGraphicsState with the current PdgGraphicsState.
      this.gfxState.InternalState = state.InternalState;
      SaveState();
    }

    public void Restore(XGraphicsState state)
    {
      BeginGraphic();
      RestoreState(state.InternalState);
    }

    public void BeginContainer(XGraphicsContainer container, XRect dstrect, XRect srcrect, XGraphicsUnit unit)
    {
      // Before saving, the current transformation matrix must be completely realized.
      BeginGraphic();
      RealizeTransform();
      this.gfxState.InternalState = container.InternalState;
      SaveState();
      //throw new NotImplementedException("BeginContainer");
      //      PdfGraphicsState pdfstate = (PdfGraphicsState)this.gfxState.Clone();
      //      this.gfxStateStack.Push(pdfstate);
      //      container.Handle = pdfstate;
      //      container.Handle = this.gfxState.Clone();

      //      Matrix matrix = new Matrix();
      //      matrix.Translate(srcrect.X, srcrect.Y);
      //      matrix.Scale(dstrect.Width / srcrect.Width, dstrect.Height / srcrect.Height);
      //      matrix.Translate(dstrect.X, dstrect.Y);
      //      Transform = matrix;
    }

    public void EndContainer(XGraphicsContainer container)
    {
      BeginGraphic();
      RestoreState(container.InternalState);
      //      PdfGraphicsState pdfstate = (PdfGraphicsState)container.Handle;
      //      this.dirty |= DirtyFlags.Ctm;
      //      this.gfxState = pdfstate;
    }

    #endregion

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

    #region Transformation

    public void SetPageTransform(XPageDirection direction, XPoint origion, XGraphicsUnit unit)
    {
      if (this.gfxStateStack.Count > 0)
        throw new InvalidOperationException("PageTransformation can be modified only when the graphics stack is empty.");

      throw new NotImplementedException("SetPageTransform");
    }

    //public void TranslateTransform(double dx, double dy, XMatrixOrder order)
    //{
    //}
    //
    //public void ScaleTransform(double scaleX, double scaleY, XMatrixOrder order)
    //{
    //}
    //
    //public void ScaleTransform(double scaleXY, XMatrixOrder order)
    //{
    //}
    //
    //public void RotateTransform(double angle, XMatrixOrder order)
    //{
    //}
    //
    //public void MultiplyTransform(XMatrix matrix, XMatrixOrder order)
    //{
    //}

    public XMatrix Transform
    {
      //get {return this.gfxState.Ctm;}
      set { this.gfxState.Transform = value; }
    }

    #endregion

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

    #region Clipping

    public void SetClip(XGraphicsPath path, XCombineMode combineMode)
    {
      if (path == null)
        throw new NotImplementedException("SetClip with no path.");

      // Ensure that the graphics state stack level is at least 2, because otherwise an error
      // occurs when someone set the clip region before something was drawn.
      if (this.gfxState.Level < GraphicsStackLevelWorldSpace)
        RealizeTransform();  // TODO: refactor this function

      if (combineMode == XCombineMode.Replace)
      {
        if (this.clipLevel != 0)
        {
          if (this.clipLevel != this.gfxState.Level)
            throw new NotImplementedException("Cannot set new clip region in an inner graphic state level.");
          else
            ResetClip();
        }
        this.clipLevel = this.gfxState.Level;
      }
      else if (combineMode == XCombineMode.Intersect)
      {
        if (this.clipLevel == 0)
          this.clipLevel = this.gfxState.Level;
      }
      else
      {
        Debug.Assert(false, "Invalid XCombineMode in internal function.");
      }
      this.gfxState.SetAndRealizeClipPath(path);
    }

    /// <summary>
    /// Sets the clip path empty. Only possible if graphic state level has the same value as it has when
    /// the first time SetClip was invoked.
    /// </summary>
    public void ResetClip()
    {
      // No clip level means no clipping occurs and nothing is to do.
      if (this.clipLevel == 0)
        return;

      // Only at the clipLevel the clipping can be reset.
      if (this.clipLevel != this.gfxState.Level)
        throw new NotImplementedException("Cannot reset clip region in an inner graphic state level.");

      // Must be in graphical mode before popping the graphics state.
      BeginGraphic();

      // Save InternalGraphicsState and transformation of the current graphical state.
      InternalGraphicsState state = this.gfxState.InternalState;
      XMatrix ctm = this.gfxState.Transform;
      // Empty clip path by switching back the the previous state.
      RestoreState();
      SaveState();
      // Save internal state
      this.gfxState.InternalState = state;
      // Restore CTM
      this.gfxState.Transform = ctm;
    }

    /// <summary>
    /// The nesting level of the PDF graphics state stack when the clip region was set to non empty.
    /// Because of the way PDF is made the clip region can only be reset at this level.
    /// </summary>
    int clipLevel;

    #endregion

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

    #region Miscellaneous

    /// <summary>
    /// Writes a comment to the PDF content stream. May be useful for debugging purposes.
    /// </summary>
    public void WriteComment(string comment)
    {
      comment.Replace("\n", "\n% ");
      // TODO: Some more checks neccessary?
      Append("% " + comment + "\n");
    }

    #endregion

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

    #region Append to PDF stream

    /// <summary>
    /// Appends one or up to five Bézier curves that interpolate the arc.
    /// </summary>
    void AppendPartialArc(double x, double y, double width, double height, double startAngle, double sweepAngle,
      PathStart pathStart)
    {
      // Normalize the angles
      double α = startAngle;
      if (α < 0)
        α = α + (1 + Math.Floor((Math.Abs(α) / 360))) * 360;
      else if (α > 360)
        α = α - Math.Floor(α / 360) * 360;
      Debug.Assert(α >= 0 && α <= 360);

      double β = sweepAngle;
      if (β < -360)
        β = -360;
      else if (β > 360)
        β = 360;

      if (α == 0 && β < 0)
        α = 360;
      else if (α == 360 && β > 0)
        α = 0;

      // Is it possible that the arc is small starts and ends in same quadrant?
      bool smallAngle = Math.Abs(β) <= 90;

      β = α + β;
      if (β < 0)
        β = β + (1 + Math.Floor((Math.Abs(β) / 360))) * 360;

      bool clockwise = sweepAngle > 0;
      int startQuadrant = Quatrant(α, true, clockwise);
      int endQuadrant = Quatrant(β, false, clockwise);

      if (startQuadrant == endQuadrant && smallAngle)
        AppendPartialArcQuadrant(x, y, width, height, α, β, pathStart);
      else
      {
        int currentQuadrant = startQuadrant;
        bool firstLoop = true;
        do
        {
          if (currentQuadrant == startQuadrant && firstLoop)
          {
            double ξ = currentQuadrant * 90 + (clockwise ? 90 : 0);
            AppendPartialArcQuadrant(x, y, width, height, α, ξ, pathStart);
          }
          else if (currentQuadrant == endQuadrant)
          {
            double ξ = currentQuadrant * 90 + (clockwise ? 0 : 90);
            AppendPartialArcQuadrant(x, y, width, height, ξ, β, PathStart.Ignore1st);
          }
          else
          {
            double ξ1 = currentQuadrant * 90 + (clockwise ? 0 : 90);
            double ξ2 = currentQuadrant * 90 + (clockwise ? 90 : 0);
            AppendPartialArcQuadrant(x, y, width, height, ξ1, ξ2, PathStart.Ignore1st);
          }

          // Don't stop immediately if arc is greater than 270 degrees
          if (currentQuadrant == endQuadrant && smallAngle)
            break;
          else
            smallAngle = true;

          if (clockwise)
            currentQuadrant = currentQuadrant == 3 ? 0 : currentQuadrant + 1;
          else
            currentQuadrant = currentQuadrant == 0 ? 3 : currentQuadrant - 1;

          firstLoop = false;
        } while (true);
      }
    }

    /// <summary>
    /// Gets the quadrant (0 through 3) of the specified angle. If the angle lies on an edge
    /// (0, 90, 180, etc.) the result depends on the details how the angle is used.
    /// </summary>
    int Quatrant(double φ, bool start, bool clockwise)
    {
      Debug.Assert(φ >= 0);
      if (φ > 360)
        φ = φ - Math.Floor(φ / 360) * 360;

      int quadrant = (int)(φ / 90);
      if (quadrant * 90 == φ)
      {
        if ((start && !clockwise) || (!start && clockwise))
          quadrant = quadrant == 0 ? 3 : quadrant - 1;
      }
      else
        quadrant = clockwise ? ((int)Math.Floor(φ / 90)) % 4 : (int)Math.Floor(φ / 90);
      return quadrant;
    }

    /// <summary>
    /// Appends a Bézier curve for an arc within a quadrant.
    /// </summary>
    void AppendPartialArcQuadrant(double x, double y, double width, double height, double α, double β, PathStart pathStart)
    {
      Debug.Assert(α >= 0 && α <= 360);
      Debug.Assert(β >= 0);
      if (β > 360)
        β = β - Math.Floor(β / 360) * 360;
      Debug.Assert(Math.Abs(α - β) <= 90);

      // Scanling factor
      double δx = width / 2;
      double δy = height / 2;

      // Center of ellipse
      double x0 = x + δx;
      double y0 = y + δy;

      // We have the following quarters:
      //     |
      //   2 | 3
      // ----+-----
      //   1 | 0
      //     |
      // If the angles lie in quarter 2 or 3, their values are subtracted by 180 and the
      // resulting curve is reflected at the center. This algorythm works as expected (simply tried out).
      // There may be a mathematical more elegant solution...
      bool reflect = false;
      if (α >= 180 && β >= 180)
      {
        α -= 180;
        β -= 180;
        reflect = true;
      }

      double cosα, cosβ, sinα, sinβ;
      if (width == height)
      {
        // Circular arc needs no correction.
        α = α * Calc.Deg2Rad;
        β = β * Calc.Deg2Rad;
      }
      else
      {
        // Elliptic arc needs the angles to be adjusted such that the scaling transformation is compensated.
        α = α * Calc.Deg2Rad;
        sinα = Math.Sin(α);
        if (Math.Abs(sinα) > 1E-10)
          α = Calc.πHalf - Math.Atan(δy * Math.Cos(α) / (δx * sinα));
        β = β * Calc.Deg2Rad;
        sinβ = Math.Sin(β);
        if (Math.Abs(sinβ) > 1E-10)
          β = Calc.πHalf - Math.Atan(δy * Math.Cos(β) / (δx * sinβ));
      }

      double κ = 4 * (1 - Math.Cos((α - β) / 2)) / (3 * Math.Sin((β - α) / 2));
      sinα = Math.Sin(α);
      cosα = Math.Cos(α);
      sinβ = Math.Sin(β);
      cosβ = Math.Cos(β);

      if (!reflect)
      {
        // Calculation for quarter 0 and 1
        switch (pathStart)
        {
          case PathStart.MoveTo1st:
            AppendFormat("{0:0.###} {1:0.###} m\n", x0 + δx * cosα, y0 + δy * sinα);
            break;

          case PathStart.LineTo1st:
            AppendFormat("{0:0.###} {1:0.###} l\n", x0 + δx * cosα, y0 + δy * sinα);
            break;

          case PathStart.Ignore1st:
            break;
        }
        AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
          x0 + δx * (cosα - κ * sinα), y0 + δy * (sinα + κ * cosα),
          x0 + δx * (cosβ + κ * sinβ), y0 + δy * (sinβ - κ * cosβ),
          x0 + δx * cosβ, y0 + δy * sinβ);
      }
      else
      {
        // Calculation for quarter 2 and 3
        switch (pathStart)
        {
          case PathStart.MoveTo1st:
            AppendFormat("{0:0.###} {1:0.###} m\n", x0 - δx * cosα, y0 - δy * sinα);
            break;

          case PathStart.LineTo1st:
            AppendFormat("{0:0.###} {1:0.###} l\n", x0 - δx * cosα, y0 - δy * sinα);
            break;

          case PathStart.Ignore1st:
            break;
        }
        AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
          x0 - δx * (cosα - κ * sinα), y0 - δy * (sinα + κ * cosα),
          x0 - δx * (cosβ + κ * sinβ), y0 - δy * (sinβ - κ * cosβ),
          x0 - δx * cosβ, y0 - δy * sinβ);
      }
    }

    /// <summary>
    /// Appends a Bézier curve for a cardinal spline through pt1 and pt2.
    /// </summary>
    void AppendCurveSegment(XPoint pt0, XPoint pt1, XPoint pt2, XPoint pt3, double tension3)
    {
      AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n",
        pt1.X + tension3 * (pt2.X - pt0.X), pt1.Y + tension3 * (pt2.Y - pt0.Y),
        pt2.X - tension3 * (pt3.X - pt1.X), pt2.Y - tension3 * (pt3.Y - pt1.Y),
        pt2.X, pt2.Y);
    }

#if Gdip
    /// <summary>
    /// Appends the content of a GraphicsPath object.
    /// </summary>
    internal void AppendPath(GraphicsPath path)
    {
      int count = path.PointCount;
      if (count == 0)
        return;
      PointF[] points = path.PathPoints;
      Byte[] types = path.PathTypes;

      for (int idx = 0; idx < count; idx++)
      {
        // From GDI+ documentation:
        const byte PathPointTypeStart = 0;    // move
        const byte PathPointTypeLine = 1;    // line
        const byte PathPointTypeBezier = 3;    // default Bezier (= cubic Bezier)
        const byte PathPointTypePathTypeMask = 0x07; // type mask (lowest 3 bits).
        //const byte PathPointTypeDashMode     = 0x10; // currently in dash mode.
        //const byte PathPointTypePathMarker   = 0x20; // a marker for the path.
        const byte PathPointTypeCloseSubpath = 0x80; // closed flag

        byte type = types[idx];
        switch (type & PathPointTypePathTypeMask)
        {
          case PathPointTypeStart:
            //PDF_moveto(pdf, points[idx].X, points[idx].Y);
            AppendFormat("{0:0.###} {1:0.###} m\n", points[idx].X, points[idx].Y);
            break;

          case PathPointTypeLine:
            //PDF_lineto(pdf, points[idx].X, points[idx].Y);
            AppendFormat("{0:0.###} {1:0.###} l\n", points[idx].X, points[idx].Y);
            if ((type & PathPointTypeCloseSubpath) != 0)
              Append("h\n");
            break;

          case PathPointTypeBezier:
            Debug.Assert(idx + 2 < count);
            //PDF_curveto(pdf, points[idx].X, points[idx].Y, 
            //                 points[idx + 1].X, points[idx + 1].Y, 
            //                 points[idx + 2].X, points[idx + 2].Y);
            AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} c\n", points[idx].X, points[idx].Y,
              points[++idx].X, points[idx].Y, points[++idx].X, points[idx].Y);
            if ((types[idx] & PathPointTypeCloseSubpath) != 0)
              Append("h\n");
            break;
        }
      }
    }
#endif

    internal void Append(string value)
    {
      this.content.Append(value);
    }

    internal void AppendFormat(string format, params object[] args)
    {
      this.content.AppendFormat(CultureInfo.InvariantCulture, format, args);
    }

    void AppendStrokeFill(XPen pen, XBrush brush, XFillMode fillMode, bool closePath)
    {
      if (closePath)
        this.content.Append("h ");

      if (fillMode == XFillMode.Winding)
      {
        if (pen != null && brush != null)
          this.content.Append("B\n");
        else if (pen != null)
          this.content.Append("S\n");
        else
          this.content.Append("f\n");
      }
      else
      {
        if (pen != null && brush != null)
          this.content.Append("B*\n");
        else if (pen != null)
          this.content.Append("S\n");
        else
          this.content.Append("f*\n");
      }
    }
    #endregion

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

    #region Realizing graphical state

    /// <summary>
    /// Initializes the default view transformation, i.e. the transformation from the user page
    /// space to the PDF page space.
    /// </summary>
    void BeginPage()
    {
      if (this.gfxState.Level == GraphicsStackLevelInitial)
      {
        // Flip page horizontaly and mirror text.
        // TODO: Is PageOriging and PageScale (== Viewport) useful? Or just public DefaultViewMatrix (like Presentation Manager has had)
        this.defaultViewMatrix = XMatrix.Identity;
        if (this.gfx.PageDirection == XPageDirection.Downwards)
        {
#if MIGRADOC
          if (this.pdflibHack)
          {
            // MigraDoc 1.1 (written in C++) based on PDFlib and our RenderContext for GDI+. To keep it running
            // on PDFlib (and PGL) I need this hack here in the MigraDoc build of PDFsharp.
            // The new and more flexible MigraDoc Rendering (written in C#) doesn't need it anymore.
            SaveState();
            defaultViewMatrix.Translate(0, this.page.Height);
            defaultViewMatrix.Scale(1, -1);
          }
          else
#endif
          {
            if (this.page != null && this.page.Elements.GetInteger("/Rotate") == 90)  // HACK for InDesign flyer
            {
              defaultViewMatrix.Rotate(90);
              defaultViewMatrix.Scale(1, -1);
            }
            else
            {
              // Recall that the value of Height depends on Orientation.
              defaultViewMatrix.Translate(0, Size.Height);
              defaultViewMatrix.Scale(1, -1);
            }

            // Scale with page units
            switch (this.gfx.PageUnit)
            {
              case XGraphicsUnit.Inch:
                defaultViewMatrix.Scale(XUnit.InchFactor);
                break;

              case XGraphicsUnit.Millimeter:
                defaultViewMatrix.Scale(XUnit.MillimeterFactor);
                break;
          
              case XGraphicsUnit.Centimeter:
                defaultViewMatrix.Scale(XUnit.CentimeterFactor);
                break;
            }

            // Save initial graphic state
            SaveState();
            // Set page transformation
            double[] cm = defaultViewMatrix.Elements;
            AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} cm ",
              cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]);
            AppendFormat("-100 Tz\n");
          }
        }
        else
        {
          // Scale with page units
          switch (this.gfx.PageUnit)
          {
            case XGraphicsUnit.Inch:
              defaultViewMatrix.Scale(XUnit.InchFactor);
              break;

            case XGraphicsUnit.Millimeter:
              defaultViewMatrix.Scale(XUnit.MillimeterFactor);
              break;
          
            case XGraphicsUnit.Centimeter:
              defaultViewMatrix.Scale(XUnit.CentimeterFactor);
              break;
          }

          // Save initial graphic state
          SaveState();
          // Set page transformation
          double[] cm = defaultViewMatrix.Elements;
          AppendFormat("{0:0.###} {1:0.###} {2:0.###} {3:0.###} {4:0.###} {5:0.###} cm ",
            cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]);
        }
      }
    }

    /// <summary>
    /// Ends the content stream, i.e. ends the text mode and balances the graphic state stack.
    /// </summary>
    void EndPage()
    {
      if (this.streamMode == StreamMode.Text)
      {
        this.content.Append("ET\n");
        this.streamMode = StreamMode.Graphic;
      }

      while (this.gfxStateStack.Count != 0)
        RestoreState();
    }

    /// <summary>
    /// Begins the graphic mode (i.e. ends the text mode).
    /// </summary>
    internal void BeginGraphic()
    {
      if (this.streamMode != StreamMode.Graphic)
      {
        if (this.streamMode == StreamMode.Text)
          this.content.Append("ET\n");

        this.streamMode = StreamMode.Graphic;
      }
    }
    StreamMode streamMode;

    /// <summary>
    /// Makes the specified pen and brush to the current graphics objects.
    /// </summary>
    void Realize(XPen pen, XBrush brush)
    {
      BeginPage();
      BeginGraphic();
      RealizeTransform();

      if (pen != null)
        this.gfxState.RealizePen(pen);

      if (brush != null)
        this.gfxState.RealizeBrush(brush);
    }

    /// <summary>
    /// Makes the specified pen to the current graphics object.
    /// </summary>
    void Realize(XPen pen)
    {
      Realize(pen, null);
    }

    /// <summary>
    /// Makes the specified brush to the current graphics object.
    /// </summary>
    void Realize(XBrush brush)
    {
      Realize(null, brush);
    }

    /// <summary>
    /// Makes the specified font and brush to the current graphics objects.
    /// </summary>
    void Realize(XFont font, XBrush brush, int renderMode)
    {
      BeginPage();
      RealizeTransform();

      if (this.streamMode != StreamMode.Text)
      {
        this.streamMode = StreamMode.Text;
        this.content.Append("BT\n");
        // Text matrix is empty after BT
        this.gfxState.realizedTextPosition = XPoint.Empty;
      }
      this.gfxState.RealizeFont(font, brush, renderMode);
    }

    void AdjustTextMatrix(ref XPoint pos)
    {
      XPoint posSave = pos;
      pos = pos - new XSize(this.gfxState.realizedTextPosition);
      this.gfxState.realizedTextPosition = posSave;
    }

    /// <summary>
    /// Makes the specified image to the current graphics object.
    /// </summary>
    string Realize(XImage image)
    {
      BeginPage();
      BeginGraphic();
      RealizeTransform();

      string imageName;
      if (image is XForm)
        imageName = GetFormName(image as XForm);
      else
        imageName = GetImageName(image);
      return imageName;
    }

    /// <summary>
    /// Realizes the current transformation matrix, if necessary.
    /// </summary>
    void RealizeTransform()
    {
      BeginPage();

      if (this.gfxState.Level == GraphicsStackLevelPageSpace)
      {
        BeginGraphic();
        SaveState();
      }

      if (gfxState.MustRealizeCtm)
      {
        BeginGraphic();
        this.gfxState.RealizeCtm();
      }
    }

    #endregion

#if Gdip
    [Conditional("DEBUG")]
    void DumpPathData(PathData pathData)
    {
      int count = pathData.Points.Length;
      for (int idx = 0; idx < count; idx++)
      {
        string info = PdfEncoders.Format("{0:X}   {1:####0.000} {2:####0.000}", pathData.Types[idx], pathData.Points[idx].X, pathData.Points[idx].Y);
        Debug.WriteLine(info, "PathData");
      }
    }
#endif

    /// <summary>
    /// Gets the owning PdfDocument of this page or form.
    /// </summary>
    internal PdfDocument Owner
    {
      get
      {
        if (this.page != null)
          return this.page.Owner;
        else
          return this.form.Owner;
      }
    }

    internal XGraphics Gfx
    {
      get { return this.gfx; }
    }

    /// <summary>
    /// Gets the PdfResources of this page or form.
    /// </summary>
    internal PdfResources Resources
    {
      get
      {
        if (this.page != null)
          return this.page.Resources;
        else
          return this.form.Resources;
      }
    }

    /// <summary>
    /// Gets the size of this page or form.
    /// </summary>
    internal XSize Size
    {
      get
      {
        if (this.page != null)
          return new XSize(this.page.Width, this.page.Height);
        else
          return this.form.Size;
      }
    }

    /// <summary>
    /// Gets the resource name of the specified font within this page or form.
    /// </summary>
    internal string GetFontName(XFont font, out PdfFont pdfFont)
    {
      if (this.page != null)
        return this.page.GetFontName(font, out pdfFont);
      else
        return this.form.GetFontName(font, out pdfFont);
    }

    /// <summary>
    /// Gets the resource name of the specified image within this page or form.
    /// </summary>
    internal string GetImageName(XImage image)
    {
      if (this.page != null)
        return this.page.GetImageName(image);
      else
        return this.form.GetImageName(image);
    }

    /// <summary>
    /// Gets the resource name of the specified form within this page or form.
    /// </summary>
    internal string GetFormName(XForm form)
    {
      if (this.page != null)
        return this.page.GetFormName(form);
      else
        return this.form.GetFormName(form);
    }

    internal PdfPage page;
    internal XForm form;
    XGraphicsPdfPageOptions options;
    XGraphics gfx;
    StringBuilder content;

    /// <summary>
    /// The q/Q nesting level is 0.
    /// </summary>
    const int GraphicsStackLevelInitial = 0;

    /// <summary>
    /// The q/Q nesting level is 1.
    /// </summary>
    const int GraphicsStackLevelPageSpace = 1;

    /// <summary>
    /// The q/Q nesting level is 2.
    /// </summary>
    const int GraphicsStackLevelWorldSpace = 2;

    #region PDF Graphics State

    /// <summary>
    /// Saves the current graphical state.
    /// </summary>
    void SaveState()
    {
      Debug.Assert(this.streamMode == StreamMode.Graphic, "Cannot restore state in text mode.");

      this.gfxStateStack.Push(this.gfxState);
      this.gfxState = this.gfxState.Clone();
      this.gfxState.Level = this.gfxStateStack.Count;
      Append("q\n");
    }

    /// <summary>
    /// Restores the previous graphical state.
    /// </summary>
    void RestoreState()
    {
      Debug.Assert(this.streamMode == StreamMode.Graphic, "Cannot restore state in text mode.");

      this.gfxState = (PdfGraphicsState)this.gfxStateStack.Pop();
      Append("Q\n");
    }

    PdfGraphicsState RestoreState(InternalGraphicsState state)
    {
      int count = 1;
      PdfGraphicsState top = (PdfGraphicsState)this.gfxStateStack.Pop();
      while (top.InternalState != state)
      {
        Append("Q\n");
        count++;
        top = (PdfGraphicsState)this.gfxStateStack.Pop();
      }
      Append("Q\n");
      this.gfxState = top;
      return top;
    }

    /// <summary>
    /// The current graphical state.
    /// </summary>
    PdfGraphicsState gfxState;

    /// <summary>
    /// The graphical state stack.
    /// </summary>
    Stack gfxStateStack = new Stack();

    #endregion

    /// <summary>
    /// The final transformation from the world space to the default page space.
    /// </summary>
    internal XMatrix defaultViewMatrix;
  }
}
