#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 { /// /// Represents the current PDF graphics state. /// /// /// Completely revised for PDFsharp 1.4. /// 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  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; /// /// Indicates that the text transformation matrix currently skews 20° to the right. /// public bool ItalicSimulationOn; #endregion #region Transformation /// /// The already realized part of the current transformation matrix. /// public XMatrix RealizedCtm; /// /// The not yet realized part of the current transformation matrix. /// public XMatrix UnrealizedCtm; /// /// Product of RealizedCtm and UnrealizedCtm. /// public XMatrix EffectiveCtm; /// /// Inverse of EffectiveCtm used for transformation. /// public XMatrix InverseEffectiveCtm; public XMatrix WorldTransform; ///// ///// The world transform in PDF world space. ///// //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); } /// /// Realizes the CTM. /// 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 } }