1
#region PDFsharp - A .NET library for processing PDF
4
// Stefan Lange (mailto:Stefan.Lange@pdfsharp.com)
6
// Copyright (c) 2005-2008 empira Software GmbH, Cologne (Germany)
8
// http://www.pdfsharp.com
9
// http://sourceforge.net/projects/pdfsharp
11
// Permission is hereby granted, free of charge, to any person obtaining a
12
// copy of this software and associated documentation files (the "Software"),
13
// to deal in the Software without restriction, including without limitation
14
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
// and/or sell copies of the Software, and to permit persons to whom the
16
// Software is furnished to do so, subject to the following conditions:
18
// The above copyright notice and this permission notice shall be included
19
// in all copies or substantial portions of the Software.
21
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
// DEALINGS IN THE SOFTWARE.
31
using System.Diagnostics;
32
using System.Globalization;
37
using System.Drawing.Drawing2D;
40
using System.Windows.Media;
42
using PdfSharp.Internal;
44
using PdfSharp.Pdf.Advanced;
45
using PdfSharp.Pdf.Internal;
47
namespace PdfSharp.Drawing.Pdf
49
// TODO: update the following text
51
// In PDF the current transformation matrix (CTM) can only be modified, but not set. The XGraphics
52
// object allows to set the transformation matrix, which leads to a problem. In PDF the only way
53
// to reset the CTM to its original value is to save and restore the PDF graphics state. Don't try
54
// to keep track of every modification and then reset the CTM by multiplying with the inverse matrix
55
// of the product of all modifications. PDFlib uses this 'trick', but it does not work. Because of
56
// rounding errors everything on the PDF page looks sloping after some resets. Saving and restoring
57
// the graphics state is the only possible way to reset the CTM, but because the PDF restore operator
58
// 'Q' resets not only the CTM but all other graphics state values, we have to implement our own
59
// graphics state management. This is apparently the only safe way to give the XGrahics users the
60
// illusion that they can arbitrarily set the transformation matrix.
62
// The current implementation is just a draft. Save/Restore works only once and clipping is not
63
// correctly restored in some cases.
66
/// Represents the current PDF graphics state.
68
internal sealed class PdfGraphicsState : ICloneable
70
public PdfGraphicsState(XGraphicsPdfRenderer renderer)
72
this.renderer = renderer;
74
XGraphicsPdfRenderer renderer;
76
public PdfGraphicsState Clone()
78
PdfGraphicsState state = (PdfGraphicsState)MemberwiseClone();
82
object ICloneable.Clone()
89
internal InternalGraphicsState InternalState;
91
public void PushState()
94
this.renderer.Append("q/n");
97
public void PopState()
100
this.renderer.Append("Q/n");
105
double realizedLineWith = -1;
106
int realizedLineCap = -1;
107
int realizedLineJoin = -1;
108
double realizedMiterLimit = -1;
109
XDashStyle realizedDashStyle = (XDashStyle)(-1);
110
string realizedDashPattern;
111
XColor realizedStrokeColor = XColor.Empty;
113
public void RealizePen(XPen pen, PdfColorMode colorMode)
115
XColor color = pen.Color;
116
color = ColorSpaceHelper.EnsureColorMode(colorMode, color);
118
if (this.realizedLineWith != pen.width)
120
this.renderer.AppendFormat("{0:0.###} w\n", pen.width);
121
this.realizedLineWith = pen.width;
124
if (this.realizedLineCap != (int)pen.lineCap)
126
this.renderer.AppendFormat("{0} J\n", (int)pen.lineCap);
127
this.realizedLineCap = (int)pen.lineCap;
130
if (this.realizedLineJoin != (int)pen.lineJoin)
132
this.renderer.AppendFormat("{0} j\n", (int)pen.lineJoin);
133
this.realizedLineJoin = (int)pen.lineJoin;
136
if (this.realizedLineCap == (int)XLineJoin.Miter)
138
if (this.realizedMiterLimit != (int)pen.miterLimit && (int)pen.miterLimit != 0)
140
this.renderer.AppendFormat("{0} M\n", (int)pen.miterLimit);
141
this.realizedMiterLimit = (int)pen.miterLimit;
145
if (this.realizedDashStyle != pen.dashStyle || pen.dashStyle == XDashStyle.Custom)
147
double dot = pen.Width;
148
double dash = 3 * dot;
150
// Line width 0 is not recommended but valid
151
XDashStyle dashStyle = pen.DashStyle;
153
dashStyle = XDashStyle.Solid;
157
case XDashStyle.Solid:
158
this.renderer.Append("[]0 d\n");
161
case XDashStyle.Dash:
162
this.renderer.AppendFormat("[{0:0.##} {1:0.##}]0 d\n", dash, dot);
166
this.renderer.AppendFormat("[{0:0.##}]0 d\n", dot);
169
case XDashStyle.DashDot:
170
this.renderer.AppendFormat("[{0:0.##} {1:0.##} {1:0.##} {1:0.##}]0 d\n", dash, dot);
173
case XDashStyle.DashDotDot:
174
this.renderer.AppendFormat("[{0:0.##} {1:0.##} {1:0.##} {1:0.##} {1:0.##} {1:0.##}]0 d\n", dash, dot);
177
case XDashStyle.Custom:
179
StringBuilder pdf = new StringBuilder("[", 256);
180
int len = pen.dashPattern == null ? 0 : pen.dashPattern.Length;
181
for (int idx = 0; idx < len; idx++)
185
pdf.Append(PdfEncoders.ToString(pen.dashPattern[idx] * pen.width));
187
// Make an even number of values look like in GDI+
188
if (len > 0 && len % 2 == 1)
191
pdf.Append(PdfEncoders.ToString(0.2 * pen.width));
193
pdf.AppendFormat(CultureInfo.InvariantCulture, "]{0:0.###} d\n", pen.dashOffset * pen.width);
194
string pattern = pdf.ToString();
196
// BUG: drice2@ageone.de reported a realizing problem
197
// HACK: I romove the if clause
198
//if (this.realizedDashPattern != pattern)
200
this.realizedDashPattern = pattern;
201
this.renderer.Append(pattern);
206
this.realizedDashStyle = dashStyle;
209
if (colorMode != PdfColorMode.Cmyk)
211
if (this.realizedStrokeColor.Rgb != color.Rgb)
213
this.renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb));
214
this.renderer.Append(" RG\n");
219
if (!ColorSpaceHelper.IsEqualCmyk(this.realizedStrokeColor, color))
221
this.renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk));
222
this.renderer.Append(" K\n");
226
if (this.renderer.Owner.Version >= 14 && this.realizedStrokeColor.A != color.A)
228
PdfExtGState extGState = this.renderer.Owner.ExtGStateTable.GetExtGStateStroke(color.A);
229
string gs = this.renderer.Resources.AddExtGState(extGState);
230
this.renderer.AppendFormat("{0} gs\n", gs);
232
// Must create transparany group
233
if (this.renderer.page != null && color.A < 1)
234
this.renderer.page.transparencyUsed = true;
236
this.realizedStrokeColor = color;
243
XColor realizedFillColor = XColor.Empty;
245
public void RealizeBrush(XBrush brush, PdfColorMode colorMode)
247
if (brush is XSolidBrush)
249
XColor color = ((XSolidBrush)brush).Color;
250
color = ColorSpaceHelper.EnsureColorMode(colorMode, color);
252
if (colorMode != PdfColorMode.Cmyk)
254
if (this.realizedFillColor.Rgb != color.Rgb)
256
this.renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb));
257
this.renderer.Append(" rg\n");
262
if (!ColorSpaceHelper.IsEqualCmyk(this.realizedFillColor, color))
264
this.renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk));
265
this.renderer.Append(" k\n");
269
if (this.renderer.Owner.Version >= 14 && this.realizedFillColor.A != color.A)
271
PdfExtGState extGState = this.renderer.Owner.ExtGStateTable.GetExtGStateNonStroke(color.A);
272
string gs = this.renderer.Resources.AddExtGState(extGState);
273
this.renderer.AppendFormat("{0} gs\n", gs);
275
// Must create transparany group
276
if (this.renderer.page != null && color.A < 1)
277
this.renderer.page.transparencyUsed = true;
279
this.realizedFillColor = color;
281
else if (brush is XLinearGradientBrush)
283
XMatrix matrix = this.renderer.defaultViewMatrix;
284
matrix.Prepend(this.Transform);
285
PdfShadingPattern pattern = new PdfShadingPattern(this.renderer.Owner);
286
pattern.SetupFromBrush((XLinearGradientBrush)brush, matrix);
287
string name = this.renderer.Resources.AddPattern(pattern);
288
this.renderer.AppendFormat("/Pattern cs\n", name);
289
this.renderer.AppendFormat("{0} scn\n", name);
291
// Invalidate fill color
292
this.realizedFillColor = XColor.Empty;
299
internal PdfFont realizedFont;
300
string realizedFontName = String.Empty;
301
double realizedFontSize = 0;
303
public void RealizeFont(XFont font, XBrush brush, int renderMode)
305
// So far rendering mode 0 only
306
RealizeBrush(brush, this.renderer.colorMode); // this.renderer.page.document.Options.ColorMode);
308
this.realizedFont = null;
309
string fontName = this.renderer.GetFontName(font, out this.realizedFont);
310
if (fontName != this.realizedFontName || this.realizedFontSize != font.Size)
312
if (this.renderer.Gfx.PageDirection == XPageDirection.Downwards)
313
this.renderer.AppendFormat("{0} {1:0.###} Tf\n", fontName, -font.Size);
315
this.renderer.AppendFormat("{0} {1:0.###} Tf\n", fontName, font.Size);
317
this.realizedFontName = fontName;
318
this.realizedFontSize = font.Size;
322
public XPoint realizedTextPosition = new XPoint();
326
#region Transformation
329
/// The realized current transformation matrix.
331
XMatrix realizedCtm = XMatrix.Identity;
334
/// The unrealized current transformation matrix.
336
XMatrix unrealizedCtm = XMatrix.Identity;
339
/// A flag indicating whether the CTM must be realized.
341
public bool MustRealizeCtm;
343
public XMatrix Transform
347
if (this.MustRealizeCtm)
349
XMatrix matrix = this.realizedCtm;
350
matrix.Prepend(this.unrealizedCtm);
353
return this.realizedCtm;
357
XMatrix matrix = this.realizedCtm;
359
matrix.Prepend(value);
360
this.unrealizedCtm = matrix;
361
this.MustRealizeCtm = !this.unrealizedCtm.IsIdentity;
366
/// Modifies the current transformation matrix.
368
public void MultiplyTransform(XMatrix matrix, XMatrixOrder order)
370
if (!matrix.IsIdentity)
372
this.MustRealizeCtm = true;
373
this.unrealizedCtm.Multiply(matrix, order);
378
/// Realizes the CTM.
380
public void RealizeCtm()
382
if (this.MustRealizeCtm)
384
Debug.Assert(!this.unrealizedCtm.IsIdentity, "mrCtm is unnecessarily set.");
386
double[] matrix = this.unrealizedCtm.GetElements();
387
// Up to six decimal digits to prevent round up problems
388
this.renderer.AppendFormat("{0:0.######} {1:0.######} {2:0.######} {3:0.######} {4:0.######} {5:0.######} cm\n",
389
matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
391
this.realizedCtm.Prepend(this.unrealizedCtm);
392
this.unrealizedCtm = XMatrix.Identity;
393
this.MustRealizeCtm = false;
401
public void SetAndRealizeClipRect(XRect clipRect)
403
XGraphicsPath clipPath = new XGraphicsPath();
404
clipPath.AddRectangle(clipRect);
405
RealizeClipPath(clipPath);
408
public void SetAndRealizeClipPath(XGraphicsPath clipPath)
410
RealizeClipPath(clipPath);
413
void RealizeClipPath(XGraphicsPath clipPath)
415
this.renderer.BeginGraphic();
418
this.renderer.AppendPath(clipPath.gdipPath);
421
this.renderer.AppendPath(clipPath.pathGeometry);
424
if (this.renderer.Gfx.targetContext == XGraphicTargetContext.GDI)
426
this.renderer.AppendPath(clipPath.gdipPath);
430
this.renderer.AppendPath(clipPath.pathGeometry);
433
if (clipPath.FillMode == XFillMode.Winding)
434
this.renderer.Append("W n\n");
436
this.renderer.Append("W* n\n");