ASCU_ALL/PrintPDF/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs
2021-05-25 17:00:45 +05:00

2101 lines
88 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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
{
/// <summary>
/// Represents a drawing surface for PdfPages.
/// </summary>
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);
}
/// <summary>
/// Gets the content created by this renderer.
/// </summary>
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 -----------------------------------------------------------------------------
/// <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[] { 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);
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
/// <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.
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);
}
/// <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 (_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;
}
/// <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 = comment.Replace("\n", "\n% ");
// TODO: Some more checks necessary?
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, 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);
}
}
/// <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 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;
}
/// <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, 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
/// <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)
{
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_
/// <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:" + 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
/// <summary>
/// Appends the content of a GraphicsPath object.
/// </summary>
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
/// <summary>
/// Appends the content of a GraphicsPath object.
/// </summary>
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
/// <summary>
/// Appends the content of a PathGeometry object.
/// </summary>
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
/// <summary>
/// Initializes the default view transformation, i.e. the transformation from the user page
/// space to the PDF page space.
/// </summary>
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]);
}
}
}
/// <summary>
/// Ends the content stream, i.e. ends the text mode and balances the graphic state stack.
/// </summary>
void EndPage()
{
if (_streamMode == StreamMode.Text)
{
_content.Append("ET\n");
_streamMode = StreamMode.Graphic;
}
while (_gfxStateStack.Count != 0)
RestoreState();
}
/// <summary>
/// Begins the graphic mode (i.e. ends the text mode).
/// </summary>
internal void BeginGraphicMode()
{
if (_streamMode != StreamMode.Graphic)
{
if (_streamMode == StreamMode.Text)
_content.Append("ET\n");
_streamMode = StreamMode.Graphic;
}
}
/// <summary>
/// Begins the graphic mode (i.e. ends the text mode).
/// </summary>
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;
/// <summary>
/// Makes the specified pen and brush to the current graphics objects.
/// </summary>
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);
}
}
/// <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 renderingMode)
{
BeginPage();
RealizeTransform();
BeginTextMode();
_gfxState.RealizeFont(font, brush, renderingMode);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pos">The absolute text position.</param>
/// <param name="dy">The dy.</param>
/// <param name="adjustSkew">true if skewing for italic simulation is currently on.</param>
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;
}
/// <summary>
/// Makes the specified image to the current graphics object.
/// </summary>
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);
}
/// <summary>
/// Realizes the current transformation matrix, if necessary.
/// </summary>
void RealizeTransform()
{
BeginPage();
if (_gfxState.Level == GraphicsStackLevelPageSpace)
{
BeginGraphicMode();
SaveState();
}
//if (gfxState.MustRealizeCtm)
if (!_gfxState.UnrealizedCtm.IsIdentity)
{
BeginGraphicMode();
_gfxState.RealizeCtm();
}
}
/// <summary>
/// Convert a point from Windows world space to PDF world space.
/// </summary>
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
/// <summary>
/// Gets the owning PdfDocument of this page or form.
/// </summary>
internal PdfDocument Owner
{
get
{
if (_page != null)
return _page.Owner;
return _form.Owner;
}
}
internal XGraphics Gfx
{
get { return _gfx; }
}
/// <summary>
/// Gets the PdfResources of this page or form.
/// </summary>
internal PdfResources Resources
{
get
{
if (_page != null)
return _page.Resources;
return _form.Resources;
}
}
/// <summary>
/// Gets the size of this page or form.
/// </summary>
internal XSize Size
{
get
{
if (_page != null)
return new XSize(_page.Width, _page.Height);
return _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 (_page != null)
return _page.GetFontName(font, out pdfFont);
return _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 (_page != null)
return _page.GetImageName(image);
return _form.GetImageName(image);
}
/// <summary>
/// Gets the resource name of the specified form within this page or form.
/// </summary>
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;
/// <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(_streamMode == StreamMode.Graphic, "Cannot save state in text mode.");
_gfxStateStack.Push(_gfxState);
_gfxState = _gfxState.Clone();
_gfxState.Level = _gfxStateStack.Count;
Append("q\n");
}
/// <summary>
/// Restores the previous graphical state.
/// </summary>
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;
}
/// <summary>
/// The current graphical state.
/// </summary>
PdfGraphicsState _gfxState;
/// <summary>
/// The graphical state stack.
/// </summary>
readonly Stack<PdfGraphicsState> _gfxStateStack = new Stack<PdfGraphicsState>();
#endregion
/// <summary>
/// The height of the PDF page in point including the trim box.
/// </summary>
public double PageHeightPt;
/// <summary>
/// The final transformation from the world space to the default page space.
/// </summary>
public XMatrix DefaultViewMatrix;
}
}