2101 lines
88 KiB
C#
2101 lines
88 KiB
C#
#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;
|
||
}
|
||
} |