#region PDFsharp - A .NET library for processing PDF // // Authors: // Stefan Lange // // Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (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 #define ITALIC_SIMULATION using System; using System.Diagnostics; using System.Globalization; using System.Collections.Generic; using System.Text; #if GDI using System.Drawing; using System.Drawing.Drawing2D; #endif #if WPF using System.Windows; using System.Windows.Media; using SysPoint = System.Windows.Point; using SysSize = System.Windows.Size; #endif #if NETFX_CORE using Windows.UI.Xaml.Media; using SysPoint = Windows.Foundation.Point; using SysSize = Windows.Foundation.Size; #endif using PdfSharp.Fonts.OpenType; using PdfSharp.Internal; using PdfSharp.Pdf; using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.Advanced; // ReSharper disable RedundantNameQualifier // ReSharper disable CompareOfFloatsByEqualityOperator namespace PdfSharp.Drawing.Pdf { /// /// Represents a drawing surface for PdfPages. /// internal class XGraphicsPdfRenderer : IXGraphicsRenderer { public XGraphicsPdfRenderer(PdfPage page, XGraphics gfx, XGraphicsPdfPageOptions options) { _page = page; _colorMode = page._document.Options.ColorMode; _options = options; _gfx = gfx; _content = new StringBuilder(); page.RenderContent._pdfRenderer = this; _gfxState = new PdfGraphicsState(this); } public XGraphicsPdfRenderer(XForm form, XGraphics gfx) { _form = form; _colorMode = form.Owner.Options.ColorMode; _gfx = gfx; _content = new StringBuilder(); form.PdfRenderer = this; _gfxState = new PdfGraphicsState(this); } /// /// Gets the content created by this renderer. /// string GetContent() { EndPage(); return _content.ToString(); } public XGraphicsPdfPageOptions PageOptions { get { return _options; } } public void Close() { if (_page != null) { PdfContent content2 = _page.RenderContent; content2.CreateStream(PdfEncoders.RawEncoding.GetBytes(GetContent())); _gfx = null; _page.RenderContent._pdfRenderer = null; _page.RenderContent = null; _page = null; } else if (_form != null) { _form._pdfForm.CreateStream(PdfEncoders.RawEncoding.GetBytes(GetContent())); _gfx = null; _form.PdfRenderer = null; _form = null; } } // -------------------------------------------------------------------------------------------- #region Drawing //void SetPageLayout(down, point(0, 0), unit // ----- DrawLine ----------------------------------------------------------------------------- /// /// Strokes a single connection of two points. /// public void DrawLine(XPen pen, double x1, double y1, double x2, double y2) { DrawLines(pen, new XPoint[] { new XPoint(x1, y1), new XPoint(x2, y2) }); } // ----- DrawLines ---------------------------------------------------------------------------- /// /// Strokes a series of connected points. /// 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); const string format = Config.SignificantFigures4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); for (int idx = 1; idx < count; idx++) AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); _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[] { 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); const string format = Config.SignificantFigures4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); for (int idx = 1; idx < count; idx += 3) AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} 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 // Link is down... tension /= 3; Realize(pen); const string format = Config.SignificantFigures4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} 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, new XMatrix()); 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"); const string format = Config.SignificantFigures3; Realize(pen, brush); //AppendFormat123("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} re\n", x, y, width, -height); AppendFormatRect("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} re\n", x, y + height, width, height); if (pen != null && brush != null) _content.Append("B\n"); else if (pen != null) _content.Append("S\n"); else _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 is here http://home.t-online.de/home/Robert.Rossmair/ellipse.htm (note: link was dead on November 2, 2015) // or here http://www.whizkidtech.redprince.net/bezier/circle/ // Deeper but more difficult: http://www.tinaja.com/cubic01.asp XRect rect = new XRect(x, y, width, height); double δx = rect.Width / 2; double δy = rect.Height / 2; double fx = δx * Const.κ; double fy = δy * Const.κ; double x0 = rect.X + δx; double y0 = rect.Y + δy; // Approximate an ellipse by drawing four cubic splines. const string format = Config.SignificantFigures4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", x0 + δx, y0); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", x0 + δx, y0 + fy, x0 + fx, y0 + δy, x0, y0 + δy); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", x0 - fx, y0 + δy, x0 - δx, y0 + fy, x0 - δx, y0); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", x0 - δx, y0 - fy, x0 - fx, y0 - δy, x0, y0 - δy); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} 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(PSSR.PointArrayAtLeast(2), "points"); const string format = Config.SignificantFigures4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); for (int idx = 1; idx < count; idx++) AppendFormatPoint("{0:" + format + "} {1:" + format + "} 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); const string format = Config.SignificantFigures4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", x + width / 2, y + height / 2); AppendPartialArc(x, y, width, height, startAngle, sweepAngle, PathStart.LineTo1st, new XMatrix()); 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); const string format = Config.SignificantFigures4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); if (count == 2) { // Just draw 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"); #if CORE Realize(pen, brush); AppendPath(path._corePath); AppendStrokeFill(pen, brush, path.FillMode, false); #endif #if GDI && !WPF Realize(pen, brush); AppendPath(path._gdipPath); AppendStrokeFill(pen, brush, path.FillMode, false); #endif #if WPF && !GDI Realize(pen, brush); AppendPath(path._pathGeometry); AppendStrokeFill(pen, brush, path.FillMode, false); #endif #if WPF && GDI Realize(pen, brush); if (_gfx.TargetContext == XGraphicTargetContext.GDI) AppendPath(path._gdipPath); else AppendPath(path._pathGeometry); AppendStrokeFill(pen, brush, path.FillMode, false); #endif #if NETFX_CORE Realize(pen, brush); AppendPath(path._pathGeometry); AppendStrokeFill(pen, brush, path.FillMode, false); #endif } // ----- DrawString --------------------------------------------------------------------------- public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFormat format) { double x = rect.X; double y = rect.Y; double lineSpace = font.GetHeight(); double cyAscent = lineSpace * font.CellAscent / font.CellSpace; double cyDescent = lineSpace * font.CellDescent / font.CellSpace; double width = _gfx.MeasureString(s, font).Width; bool italicSimulation = (font.GlyphTypeface.StyleSimulations & XStyleSimulations.ItalicSimulation) != 0; bool boldSimulation = (font.GlyphTypeface.StyleSimulations & XStyleSimulations.BoldSimulation) != 0; bool strikeout = (font.Style & XFontStyle.Strikeout) != 0; bool underline = (font.Style & XFontStyle.Underline) != 0; Realize(font, brush, boldSimulation ? 2 : 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 = _gfxState._realizedFont; Debug.Assert(realizedFont != null); realizedFont.AddChars(s); const string format2 = Config.SignificantFigures4; OpenTypeDescriptor descriptor = realizedFont.FontDescriptor._descriptor; string text = null; if (font.Unicode) { StringBuilder sb = new StringBuilder(); bool isSymbolFont = descriptor.FontFace.cmap.symbol; for (int idx = 0; idx < s.Length; idx++) { char ch = s[idx]; if (isSymbolFont) { // Remap ch for symbol fonts. ch = (char)(ch | (descriptor.FontFace.os2.usFirstCharIndex & 0xFF00)); // @@@ refactor } int glyphID = descriptor.CharCodeToGlyphIndex(ch); sb.Append((char)glyphID); } s = sb.ToString(); byte[] bytes = PdfEncoders.RawUnicodeEncoding.GetBytes(s); bytes = PdfEncoders.FormatStringLiteral(bytes, true, false, true, null); text = PdfEncoders.RawEncoding.GetString(bytes, 0, bytes.Length); } else { byte[] bytes = PdfEncoders.WinAnsiEncoding.GetBytes(s); text = PdfEncoders.ToStringLiteral(bytes, false, null); } // Map absolute position to PDF world space. XPoint pos = new XPoint(x, y); pos = WorldToView(pos); double verticalOffset = 0; if (boldSimulation) { // Adjust baseline in case of bold simulation??? // No, because this would change the center of the glyphs. //verticalOffset = font.Size * Const.BoldEmphasis / 2; } #if ITALIC_SIMULATION if (italicSimulation) { if (_gfxState.ItalicSimulationOn) { AdjustTdOffset(ref pos, verticalOffset, true); AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} Td\n{2} Tj\n", pos.X, pos.Y, text); } else { // Italic simulation is done by skewing characters 20° to the right. XMatrix m = new XMatrix(1, 0, Const.ItalicSkewAngleSinus, 1, pos.X, pos.Y); AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} {2:" + format2 + "} {3:" + format2 + "} {4:" + format2 + "} {5:" + format2 + "} Tm\n{6} Tj\n", m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY, text); _gfxState.ItalicSimulationOn = true; AdjustTdOffset(ref pos, verticalOffset, false); } } else { if (_gfxState.ItalicSimulationOn) { XMatrix m = new XMatrix(1, 0, 0, 1, pos.X, pos.Y); AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} {2:" + format2 + "} {3:" + format2 + "} {4:" + format2 + "} {5:" + format2 + "} Tm\n{6} Tj\n", m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY, text); _gfxState.ItalicSimulationOn = false; AdjustTdOffset(ref pos, verticalOffset, false); } else { AdjustTdOffset(ref pos, verticalOffset, false); AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text); } } #else AdjustTextMatrix(ref pos); AppendFormat2("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text); #endif if (underline) { double underlinePosition = lineSpace * realizedFont.FontDescriptor._descriptor.UnderlinePosition / font.CellSpace; double underlineThickness = lineSpace * realizedFont.FontDescriptor._descriptor.UnderlineThickness / font.CellSpace; //DrawRectangle(null, brush, x, y - underlinePosition, width, underlineThickness); double underlineRectY = Gfx.PageDirection == XPageDirection.Downwards ? y - underlinePosition : y + underlinePosition - underlineThickness; DrawRectangle(null, brush, x, underlineRectY, width, underlineThickness); } if (strikeout) { double strikeoutPosition = lineSpace * realizedFont.FontDescriptor._descriptor.StrikeoutPosition / font.CellSpace; double strikeoutSize = lineSpace * realizedFont.FontDescriptor._descriptor.StrikeoutSize / font.CellSpace; //DrawRectangle(null, brush, x, y - strikeoutPosition - strikeoutSize, width, strikeoutSize); double strikeoutRectY = Gfx.PageDirection == XPageDirection.Downwards ? y - strikeoutPosition : y + strikeoutPosition - strikeoutSize; DrawRectangle(null, brush, x, strikeoutRectY, width, strikeoutSize); } } // ----- 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) { const string format = Config.SignificantFigures4; string name = Realize(image); if (!(image is XForm)) { if (_gfx.PageDirection == XPageDirection.Downwards) { AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", x, y + height, width, height, name); } else { AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} 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.PointWidth; double cy = height / image.PointHeight; if (cx != 0 && cy != 0) { XPdfForm xForm = image as XPdfForm; if (_gfx.PageDirection == XPageDirection.Downwards) { // If we have an XPdfForm, then we take the MediaBox into account. double xDraw = x; double yDraw = y; if (xForm != null) { // Yes, it is an XPdfForm - adjust the position where the page will be drawn. xDraw -= xForm.Page.MediaBox.X1; yDraw += xForm.Page.MediaBox.Y1; } AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm 100 Tz {4} Do Q\n", xDraw, yDraw + height, cx, cy, name); } else { // TODO Translation for MediaBox. AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} 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) { const string format = Config.SignificantFigures4; 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 (_gfx.PageDirection == XPageDirection.Downwards) { AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do\nQ\n", x, y + height, width, height, name); } else { AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} 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.PointWidth; double cy = height / image.PointHeight; if (cx != 0 && cy != 0) { XPdfForm xForm = image as XPdfForm; if (_gfx.PageDirection == XPageDirection.Downwards) { double xDraw = x; double yDraw = y; if (xForm != null) { // Yes, it is an XPdfForm - adjust the position where the page will be drawn. xDraw -= xForm.Page.MediaBox.X1; yDraw += xForm.Page.MediaBox.Y1; } AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", xDraw, yDraw + height, cx, cy, name); } else { // TODO Translation for MediaBox. AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", x, y, cx, cy, name); } } } } #endregion // -------------------------------------------------------------------------------------------- #region Save and Restore /// /// Clones the current graphics state and push it on a stack. /// public void Save(XGraphicsState state) { // Before saving, the current transformation matrix must be completely realized. BeginGraphicMode(); RealizeTransform(); // Associate the XGraphicsState with the current PdgGraphicsState. _gfxState.InternalState = state.InternalState; SaveState(); } public void Restore(XGraphicsState state) { BeginGraphicMode(); RestoreState(state.InternalState); } public void BeginContainer(XGraphicsContainer container, XRect dstrect, XRect srcrect, XGraphicsUnit unit) { // Before saving, the current transformation matrix must be completely realized. BeginGraphicMode(); RealizeTransform(); _gfxState.InternalState = container.InternalState; SaveState(); } public void EndContainer(XGraphicsContainer container) { BeginGraphicMode(); RestoreState(container.InternalState); } #endregion // -------------------------------------------------------------------------------------------- #region Transformation //public void SetPageTransform(XPageDirection direction, XPoint origion, XGraphicsUnit unit) //{ // if (_gfxStateStack.Count > 0) // throw new InvalidOperationException("PageTransformation can be modified only when the graphics stack is empty."); // throw new NotImplementedException("SetPageTransform"); //} public XMatrix Transform { get { if (_gfxState.UnrealizedCtm.IsIdentity) return _gfxState.EffectiveCtm; return _gfxState.UnrealizedCtm * _gfxState.RealizedCtm; } } public void AddTransform(XMatrix value, XMatrixOrder matrixOrder) { _gfxState.AddTransform(value, matrixOrder); } #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 (_gfxState.Level < GraphicsStackLevelWorldSpace) RealizeTransform(); // TODO: refactor this function if (combineMode == XCombineMode.Replace) { if (_clipLevel != 0) { if (_clipLevel != _gfxState.Level) throw new NotImplementedException("Cannot set new clip region in an inner graphic state level."); else ResetClip(); } _clipLevel = _gfxState.Level; } else if (combineMode == XCombineMode.Intersect) { if (_clipLevel == 0) _clipLevel = _gfxState.Level; } else { Debug.Assert(false, "Invalid XCombineMode in internal function."); } _gfxState.SetAndRealizeClipPath(path); } /// /// 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. /// public void ResetClip() { // No clip level means no clipping occurs and nothing is to do. if (_clipLevel == 0) return; // Only at the clipLevel the clipping can be reset. if (_clipLevel != _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. BeginGraphicMode(); // Save InternalGraphicsState and transformation of the current graphical state. InternalGraphicsState state = _gfxState.InternalState; XMatrix ctm = _gfxState.EffectiveCtm; // Empty clip path by switching back to the previous state. RestoreState(); SaveState(); // Save internal state _gfxState.InternalState = state; // Restore CTM // TODO: check rest of clip //GfxState.Transform = ctm; } /// /// 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. /// int _clipLevel; #endregion // -------------------------------------------------------------------------------------------- #region Miscellaneous /// /// Writes a comment to the PDF content stream. May be useful for debugging purposes. /// public void WriteComment(string comment) { comment = comment.Replace("\n", "\n% "); // TODO: Some more checks necessary? Append("% " + comment + "\n"); } #endregion // -------------------------------------------------------------------------------------------- #region Append to PDF stream /// /// Appends one or up to five Bézier curves that interpolate the arc. /// void AppendPartialArc(double x, double y, double width, double height, double startAngle, double sweepAngle, PathStart pathStart, XMatrix matrix) { // 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 = Quadrant(α, true, clockwise); int endQuadrant = Quadrant(β, false, clockwise); if (startQuadrant == endQuadrant && smallAngle) AppendPartialArcQuadrant(x, y, width, height, α, β, pathStart, matrix); else { int currentQuadrant = startQuadrant; bool firstLoop = true; do { if (currentQuadrant == startQuadrant && firstLoop) { double ξ = currentQuadrant * 90 + (clockwise ? 90 : 0); AppendPartialArcQuadrant(x, y, width, height, α, ξ, pathStart, matrix); } else if (currentQuadrant == endQuadrant) { double ξ = currentQuadrant * 90 + (clockwise ? 0 : 90); AppendPartialArcQuadrant(x, y, width, height, ξ, β, PathStart.Ignore1st, matrix); } else { double ξ1 = currentQuadrant * 90 + (clockwise ? 0 : 90); double ξ2 = currentQuadrant * 90 + (clockwise ? 90 : 0); AppendPartialArcQuadrant(x, y, width, height, ξ1, ξ2, PathStart.Ignore1st, matrix); } // Don't stop immediately if arc is greater than 270 degrees if (currentQuadrant == endQuadrant && smallAngle) break; smallAngle = true; if (clockwise) currentQuadrant = currentQuadrant == 3 ? 0 : currentQuadrant + 1; else currentQuadrant = currentQuadrant == 0 ? 3 : currentQuadrant - 1; firstLoop = false; } while (true); } } /// /// 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. /// int Quadrant(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; } /// /// Appends a Bézier curve for an arc within a quadrant. /// void AppendPartialArcQuadrant(double x, double y, double width, double height, double α, double β, PathStart pathStart, XMatrix matrix) { 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 algorithm works as expected (simply tried out). // There may be a mathematically more elegant solution... bool reflect = false; if (α >= 180 && β >= 180) { α -= 180; β -= 180; reflect = true; } double 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) α = Math.PI / 2 - Math.Atan(δy * Math.Cos(α) / (δx * sinα)); β = β * Calc.Deg2Rad; sinβ = Math.Sin(β); if (Math.Abs(sinβ) > 1E-10) β = Math.PI / 2 - Math.Atan(δy * Math.Cos(β) / (δx * sinβ)); } double κ = 4 * (1 - Math.Cos((α - β) / 2)) / (3 * Math.Sin((β - α) / 2)); sinα = Math.Sin(α); double cosα = Math.Cos(α); sinβ = Math.Sin(β); double cosβ = Math.Cos(β); const string format = Config.SignificantFigures3; XPoint pt1, pt2, pt3; if (!reflect) { // Calculation for quarter 0 and 1 switch (pathStart) { case PathStart.MoveTo1st: pt1 = matrix.Transform(new XPoint(x0 + δx * cosα, y0 + δy * sinα)); AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", pt1.X, pt1.Y); break; case PathStart.LineTo1st: pt1 = matrix.Transform(new XPoint(x0 + δx * cosα, y0 + δy * sinα)); AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", pt1.X, pt1.Y); break; case PathStart.Ignore1st: break; } pt1 = matrix.Transform(new XPoint(x0 + δx * (cosα - κ * sinα), y0 + δy * (sinα + κ * cosα))); pt2 = matrix.Transform(new XPoint(x0 + δx * (cosβ + κ * sinβ), y0 + δy * (sinβ - κ * cosβ))); pt3 = matrix.Transform(new XPoint(x0 + δx * cosβ, y0 + δy * sinβ)); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", pt1.X, pt1.Y, pt2.X, pt2.Y, pt3.X, pt3.Y); } else { // Calculation for quarter 2 and 3. switch (pathStart) { case PathStart.MoveTo1st: pt1 = matrix.Transform(new XPoint(x0 - δx * cosα, y0 - δy * sinα)); AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", pt1.X, pt1.Y); break; case PathStart.LineTo1st: pt1 = matrix.Transform(new XPoint(x0 - δx * cosα, y0 - δy * sinα)); AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", pt1.X, pt1.Y); break; case PathStart.Ignore1st: break; } pt1 = matrix.Transform(new XPoint(x0 - δx * (cosα - κ * sinα), y0 - δy * (sinα + κ * cosα))); pt2 = matrix.Transform(new XPoint(x0 - δx * (cosβ + κ * sinβ), y0 - δy * (sinβ - κ * cosβ))); pt3 = matrix.Transform(new XPoint(x0 - δx * cosβ, y0 - δy * sinβ)); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", pt1.X, pt1.Y, pt2.X, pt2.Y, pt3.X, pt3.Y); } } #if WPF || NETFX_CORE void AppendPartialArc(SysPoint point1, SysPoint point2, double rotationAngle, SysSize size, bool isLargeArc, SweepDirection sweepDirection, PathStart pathStart) { const string format = Config.SignificantFigures4; Debug.Assert(pathStart == PathStart.Ignore1st); int pieces; PointCollection points = GeometryHelper.ArcToBezier(point1.X, point1.Y, size.Width, size.Height, rotationAngle, isLargeArc, sweepDirection == SweepDirection.Clockwise, point2.X, point2.Y, out pieces); int count = points.Count; int start = count % 3 == 1 ? 1 : 0; if (start == 1) AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); for (int idx = start; idx < count; idx += 3) AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", points[idx].X, points[idx].Y, points[idx + 1].X, points[idx + 1].Y, points[idx + 2].X, points[idx + 2].Y); } #endif /// /// Appends a Bézier curve for a cardinal spline through pt1 and pt2. /// void AppendCurveSegment(XPoint pt0, XPoint pt1, XPoint pt2, XPoint pt3, double tension3) { const string format = Config.SignificantFigures4; AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} 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 CORE_ /// /// Appends the content of a GraphicsPath object. /// 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:" + format + "} {1:" + format + "} m\n", points[idx].X, points[idx].Y); break; case PathPointTypeLine: //PDF_lineto(pdf, points[idx].X, points[idx].Y); AppendFormat("{0:" + format + "} {1:" + format + "} 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:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} 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 #if CORE /// /// Appends the content of a GraphicsPath object. /// internal void AppendPath(CoreGraphicsPath path) { AppendPath(path.PathPoints, path.PathTypes); //XPoint[] points = path.PathPoints; //Byte[] types = path.PathTypes; //int count = points.Length; //if (count == 0) // return; //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:" + format + "} {1:" + format + "} m\n", points[idx].X, points[idx].Y); // break; // case PathPointTypeLine: // //PDF_lineto(pdf, points[idx].X, points[idx].Y); // AppendFormat("{0:" + format + "} {1:" + format + "} 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:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} 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 #if GDI /// /// Appends the content of a GraphicsPath object. /// internal void AppendPath(GraphicsPath path) { #if true AppendPath(XGraphics.MakeXPointArray(path.PathPoints, 0, path.PathPoints.Length), path.PathTypes); #else 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:" + format + "} {1:" + format + "} m\n", points[idx].X, points[idx].Y); break; case PathPointTypeLine: //PDF_lineto(pdf, points[idx].X, points[idx].Y); AppendFormat("{0:" + format + "} {1:" + format + "} 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:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} 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 } #endif #if CORE || GDI void AppendPath(XPoint[] points, Byte[] types) { const string format = Config.SignificantFigures4; int count = points.Length; if (count == 0) return; for (int idx = 0; idx < count; idx++) { // ReSharper disable InconsistentNaming // 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 // ReSharper restore InconsistentNaming byte type = types[idx]; switch (type & PathPointTypePathTypeMask) { case PathPointTypeStart: //PDF_moveto(pdf, points[idx].X, points[idx].Y); AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[idx].X, points[idx].Y); break; case PathPointTypeLine: //PDF_lineto(pdf, points[idx].X, points[idx].Y); AppendFormatPoint("{0:" + format + "} {1:" + format + "} 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); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} 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 #if WPF || NETFX_CORE /// /// Appends the content of a PathGeometry object. /// internal void AppendPath(PathGeometry geometry) { const string format = Config.SignificantFigures4; foreach (PathFigure figure in geometry.Figures) { #if DEBUG //#warning For DdlGBE_Chart_Layout (WPF) execution stucks at this Assertion. // The empty Figure is added via XGraphicsPath.CurrentPathFigure Getter. // Some methods like XGraphicsPath.AddRectangle() or AddLine() use this emtpy Figure to add Segments, others like AddEllipse() don't. // Here, _pathGeometry.AddGeometry() of course ignores this first Figure and adds a second. // Encapsulate relevant Add methods to delete a first emty Figure or move the Addition of an first empty Figure to a GetOrCreateCurrentPathFigure() or simply remove Assertion? // Look for: // MAOS4STLA: CurrentPathFigure. if (figure.Segments.Count == 0) 42.GetType(); Debug.Assert(figure.Segments.Count > 0); #endif // Skip the Move if the segment is empty. Workaround for empty segments. Empty segments should not occur (see Debug.Assert above). if (figure.Segments.Count > 0) { // Move to start point. SysPoint currentPoint = figure.StartPoint; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", currentPoint.X, currentPoint.Y); foreach (PathSegment segment in figure.Segments) { Type type = segment.GetType(); if (type == typeof(LineSegment)) { // Draw a single line. SysPoint point = ((LineSegment)segment).Point; currentPoint = point; AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", point.X, point.Y); } else if (type == typeof(PolyLineSegment)) { // Draw connected lines. PointCollection points = ((PolyLineSegment)segment).Points; foreach (SysPoint point in points) { currentPoint = point; // I forced myself not to optimize this assignment. AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", point.X, point.Y); } } else if (type == typeof(BezierSegment)) { // Draw Bézier curve. BezierSegment seg = (BezierSegment)segment; SysPoint point1 = seg.Point1; SysPoint point2 = seg.Point2; SysPoint point3 = seg.Point3; AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", point1.X, point1.Y, point2.X, point2.Y, point3.X, point3.Y); currentPoint = point3; } else if (type == typeof(PolyBezierSegment)) { // Draw connected Bézier curves. PointCollection points = ((PolyBezierSegment)segment).Points; int count = points.Count; if (count > 0) { Debug.Assert(count % 3 == 0, "Number of Points in PolyBezierSegment are not a multiple of 3."); for (int idx = 0; idx < count - 2; idx += 3) { SysPoint point1 = points[idx]; SysPoint point2 = points[idx + 1]; SysPoint point3 = points[idx + 2]; AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", point1.X, point1.Y, point2.X, point2.Y, point3.X, point3.Y); } currentPoint = points[count - 1]; } } else if (type == typeof(ArcSegment)) { // Draw arc. ArcSegment seg = (ArcSegment)segment; AppendPartialArc(currentPoint, seg.Point, seg.RotationAngle, seg.Size, seg.IsLargeArc, seg.SweepDirection, PathStart.Ignore1st); currentPoint = seg.Point; } else if (type == typeof(QuadraticBezierSegment)) { QuadraticBezierSegment seg = (QuadraticBezierSegment)segment; currentPoint = seg.Point2; // TODOWPF: Undone because XGraphics has no such curve type throw new NotImplementedException("AppendPath with QuadraticBezierSegment."); } else if (type == typeof(PolyQuadraticBezierSegment)) { PolyQuadraticBezierSegment seg = (PolyQuadraticBezierSegment)segment; currentPoint = seg.Points[seg.Points.Count - 1]; // TODOWPF: Undone because XGraphics has no such curve type throw new NotImplementedException("AppendPath with PolyQuadraticBezierSegment."); } } if (figure.IsClosed) Append("h\n"); } } } #endif internal void Append(string value) { _content.Append(value); } internal void AppendFormatArgs(string format, params object[] args) { _content.AppendFormat(CultureInfo.InvariantCulture, format, args); #if DEBUG string dummy = _content.ToString(); dummy = dummy.Substring(Math.Max(0, dummy.Length - 100)); dummy.GetType(); #endif } internal void AppendFormatString(string format, string s) { _content.AppendFormat(CultureInfo.InvariantCulture, format, s); } internal void AppendFormatFont(string format, string s, double d) { _content.AppendFormat(CultureInfo.InvariantCulture, format, s, d); } internal void AppendFormatInt(string format, int n) { _content.AppendFormat(CultureInfo.InvariantCulture, format, n); } internal void AppendFormatDouble(string format, double d) { _content.AppendFormat(CultureInfo.InvariantCulture, format, d); } internal void AppendFormatPoint(string format, double x, double y) { XPoint result = WorldToView(new XPoint(x, y)); _content.AppendFormat(CultureInfo.InvariantCulture, format, result.X, result.Y); } internal void AppendFormatRect(string format, double x, double y, double width, double height) { XPoint point1 = WorldToView(new XPoint(x, y)); _content.AppendFormat(CultureInfo.InvariantCulture, format, point1.X, point1.Y, width, height); } internal void AppendFormat3Points(string format, double x1, double y1, double x2, double y2, double x3, double y3) { XPoint point1 = WorldToView(new XPoint(x1, y1)); XPoint point2 = WorldToView(new XPoint(x2, y2)); XPoint point3 = WorldToView(new XPoint(x3, y3)); _content.AppendFormat(CultureInfo.InvariantCulture, format, point1.X, point1.Y, point2.X, point2.Y, point3.X, point3.Y); } internal void AppendFormat(string format, XPoint point) { XPoint result = WorldToView(point); _content.AppendFormat(CultureInfo.InvariantCulture, format, result.X, result.Y); } internal void AppendFormat(string format, double x, double y, string s) { XPoint result = WorldToView(new XPoint(x, y)); _content.AppendFormat(CultureInfo.InvariantCulture, format, result.X, result.Y, s); } internal void AppendFormatImage(string format, double x, double y, double width, double height, string name) { XPoint result = WorldToView(new XPoint(x, y)); _content.AppendFormat(CultureInfo.InvariantCulture, format, result.X, result.Y, width, height, name); } void AppendStrokeFill(XPen pen, XBrush brush, XFillMode fillMode, bool closePath) { if (closePath) _content.Append("h "); if (fillMode == XFillMode.Winding) { if (pen != null && brush != null) _content.Append("B\n"); else if (pen != null) _content.Append("S\n"); else _content.Append("f\n"); } else { if (pen != null && brush != null) _content.Append("B*\n"); else if (pen != null) _content.Append("S\n"); else _content.Append("f*\n"); } } #endregion // -------------------------------------------------------------------------------------------- #region Realizing graphical state /// /// Initializes the default view transformation, i.e. the transformation from the user page /// space to the PDF page space. /// void BeginPage() { if (_gfxState.Level == GraphicsStackLevelInitial) { // TODO: Is PageOriging and PageScale (== Viewport) useful? Or just public DefaultViewMatrix (like Presentation Manager has had) // May be a BeginContainer(windows, viewport) is useful for userer that are not familar with maxtrix transformations. // Flip page horizontally and mirror text. // PDF uses a standard right-handed Cartesian coordinate system with the y axis directed up // and the rotation counterclockwise. Windows uses the opposite convertion with y axis // directed down and rotation clockwise. When I started with PDFsharp I flipped pages horizontally // and then mirrored text to compensate the effect that the fipping turns text upside down. // I found this technique during analysis of PDF documents generated with PDFlib. Unfortunately // this technique leads to several problems with programms that compose or view PDF documents // generated with PDFsharp. // In PDFsharp 1.4 I implement a revised technique that does not need text mirroring any more. DefaultViewMatrix = new XMatrix(); if (_gfx.PageDirection == XPageDirection.Downwards) { // Take TrimBox into account. PageHeightPt = Size.Height; XPoint trimOffset = new XPoint(); if (_page != null && _page.TrimMargins.AreSet) { PageHeightPt += _page.TrimMargins.Top.Point + _page.TrimMargins.Bottom.Point; trimOffset = new XPoint(_page.TrimMargins.Left.Point, _page.TrimMargins.Top.Point); } // Scale with page units. switch (_gfx.PageUnit) { case XGraphicsUnit.Point: // Factor is 1. // DefaultViewMatrix.ScalePrepend(XUnit.PointFactor); break; case XGraphicsUnit.Presentation: DefaultViewMatrix.ScalePrepend(XUnit.PresentationFactor); break; case XGraphicsUnit.Inch: DefaultViewMatrix.ScalePrepend(XUnit.InchFactor); break; case XGraphicsUnit.Millimeter: DefaultViewMatrix.ScalePrepend(XUnit.MillimeterFactor); break; case XGraphicsUnit.Centimeter: DefaultViewMatrix.ScalePrepend(XUnit.CentimeterFactor); break; } if (trimOffset != new XPoint()) { Debug.Assert(_gfx.PageUnit == XGraphicsUnit.Point, "With TrimMargins set the page units must be Point. Ohter cases nyi."); DefaultViewMatrix.TranslatePrepend(trimOffset.X, -trimOffset.Y); } // Save initial graphic state. SaveState(); // Set default page transformation, if any. if (!DefaultViewMatrix.IsIdentity) { Debug.Assert(_gfxState.RealizedCtm.IsIdentity); //_gfxState.RealizedCtm = DefaultViewMatrix; const string format = Config.SignificantFigures7; double[] cm = DefaultViewMatrix.GetElements(); AppendFormatArgs("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ", cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]); } // Set page transformation //double[] cm = DefaultViewMatrix.GetElements(); //AppendFormat("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ", // cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]); } else { // Scale with page units. switch (_gfx.PageUnit) { case XGraphicsUnit.Point: // Factor is 1. // DefaultViewMatrix.ScalePrepend(XUnit.PointFactor); break; case XGraphicsUnit.Presentation: DefaultViewMatrix.ScalePrepend(XUnit.PresentationFactor); break; case XGraphicsUnit.Inch: DefaultViewMatrix.ScalePrepend(XUnit.InchFactor); break; case XGraphicsUnit.Millimeter: DefaultViewMatrix.ScalePrepend(XUnit.MillimeterFactor); break; case XGraphicsUnit.Centimeter: DefaultViewMatrix.ScalePrepend(XUnit.CentimeterFactor); break; } // Save initial graphic state. SaveState(); // Set page transformation. const string format = Config.SignificantFigures7; double[] cm = DefaultViewMatrix.GetElements(); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ", cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]); } } } /// /// Ends the content stream, i.e. ends the text mode and balances the graphic state stack. /// void EndPage() { if (_streamMode == StreamMode.Text) { _content.Append("ET\n"); _streamMode = StreamMode.Graphic; } while (_gfxStateStack.Count != 0) RestoreState(); } /// /// Begins the graphic mode (i.e. ends the text mode). /// internal void BeginGraphicMode() { if (_streamMode != StreamMode.Graphic) { if (_streamMode == StreamMode.Text) _content.Append("ET\n"); _streamMode = StreamMode.Graphic; } } /// /// Begins the graphic mode (i.e. ends the text mode). /// internal void BeginTextMode() { if (_streamMode != StreamMode.Text) { _streamMode = StreamMode.Text; _content.Append("BT\n"); // Text matrix is empty after BT _gfxState.RealizedTextPosition = new XPoint(); _gfxState.ItalicSimulationOn = false; } } StreamMode _streamMode; /// /// Makes the specified pen and brush to the current graphics objects. /// private void Realize(XPen pen, XBrush brush) { BeginPage(); BeginGraphicMode(); RealizeTransform(); if (pen != null) _gfxState.RealizePen(pen, _colorMode); // page.document.Options.ColorMode); if (brush != null) { // Render mode is 0 except for bold simulation. _gfxState.RealizeBrush(brush, _colorMode, 0, 0); // page.document.Options.ColorMode); } } /// /// Makes the specified pen to the current graphics object. /// void Realize(XPen pen) { Realize(pen, null); } /// /// Makes the specified brush to the current graphics object. /// void Realize(XBrush brush) { Realize(null, brush); } /// /// Makes the specified font and brush to the current graphics objects. /// void Realize(XFont font, XBrush brush, int renderingMode) { BeginPage(); RealizeTransform(); BeginTextMode(); _gfxState.RealizeFont(font, brush, renderingMode); } /// /// PDFsharp uses the Td operator to set the text position. Td just sets the offset of the text matrix /// and produces lesser code as Tm. /// /// The absolute text position. /// The dy. /// true if skewing for italic simulation is currently on. void AdjustTdOffset(ref XPoint pos, double dy, bool adjustSkew) { pos.Y += dy; // Reference: TABLE 5.5 Text-positioning operators / Page 406 XPoint posSave = pos; // Map from absolute to relative position. pos = pos - new XVector(_gfxState.RealizedTextPosition.X, _gfxState.RealizedTextPosition.Y); if (adjustSkew) { // In case that italic simulation is on X must be adjusted according to Y offset. Weird but works :-) pos.X -= Const.ItalicSkewAngleSinus * pos.Y; } _gfxState.RealizedTextPosition = posSave; } /// /// Makes the specified image to the current graphics object. /// string Realize(XImage image) { BeginPage(); BeginGraphicMode(); RealizeTransform(); // The transparency set for a brush also applies to images. Set opacity to 100% so image will be drawn without transparency. _gfxState.RealizeNonStrokeTransparency(1, _colorMode); XForm form = image as XForm; return form != null ? GetFormName(form) : GetImageName(image); } /// /// Realizes the current transformation matrix, if necessary. /// void RealizeTransform() { BeginPage(); if (_gfxState.Level == GraphicsStackLevelPageSpace) { BeginGraphicMode(); SaveState(); } //if (gfxState.MustRealizeCtm) if (!_gfxState.UnrealizedCtm.IsIdentity) { BeginGraphicMode(); _gfxState.RealizeCtm(); } } /// /// Convert a point from Windows world space to PDF world space. /// internal XPoint WorldToView(XPoint point) { // If EffectiveCtm is not yet realized InverseEffectiveCtm is invalid. Debug.Assert(_gfxState.UnrealizedCtm.IsIdentity, "Somewhere a RealizeTransform is missing."); #if true // See in #else case why this is correct. XPoint pt = _gfxState.WorldTransform.Transform(point); return _gfxState.InverseEffectiveCtm.Transform(new XPoint(pt.X, PageHeightPt / DefaultViewMatrix.M22 - pt.Y)); #else // Get inverted PDF world transform matrix. XMatrix invers = _gfxState.EffectiveCtm; invers.Invert(); // Apply transform in Windows world space. XPoint pt1 = _gfxState.WorldTransform.Transform(point); #if true // Do the transformation (see #else case) in one step. XPoint pt2 = new XPoint(pt1.X, PageHeightPt / DefaultViewMatrix.M22 - pt1.Y); #else // Replicable version // Apply default transformation. pt1.X = pt1.X * DefaultViewMatrix.M11; pt1.Y = pt1.Y * DefaultViewMatrix.M22; // Convert from Windows space to PDF space. XPoint pt2 = new XPoint(pt1.X, PageHeightPt - pt1.Y); pt2.X = pt2.X / DefaultViewMatrix.M11; pt2.Y = pt2.Y / DefaultViewMatrix.M22; #endif XPoint pt3 = invers.Transform(pt2); return pt3; #endif } #endregion #if GDI [Conditional("DEBUG")] void DumpPathData(PathData pathData) { XPoint[] points = new XPoint[pathData.Points.Length]; for (int i = 0; i < points.Length; i++) points[i] = new XPoint(pathData.Points[i].X, pathData.Points[i].Y); DumpPathData(points, pathData.Types); } #endif #if CORE || GDI [Conditional("DEBUG")] void DumpPathData(XPoint[] points, byte[] types) { int count = points.Length; for (int idx = 0; idx < count; idx++) { string info = PdfEncoders.Format("{0:X} {1:####0.000} {2:####0.000}", types[idx], points[idx].X, points[idx].Y); Debug.WriteLine(info, "PathData"); } } #endif /// /// Gets the owning PdfDocument of this page or form. /// internal PdfDocument Owner { get { if (_page != null) return _page.Owner; return _form.Owner; } } internal XGraphics Gfx { get { return _gfx; } } /// /// Gets the PdfResources of this page or form. /// internal PdfResources Resources { get { if (_page != null) return _page.Resources; return _form.Resources; } } /// /// Gets the size of this page or form. /// internal XSize Size { get { if (_page != null) return new XSize(_page.Width, _page.Height); return _form.Size; } } /// /// Gets the resource name of the specified font within this page or form. /// internal string GetFontName(XFont font, out PdfFont pdfFont) { if (_page != null) return _page.GetFontName(font, out pdfFont); return _form.GetFontName(font, out pdfFont); } /// /// Gets the resource name of the specified image within this page or form. /// internal string GetImageName(XImage image) { if (_page != null) return _page.GetImageName(image); return _form.GetImageName(image); } /// /// Gets the resource name of the specified form within this page or form. /// internal string GetFormName(XForm form) { if (_page != null) return _page.GetFormName(form); return _form.GetFormName(form); } internal PdfPage _page; internal XForm _form; internal PdfColorMode _colorMode; XGraphicsPdfPageOptions _options; XGraphics _gfx; readonly StringBuilder _content; /// /// The q/Q nesting level is 0. /// const int GraphicsStackLevelInitial = 0; /// /// The q/Q nesting level is 1. /// const int GraphicsStackLevelPageSpace = 1; /// /// The q/Q nesting level is 2. /// const int GraphicsStackLevelWorldSpace = 2; #region PDF Graphics State /// /// Saves the current graphical state. /// void SaveState() { Debug.Assert(_streamMode == StreamMode.Graphic, "Cannot save state in text mode."); _gfxStateStack.Push(_gfxState); _gfxState = _gfxState.Clone(); _gfxState.Level = _gfxStateStack.Count; Append("q\n"); } /// /// Restores the previous graphical state. /// void RestoreState() { Debug.Assert(_streamMode == StreamMode.Graphic, "Cannot restore state in text mode."); _gfxState = _gfxStateStack.Pop(); Append("Q\n"); } PdfGraphicsState RestoreState(InternalGraphicsState state) { int count = 1; PdfGraphicsState top = _gfxStateStack.Pop(); while (top.InternalState != state) { Append("Q\n"); count++; top = _gfxStateStack.Pop(); } Append("Q\n"); _gfxState = top; return top; } /// /// The current graphical state. /// PdfGraphicsState _gfxState; /// /// The graphical state stack. /// readonly Stack _gfxStateStack = new Stack(); #endregion /// /// The height of the PDF page in point including the trim box. /// public double PageHeightPt; /// /// The final transformation from the world space to the default page space. /// public XMatrix DefaultViewMatrix; } }