First commit
Send all results
This commit is contained in:
543
PdfSharp/Drawing.Pdf/PdfGraphicsState.cs
Normal file
543
PdfSharp/Drawing.Pdf/PdfGraphicsState.cs
Normal file
@@ -0,0 +1,543 @@
|
||||
#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
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
#if GDI
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
#endif
|
||||
#if WPF
|
||||
#endif
|
||||
using PdfSharp.Internal;
|
||||
using PdfSharp.Pdf;
|
||||
using PdfSharp.Pdf.Advanced;
|
||||
using PdfSharp.Pdf.Internal;
|
||||
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
|
||||
namespace PdfSharp.Drawing.Pdf
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the current PDF graphics state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Completely revised for PDFsharp 1.4.
|
||||
/// </remarks>
|
||||
internal sealed class PdfGraphicsState : ICloneable
|
||||
{
|
||||
public PdfGraphicsState(XGraphicsPdfRenderer renderer)
|
||||
{
|
||||
_renderer = renderer;
|
||||
}
|
||||
readonly XGraphicsPdfRenderer _renderer;
|
||||
|
||||
public PdfGraphicsState Clone()
|
||||
{
|
||||
PdfGraphicsState state = (PdfGraphicsState)MemberwiseClone();
|
||||
return state;
|
||||
}
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return Clone();
|
||||
}
|
||||
|
||||
internal int Level;
|
||||
|
||||
internal InternalGraphicsState InternalState;
|
||||
|
||||
public void PushState()
|
||||
{
|
||||
// BeginGraphic
|
||||
_renderer.Append("q/n");
|
||||
}
|
||||
|
||||
public void PopState()
|
||||
{
|
||||
//BeginGraphic
|
||||
_renderer.Append("Q/n");
|
||||
}
|
||||
|
||||
#region Stroke
|
||||
|
||||
double _realizedLineWith = -1;
|
||||
int _realizedLineCap = -1;
|
||||
int _realizedLineJoin = -1;
|
||||
double _realizedMiterLimit = -1;
|
||||
XDashStyle _realizedDashStyle = (XDashStyle)(-1);
|
||||
string _realizedDashPattern;
|
||||
XColor _realizedStrokeColor = XColor.Empty;
|
||||
bool _realizedStrokeOverPrint;
|
||||
|
||||
public void RealizePen(XPen pen, PdfColorMode colorMode)
|
||||
{
|
||||
const string frmt2 = Config.SignificantFigures2;
|
||||
const string format = Config.SignificantFigures3;
|
||||
XColor color = pen.Color;
|
||||
bool overPrint = pen.Overprint;
|
||||
color = ColorSpaceHelper.EnsureColorMode(colorMode, color);
|
||||
|
||||
if (_realizedLineWith != pen._width)
|
||||
{
|
||||
_renderer.AppendFormatArgs("{0:" + format + "} w\n", pen._width);
|
||||
_realizedLineWith = pen._width;
|
||||
}
|
||||
|
||||
if (_realizedLineCap != (int)pen._lineCap)
|
||||
{
|
||||
_renderer.AppendFormatArgs("{0} J\n", (int)pen._lineCap);
|
||||
_realizedLineCap = (int)pen._lineCap;
|
||||
}
|
||||
|
||||
if (_realizedLineJoin != (int)pen._lineJoin)
|
||||
{
|
||||
_renderer.AppendFormatArgs("{0} j\n", (int)pen._lineJoin);
|
||||
_realizedLineJoin = (int)pen._lineJoin;
|
||||
}
|
||||
|
||||
if (_realizedLineCap == (int)XLineJoin.Miter)
|
||||
{
|
||||
if (_realizedMiterLimit != (int)pen._miterLimit && (int)pen._miterLimit != 0)
|
||||
{
|
||||
_renderer.AppendFormatInt("{0} M\n", (int)pen._miterLimit);
|
||||
_realizedMiterLimit = (int)pen._miterLimit;
|
||||
}
|
||||
}
|
||||
|
||||
if (_realizedDashStyle != pen._dashStyle || pen._dashStyle == XDashStyle.Custom)
|
||||
{
|
||||
double dot = pen.Width;
|
||||
double dash = 3 * dot;
|
||||
|
||||
// Line width 0 is not recommended but valid.
|
||||
XDashStyle dashStyle = pen.DashStyle;
|
||||
if (dot == 0)
|
||||
dashStyle = XDashStyle.Solid;
|
||||
|
||||
switch (dashStyle)
|
||||
{
|
||||
case XDashStyle.Solid:
|
||||
_renderer.Append("[]0 d\n");
|
||||
break;
|
||||
|
||||
case XDashStyle.Dash:
|
||||
_renderer.AppendFormatArgs("[{0:" + frmt2 + "} {1:" + frmt2 + "}]0 d\n", dash, dot);
|
||||
break;
|
||||
|
||||
case XDashStyle.Dot:
|
||||
_renderer.AppendFormatArgs("[{0:" + frmt2 + "}]0 d\n", dot);
|
||||
break;
|
||||
|
||||
case XDashStyle.DashDot:
|
||||
_renderer.AppendFormatArgs("[{0:" + frmt2 + "} {1:" + frmt2 + "} {1:" + frmt2 + "} {1:" + frmt2 + "}]0 d\n", dash, dot);
|
||||
break;
|
||||
|
||||
case XDashStyle.DashDotDot:
|
||||
_renderer.AppendFormatArgs("[{0:" + frmt2 + "} {1:" + frmt2 + "} {1:" + frmt2 + "} {1:" + frmt2 + "} {1:" + frmt2 + "} {1:" + frmt2 + "}]0 d\n", dash, dot);
|
||||
break;
|
||||
|
||||
case XDashStyle.Custom:
|
||||
{
|
||||
StringBuilder pdf = new StringBuilder("[", 256);
|
||||
int len = pen._dashPattern == null ? 0 : pen._dashPattern.Length;
|
||||
for (int idx = 0; idx < len; idx++)
|
||||
{
|
||||
if (idx > 0)
|
||||
pdf.Append(' ');
|
||||
pdf.Append(PdfEncoders.ToString(pen._dashPattern[idx] * pen._width));
|
||||
}
|
||||
// Make an even number of values look like in GDI+
|
||||
if (len > 0 && len % 2 == 1)
|
||||
{
|
||||
pdf.Append(' ');
|
||||
pdf.Append(PdfEncoders.ToString(0.2 * pen._width));
|
||||
}
|
||||
pdf.AppendFormat(CultureInfo.InvariantCulture, "]{0:" + format + "} d\n", pen._dashOffset * pen._width);
|
||||
string pattern = pdf.ToString();
|
||||
|
||||
// BUG: drice2@ageone.de reported a realizing problem
|
||||
// HACK: I remove the if clause
|
||||
//if (_realizedDashPattern != pattern)
|
||||
{
|
||||
_realizedDashPattern = pattern;
|
||||
_renderer.Append(pattern);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
_realizedDashStyle = dashStyle;
|
||||
}
|
||||
|
||||
if (colorMode != PdfColorMode.Cmyk)
|
||||
{
|
||||
if (_realizedStrokeColor.Rgb != color.Rgb)
|
||||
{
|
||||
_renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb));
|
||||
_renderer.Append(" RG\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ColorSpaceHelper.IsEqualCmyk(_realizedStrokeColor, color))
|
||||
{
|
||||
_renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk));
|
||||
_renderer.Append(" K\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (_renderer.Owner.Version >= 14 && (_realizedStrokeColor.A != color.A || _realizedStrokeOverPrint != overPrint))
|
||||
{
|
||||
PdfExtGState extGState = _renderer.Owner.ExtGStateTable.GetExtGStateStroke(color.A, overPrint);
|
||||
string gs = _renderer.Resources.AddExtGState(extGState);
|
||||
_renderer.AppendFormatString("{0} gs\n", gs);
|
||||
|
||||
// Must create transparency group.
|
||||
if (_renderer._page != null && color.A < 1)
|
||||
_renderer._page.TransparencyUsed = true;
|
||||
}
|
||||
_realizedStrokeColor = color;
|
||||
_realizedStrokeOverPrint = overPrint;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fill
|
||||
|
||||
XColor _realizedFillColor = XColor.Empty;
|
||||
bool _realizedNonStrokeOverPrint;
|
||||
|
||||
public void RealizeBrush(XBrush brush, PdfColorMode colorMode, int renderingMode, double fontEmSize)
|
||||
{
|
||||
// Rendering mode 2 is used for bold simulation.
|
||||
// Reference: TABLE 5.3<EFBFBD><EFBFBD>Text rendering modes / Page 402
|
||||
|
||||
XSolidBrush solidBrush = brush as XSolidBrush;
|
||||
if (solidBrush != null)
|
||||
{
|
||||
XColor color = solidBrush.Color;
|
||||
bool overPrint = solidBrush.Overprint;
|
||||
|
||||
if (renderingMode == 0)
|
||||
{
|
||||
RealizeFillColor(color, overPrint, colorMode);
|
||||
}
|
||||
else if (renderingMode == 2)
|
||||
{
|
||||
// Come here in case of bold simulation.
|
||||
RealizeFillColor(color, false, colorMode);
|
||||
//color = XColors.Green;
|
||||
RealizePen(new XPen(color, fontEmSize * Const.BoldEmphasis), colorMode);
|
||||
}
|
||||
else
|
||||
throw new InvalidOperationException("Only rendering modes 0 and 2 are currently supported.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (renderingMode != 0)
|
||||
throw new InvalidOperationException("Rendering modes other than 0 can only be used with solid color brushes.");
|
||||
|
||||
XLinearGradientBrush gradientBrush = brush as XLinearGradientBrush;
|
||||
if (gradientBrush != null)
|
||||
{
|
||||
Debug.Assert(UnrealizedCtm.IsIdentity, "Must realize ctm first.");
|
||||
XMatrix matrix = _renderer.DefaultViewMatrix;
|
||||
matrix.Prepend(EffectiveCtm);
|
||||
PdfShadingPattern pattern = new PdfShadingPattern(_renderer.Owner);
|
||||
pattern.SetupFromBrush(gradientBrush, matrix, _renderer);
|
||||
string name = _renderer.Resources.AddPattern(pattern);
|
||||
_renderer.AppendFormatString("/Pattern cs\n", name);
|
||||
_renderer.AppendFormatString("{0} scn\n", name);
|
||||
|
||||
// Invalidate fill color.
|
||||
_realizedFillColor = XColor.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode)
|
||||
{
|
||||
color = ColorSpaceHelper.EnsureColorMode(colorMode, color);
|
||||
|
||||
if (colorMode != PdfColorMode.Cmyk)
|
||||
{
|
||||
if (_realizedFillColor.IsEmpty || _realizedFillColor.Rgb != color.Rgb)
|
||||
{
|
||||
_renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb));
|
||||
_renderer.Append(" rg\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(colorMode == PdfColorMode.Cmyk);
|
||||
|
||||
if (_realizedFillColor.IsEmpty || !ColorSpaceHelper.IsEqualCmyk(_realizedFillColor, color))
|
||||
{
|
||||
_renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk));
|
||||
_renderer.Append(" k\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (_renderer.Owner.Version >= 14 && (_realizedFillColor.A != color.A || _realizedNonStrokeOverPrint != overPrint))
|
||||
{
|
||||
|
||||
PdfExtGState extGState = _renderer.Owner.ExtGStateTable.GetExtGStateNonStroke(color.A, overPrint);
|
||||
string gs = _renderer.Resources.AddExtGState(extGState);
|
||||
_renderer.AppendFormatString("{0} gs\n", gs);
|
||||
|
||||
// Must create transparency group.
|
||||
if (_renderer._page != null && color.A < 1)
|
||||
_renderer._page.TransparencyUsed = true;
|
||||
}
|
||||
_realizedFillColor = color;
|
||||
_realizedNonStrokeOverPrint = overPrint;
|
||||
}
|
||||
|
||||
internal void RealizeNonStrokeTransparency(double transparency, PdfColorMode colorMode)
|
||||
{
|
||||
XColor color = _realizedFillColor;
|
||||
color.A = transparency;
|
||||
RealizeFillColor(color, _realizedNonStrokeOverPrint, colorMode);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Text
|
||||
|
||||
internal PdfFont _realizedFont;
|
||||
string _realizedFontName = String.Empty;
|
||||
double _realizedFontSize;
|
||||
int _realizedRenderingMode; // Reference: TABLE 5.2 Text state operators / Page 398
|
||||
double _realizedCharSpace; // Reference: TABLE 5.2 Text state operators / Page 398
|
||||
|
||||
public void RealizeFont(XFont font, XBrush brush, int renderingMode)
|
||||
{
|
||||
const string format = Config.SignificantFigures3;
|
||||
|
||||
// So far rendering mode 0 (fill text) and 2 (fill, then stroke text) only.
|
||||
RealizeBrush(brush, _renderer._colorMode, renderingMode, font.Size); // _renderer.page.document.Options.ColorMode);
|
||||
|
||||
// Realize rendering mode.
|
||||
if (_realizedRenderingMode != renderingMode)
|
||||
{
|
||||
_renderer.AppendFormatInt("{0} Tr\n", renderingMode);
|
||||
_realizedRenderingMode = renderingMode;
|
||||
}
|
||||
|
||||
// Realize character spacing.
|
||||
if (_realizedRenderingMode == 0)
|
||||
{
|
||||
if (_realizedCharSpace != 0)
|
||||
{
|
||||
_renderer.Append("0 Tc\n");
|
||||
_realizedCharSpace = 0;
|
||||
}
|
||||
}
|
||||
else // _realizedRenderingMode is 2.
|
||||
{
|
||||
double charSpace = font.Size * Const.BoldEmphasis;
|
||||
if (_realizedCharSpace != charSpace)
|
||||
{
|
||||
_renderer.AppendFormatDouble("{0:" + format + "} Tc\n", charSpace);
|
||||
_realizedCharSpace = charSpace;
|
||||
}
|
||||
}
|
||||
|
||||
_realizedFont = null;
|
||||
string fontName = _renderer.GetFontName(font, out _realizedFont);
|
||||
if (fontName != _realizedFontName || _realizedFontSize != font.Size)
|
||||
{
|
||||
if (_renderer.Gfx.PageDirection == XPageDirection.Downwards)
|
||||
_renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, font.Size);
|
||||
else
|
||||
_renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, font.Size);
|
||||
_realizedFontName = fontName;
|
||||
_realizedFontSize = font.Size;
|
||||
}
|
||||
}
|
||||
|
||||
public XPoint RealizedTextPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the text transformation matrix currently skews 20<32> to the right.
|
||||
/// </summary>
|
||||
public bool ItalicSimulationOn;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transformation
|
||||
|
||||
/// <summary>
|
||||
/// The already realized part of the current transformation matrix.
|
||||
/// </summary>
|
||||
public XMatrix RealizedCtm;
|
||||
|
||||
/// <summary>
|
||||
/// The not yet realized part of the current transformation matrix.
|
||||
/// </summary>
|
||||
public XMatrix UnrealizedCtm;
|
||||
|
||||
/// <summary>
|
||||
/// Product of RealizedCtm and UnrealizedCtm.
|
||||
/// </summary>
|
||||
public XMatrix EffectiveCtm;
|
||||
|
||||
/// <summary>
|
||||
/// Inverse of EffectiveCtm used for transformation.
|
||||
/// </summary>
|
||||
public XMatrix InverseEffectiveCtm;
|
||||
|
||||
public XMatrix WorldTransform;
|
||||
|
||||
///// <summary>
|
||||
///// The world transform in PDF world space.
|
||||
///// </summary>
|
||||
//public XMatrix EffectiveCtm
|
||||
//{
|
||||
// get
|
||||
// {
|
||||
// //if (MustRealizeCtm)
|
||||
// if (!UnrealizedCtm.IsIdentity)
|
||||
// {
|
||||
// XMatrix matrix = RealizedCtm;
|
||||
// matrix.Prepend(UnrealizedCtm);
|
||||
// return matrix;
|
||||
// }
|
||||
// return RealizedCtm;
|
||||
// }
|
||||
// //set
|
||||
// //{
|
||||
// // XMatrix matrix = realizedCtm;
|
||||
// // matrix.Invert();
|
||||
// // matrix.Prepend(value);
|
||||
// // unrealizedCtm = matrix;
|
||||
// // MustRealizeCtm = !unrealizedCtm.IsIdentity;
|
||||
// //}
|
||||
//}
|
||||
|
||||
public void AddTransform(XMatrix value, XMatrixOrder matrixOrder)
|
||||
{
|
||||
// TODO: User matrixOrder
|
||||
#if DEBUG
|
||||
if (matrixOrder == XMatrixOrder.Append)
|
||||
throw new NotImplementedException("XMatrixOrder.Append");
|
||||
#endif
|
||||
XMatrix transform = value;
|
||||
if (_renderer.Gfx.PageDirection == XPageDirection.Downwards)
|
||||
{
|
||||
// Take chirality into account and
|
||||
// invert the direction of rotation.
|
||||
transform.M12 = -value.M12;
|
||||
transform.M21 = -value.M21;
|
||||
}
|
||||
UnrealizedCtm.Prepend(transform);
|
||||
|
||||
WorldTransform.Prepend(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Realizes the CTM.
|
||||
/// </summary>
|
||||
public void RealizeCtm()
|
||||
{
|
||||
//if (MustRealizeCtm)
|
||||
if (!UnrealizedCtm.IsIdentity)
|
||||
{
|
||||
Debug.Assert(!UnrealizedCtm.IsIdentity, "mrCtm is unnecessarily set.");
|
||||
|
||||
const string format = Config.SignificantFigures7;
|
||||
|
||||
double[] matrix = UnrealizedCtm.GetElements();
|
||||
// Use up to six decimal digits to prevent round up problems.
|
||||
_renderer.AppendFormatArgs("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm\n",
|
||||
matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
|
||||
|
||||
RealizedCtm.Prepend(UnrealizedCtm);
|
||||
UnrealizedCtm = new XMatrix();
|
||||
EffectiveCtm = RealizedCtm;
|
||||
InverseEffectiveCtm = EffectiveCtm;
|
||||
InverseEffectiveCtm.Invert();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Clip Path
|
||||
|
||||
public void SetAndRealizeClipRect(XRect clipRect)
|
||||
{
|
||||
XGraphicsPath clipPath = new XGraphicsPath();
|
||||
clipPath.AddRectangle(clipRect);
|
||||
RealizeClipPath(clipPath);
|
||||
}
|
||||
|
||||
public void SetAndRealizeClipPath(XGraphicsPath clipPath)
|
||||
{
|
||||
RealizeClipPath(clipPath);
|
||||
}
|
||||
|
||||
void RealizeClipPath(XGraphicsPath clipPath)
|
||||
{
|
||||
#if CORE
|
||||
DiagnosticsHelper.HandleNotImplemented("RealizeClipPath");
|
||||
#endif
|
||||
#if GDI
|
||||
// Do not render an empty path.
|
||||
if (clipPath._gdipPath.PointCount < 0)
|
||||
return;
|
||||
#endif
|
||||
#if WPF
|
||||
// Do not render an empty path.
|
||||
if (clipPath._pathGeometry.Bounds.IsEmpty)
|
||||
return;
|
||||
#endif
|
||||
_renderer.BeginGraphicMode();
|
||||
RealizeCtm();
|
||||
#if CORE
|
||||
_renderer.AppendPath(clipPath._corePath);
|
||||
#endif
|
||||
#if GDI && !WPF
|
||||
_renderer.AppendPath(clipPath._gdipPath);
|
||||
#endif
|
||||
#if WPF && !GDI
|
||||
_renderer.AppendPath(clipPath._pathGeometry);
|
||||
#endif
|
||||
#if WPF && GDI
|
||||
if (_renderer.Gfx.TargetContext == XGraphicTargetContext.GDI)
|
||||
_renderer.AppendPath(clipPath._gdipPath);
|
||||
else
|
||||
_renderer.AppendPath(clipPath._pathGeometry);
|
||||
#endif
|
||||
_renderer.Append(clipPath.FillMode == XFillMode.Winding ? "W n\n" : "W* n\n");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user