VectSharp  2.2.1
A light library for C# vector graphics
PDFContext.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using System.Collections;
20 using System.Collections.Generic;
21 using System.IO;
22 using System.IO.Compression;
23 using System.Linq;
24 using System.Runtime.InteropServices;
25 using System.Text;
26 using VectSharp.Filters;
27 
28 namespace VectSharp.PDF
29 {
30  internal enum SegmentType
31  {
33  }
34 
35  internal abstract class Segment
36  {
37  public abstract SegmentType Type { get; }
38  public Point[] Points { get; protected set; }
39 
40  public virtual Point Point
41  {
42  get
43  {
44  return Points[Points.Length - 1];
45  }
46  }
47 
48  public abstract Segment Clone();
49  }
50 
51  internal class MoveSegment : Segment
52  {
53  public override SegmentType Type => SegmentType.Move;
54 
55  public MoveSegment(Point p)
56  {
57  this.Points = new Point[] { p };
58  }
59 
60  public MoveSegment(double x, double y)
61  {
62  this.Points = new Point[] { new Point(x, y) };
63  }
64 
65  public override Segment Clone()
66  {
67  return new MoveSegment(this.Point);
68  }
69  }
70 
71  internal class LineSegment : Segment
72  {
73  public override SegmentType Type => SegmentType.Line;
74 
75  public LineSegment(Point p)
76  {
77  this.Points = new Point[] { p };
78  }
79 
80  public LineSegment(double x, double y)
81  {
82  this.Points = new Point[] { new Point(x, y) };
83  }
84 
85  public override Segment Clone()
86  {
87  return new LineSegment(this.Point);
88  }
89  }
90 
91  internal class CloseSegment : Segment
92  {
93  public override SegmentType Type => SegmentType.Close;
94 
95  public CloseSegment() { }
96 
97  public override Segment Clone()
98  {
99  return new CloseSegment();
100  }
101  }
102 
103  internal class CubicBezierSegment : Segment
104  {
105  public override SegmentType Type => SegmentType.CubicBezier;
106  public CubicBezierSegment(double x1, double y1, double x2, double y2, double x3, double y3)
107  {
108  Points = new Point[] { new Point(x1, y1), new Point(x2, y2), new Point(x3, y3) };
109  }
110 
111  public CubicBezierSegment(Point p1, Point p2, Point p3)
112  {
113  Points = new Point[] { p1, p2, p3 };
114  }
115 
116  public override Segment Clone()
117  {
118  return new CubicBezierSegment(Points[0], Points[1], Points[2]);
119  }
120  }
121 
122  internal interface IFigure
123  {
124  Brush Fill { get; }
125  Brush Stroke { get; }
126  double LineWidth { get; }
127  LineCaps LineCap { get; }
128  LineJoins LineJoin { get; }
129  LineDash LineDash { get; }
130  bool IsClipping { get; }
131  string Tag { get; }
132  Rectangle GetBounds();
133  }
134 
135  internal class TransformFigure : IFigure
136  {
137  public enum TransformTypes
138  {
139  Transform, Save, Restore
140  }
141 
142  public TransformTypes TransformType { get; }
143 
144  public Brush Fill { get; }
145  public Brush Stroke { get; }
146 
147  public bool IsClipping { get; }
148  public double LineWidth { get; }
149 
150  public double[,] TransformationMatrix { get; }
151 
152  public LineCaps LineCap { get; }
153 
154  public LineJoins LineJoin { get; }
155 
156  public LineDash LineDash { get; }
157  public string Tag { get; }
158 
159  public TransformFigure(TransformTypes type, double[,] transformationMatrix, string tag)
160  {
161  this.TransformType = type;
162  this.TransformationMatrix = transformationMatrix;
163  this.Tag = tag;
164  }
165 
166  public Rectangle GetBounds()
167  {
168  return Rectangle.NaN;
169  }
170  }
171 
172  internal class RasterImageFigure : IFigure
173  {
174 
175  public Brush Fill { get; }
176  public Brush Stroke { get; }
177 
178  public bool IsClipping { get; }
179  public double LineWidth { get; }
180 
181  public double[,] TransformationMatrix { get; }
182 
183  public LineCaps LineCap { get; }
184 
185  public LineJoins LineJoin { get; }
186 
187  public LineDash LineDash { get; }
188 
189  public RasterImage Image { get; }
190 
191  public string Tag { get; }
192 
193  public RasterImageFigure(RasterImage image, string tag)
194  {
195  this.Image = image;
196  this.Tag = tag;
197  }
198 
199  public Rectangle GetBounds()
200  {
201  return new Rectangle(0, 0, 1, 1);
202  }
203  }
204 
205  internal class PathFigure : IFigure
206  {
207  public Brush Fill { get; }
208  public Brush Stroke { get; }
209  public bool IsClipping { get; }
210  public double LineWidth { get; }
211 
212  public LineCaps LineCap { get; }
213 
214  public LineJoins LineJoin { get; }
215 
216  public LineDash LineDash { get; }
217  public Segment[] Segments { get; }
218 
219  public string Tag { get; }
220 
221  private Rectangle Bounds { get; }
222 
223  public PathFigure(IEnumerable<Segment> segments, Rectangle bounds, Brush fill, Brush stroke, double lineWidth, LineCaps lineCap, LineJoins lineJoin, LineDash lineDash, bool isClipping, string tag)
224  {
225  List<Segment> segs = new List<Segment>();
226 
227  foreach (Segment s in segments)
228  {
229  segs.Add(s.Clone());
230  }
231 
232  this.Segments = segs.ToArray();
233 
234  Fill = fill;
235  Stroke = stroke;
236  LineWidth = lineWidth;
237  LineCap = lineCap;
238  LineJoin = lineJoin;
239  LineDash = lineDash;
240  IsClipping = isClipping;
241  this.Tag = tag;
242 
243  if (stroke == null)
244  {
245  this.Bounds = bounds;
246  }
247  else
248  {
249  this.Bounds = new Rectangle(bounds.Location.X - lineWidth * 0.5, bounds.Location.Y - lineWidth * 0.5, bounds.Size.Width + lineWidth, bounds.Size.Height + lineWidth);
250  }
251  }
252 
253  public Rectangle GetBounds()
254  {
255  return Bounds;
256  }
257  }
258 
259  internal class TextFigure : IFigure
260  {
261  public Brush Fill { get; }
262  public Brush Stroke { get; }
263  public double LineWidth { get; }
264  public bool IsClipping { get; }
265  public LineCaps LineCap { get; }
266 
267  public LineJoins LineJoin { get; }
268 
269  public LineDash LineDash { get; }
270 
271  public string Text { get; }
272 
273  public Font Font { get; }
274 
275  public Point Position { get; }
276 
277  public TextBaselines TextBaseline { get; }
278 
279  public string Tag { get; }
280 
281  public TextFigure(string text, Font font, Point position, TextBaselines textBaseline, Brush fill, Brush stroke, double lineWidth, LineCaps lineCap, LineJoins lineJoin, LineDash lineDash, string tag)
282  {
283  Text = text;
284  Font = font;
285  Position = position;
286  TextBaseline = textBaseline;
287 
288  Fill = fill;
289  Stroke = stroke;
290  LineWidth = lineWidth;
291  LineCap = lineCap;
292  LineJoin = lineJoin;
293  LineDash = lineDash;
294  this.Tag = tag;
295  }
296 
297  public Rectangle GetBounds()
298  {
299  Font.DetailedFontMetrics metrics = this.Font.MeasureTextAdvanced(this.Text);
300 
301  switch (this.TextBaseline)
302  {
303  case TextBaselines.Top:
304  return new Rectangle(this.Position, new Size(metrics.Width, metrics.Height));
305  case TextBaselines.Bottom:
306  return new Rectangle(this.Position.X, this.Position.Y - metrics.Height, metrics.Width, metrics.Height);
307  case TextBaselines.Middle:
308  return new Rectangle(this.Position.X, this.Position.Y - metrics.Height * 0.5, metrics.Width, metrics.Height);
309  case TextBaselines.Baseline:
310  return new Rectangle(this.Position.X, this.Position.Y - metrics.Top, metrics.Width, metrics.Height);
311  default:
312  throw new System.ArgumentOutOfRangeException(nameof(TextBaseline), this.TextBaseline, "Invalid text baseline!");
313  }
314  }
315  }
316 
317  internal class PDFFontDescriptor
318  {
319  private static readonly Random TagGenerator = new Random();
320  private static readonly List<string> TagCache = new List<string>();
321 
322  public string FontName { get; }
323  public string FontFamily { get; }
324  public uint Flags { get; }
325  public double[] FontBBox { get; }
326  public int ItalicAngle => 0;
327  public double Ascent { get; }
328  public double Descent { get; }
329  public double CapHeight { get { return Ascent; } }
330  public int StemV => 80;
331  public int StemH => 80;
332 
333  public PDFFontDescriptor(TrueTypeFile ttf, bool isSubset, bool isSymbolic)
334  {
335  this.Ascent = ttf.Get1000EmAscent();
336  this.Descent = ttf.Get1000EmDescent();
337 
338  this.FontBBox = new double[] { ttf.Get1000EmXMin(), ttf.Get1000EmYMin(), ttf.Get1000EmXMax(), ttf.Get1000EmYMax() };
339 
340  bool fixedPitch = ttf.IsFixedPitch();
341 
342  bool serif = ttf.IsSerif();
343 
344  bool script = ttf.IsScript();
345 
346  bool italic = ttf.IsBold();
347 
348  bool allCap = false;
349 
350  bool smallCap = false;
351 
352  bool forceBold = false;
353 
354  this.Flags = (fixedPitch ? 1U : 0) | (serif ? 1U << 1 : 0) | (isSymbolic ? 1U << 2 : 0) | (script ? 1U << 3 : 0) | (!isSymbolic ? 1U << 5 : 0) | (italic ? 1U << 6 : 0) | (allCap ? 1U << 16 : 0) | (smallCap ? 1U << 17 : 0) | (forceBold ? 1U << 18 : 0);
355 
356  this.FontName = ttf.GetFontName();
357 
358  this.FontFamily = ttf.GetFontFamilyName();
359 
360  if (string.IsNullOrEmpty(this.FontFamily))
361  {
362  this.FontFamily = FontName;
363  }
364 
365  if (isSubset)
366  {
367  string randString = "";
368 
369  while (randString.Length == 0 || TagCache.Contains(randString))
370  {
371  randString = "";
372  for (int i = 0; i < 6; i++)
373  {
374  randString += (char)TagGenerator.Next(65, 91);
375  }
376  }
377 
378  this.FontName = randString + "+" + this.FontName;
379  }
380  }
381  }
382 
383  internal class PDFContext : IGraphicsContext
384  {
385  public string Tag { get; set; }
386  public double Width { get; }
387  public double Height { get; }
388 
389 
390  private List<Segment> _currentFigure;
391 
392  internal List<IFigure> _figures;
393 
394  private Brush _strokeStyle;
395  private Brush _fillStyle;
396  private LineDash _lineDash;
397 
398  private readonly bool _textToPaths;
399 
400  private PDFContextInterpreter.FilterOption _filterOption;
401 
402  public PDFContext(double width, double height, Colour background, bool textToPaths, PDFContextInterpreter.FilterOption filterOption)
403  {
404  this.Width = width;
405  this.Height = height;
406 
407  _currentFigure = new List<Segment>();
408  _figures = new List<IFigure>();
409 
410  _strokeStyle = Colour.FromRgb(0, 0, 0);
411  _fillStyle = Colour.FromRgb(0, 0, 0);
412  LineWidth = 1;
413 
414  LineCap = LineCaps.Butt;
415  LineJoin = LineJoins.Miter;
416  _lineDash = new LineDash(0, 0, 0);
417 
418  _textToPaths = textToPaths;
419 
421 
422  TextBaseline = TextBaselines.Top;
423 
424  this.Translate(0, height);
425  this.Scale(1, -1);
426 
427  this.Rectangle(0, 0, width, height);
428  this.SetFillStyle(background);
429  this.Fill();
430 
431  this.SetFillStyle(Colour.FromRgb(0, 0, 0));
432 
433  this._filterOption = filterOption;
434  }
435 
436 
437  public void MoveTo(double x, double y)
438  {
439  _currentFigure.Add(new MoveSegment(x, y));
440  }
441 
442  public void LineTo(double x, double y)
443  {
444  _currentFigure.Add(new LineSegment(x, y));
445  }
446 
447  public void Close()
448  {
449  _currentFigure.Add(new CloseSegment());
450  }
451 
452  public void Rectangle(double x0, double y0, double width, double height)
453  {
454  MoveTo(x0, y0);
455  LineTo(x0 + width, y0);
456  LineTo(x0 + width, y0 + height);
457  LineTo(x0, y0 + height);
458  Close();
459  }
460  public void SetStrokeStyle((int r, int g, int b, double a) style)
461  {
462  _strokeStyle = Colour.FromRgba(style.r, style.g, style.b, style.a);
463  }
464 
465  public void SetStrokeStyle(Brush style)
466  {
467  _strokeStyle = style;
468  }
469 
470  public void SetFillStyle((int r, int g, int b, double a) style)
471  {
472  _fillStyle = Colour.FromRgba(style.r, style.g, style.b, style.a);
473  }
474 
475  public void SetFillStyle(Brush style)
476  {
477  _fillStyle = style;
478  }
479 
480 
481  public Brush FillStyle { get { return _fillStyle; } }
482  public Brush StrokeStyle { get { return _strokeStyle; } }
483 
484  public double LineWidth { get; set; }
485 
486  public LineCaps LineCap { get; set; }
487  public LineJoins LineJoin { get; set; }
488 
489  internal static bool IsCompatible(Brush brush)
490  {
491  if (brush is SolidColourBrush)
492  {
493  return true;
494  }
495  else if (brush is GradientBrush gradient)
496  {
497  foreach (GradientStop stop in gradient.GradientStops)
498  {
499  if (stop.Colour.A != 1)
500  {
501  return false;
502  }
503  }
504  return true;
505  }
506  else
507  {
508  return false;
509  }
510  }
511 
512  private static Brush RemoveAlpha(Brush brush)
513  {
514  if (brush is SolidColourBrush)
515  {
516  return brush;
517  }
518  else if (brush is LinearGradientBrush linear)
519  {
520  return new LinearGradientBrush(linear.StartPoint, linear.EndPoint, from el in linear.GradientStops select new GradientStop(el.Colour.WithAlpha(1.0), el.Offset));
521  }
522  else if (brush is RadialGradientBrush radial)
523  {
524  return new RadialGradientBrush(radial.FocalPoint, radial.Centre, radial.Radius, from el in radial.GradientStops select new GradientStop(el.Colour.WithAlpha(1.0), el.Offset));
525  }
526  else
527  {
528  return null;
529  }
530  }
531 
532  internal static Brush GetAlphaBrush(Brush brush)
533  {
534  if (brush is SolidColourBrush)
535  {
536  return brush;
537  }
538  else if (brush is LinearGradientBrush linear)
539  {
540  return new LinearGradientBrush(linear.StartPoint, linear.EndPoint, from el in linear.GradientStops select new GradientStop(Colour.FromRgb(el.Colour.A, el.Colour.A, el.Colour.A), el.Offset));
541  }
542  else if (brush is RadialGradientBrush radial)
543  {
544  return new RadialGradientBrush(radial.FocalPoint, radial.Centre, radial.Radius, from el in radial.GradientStops select new GradientStop(Colour.FromRgb(el.Colour.A, el.Colour.A, el.Colour.A), el.Offset));
545  }
546  else
547  {
548  return null;
549  }
550  }
551 
552  private static GraphicsPath GetGraphicsPath(IEnumerable<Segment> segments)
553  {
554  GraphicsPath tbr = new GraphicsPath();
555 
556  foreach (Segment seg in segments)
557  {
558  switch (seg.Type)
559  {
560  case SegmentType.Close:
561  tbr.Close();
562  break;
563  case SegmentType.Move:
564  tbr.MoveTo(seg.Point);
565  break;
566  case SegmentType.Line:
567  tbr.LineTo(seg.Point);
568  break;
569  case SegmentType.CubicBezier:
570  tbr.CubicBezierTo(seg.Points[0], seg.Points[1], seg.Points[2]);
571  break;
572  }
573  }
574 
575  return tbr;
576  }
577 
578  private Graphics GetCurrentFigureMask(Brush brush, bool stroke, bool blackBackground = false)
579  {
580  Graphics gpr = new Graphics();
581 
582  GraphicsPath path = GetGraphicsPath(_currentFigure);
583 
584  if (!stroke)
585  {
586  gpr.FillPath(path, brush);
587  }
588  else
589  {
590  gpr.StrokePath(path, brush, LineWidth, LineCap, LineJoin, _lineDash);
591  }
592 
593  if (blackBackground)
594  {
595  Rectangle bounds = gpr.GetBounds();
596 
597  Graphics gpr2 = new Graphics();
598 
599  gpr2.FillRectangle(bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, Colours.White);
600  gpr2.DrawGraphics(0, 0, gpr);
601 
602  gpr = gpr2;
603  }
604 
605  return gpr;
606  }
607 
608  public void Fill()
609  {
610  if (IsCompatible(_fillStyle))
611  {
612  _figures.Add(new PathFigure(_currentFigure, VectSharp.Rectangle.NaN, _fillStyle, null, 0, LineCaps.Butt, LineJoins.Bevel, new LineDash(0, 0, 0), false, this.Tag));
613  }
614  else
615  {
616  _figures.Add(new PathFigure(_currentFigure, GetGraphicsPath(_currentFigure).GetBounds(), _fillStyle, null, 0, LineCaps.Butt, LineJoins.Bevel, new LineDash(0, 0, 0), false, this.Tag));
617  }
618 
619  _currentFigure = new List<Segment>();
620  }
621 
622  public void Stroke()
623  {
624  if (IsCompatible(_strokeStyle))
625  {
626  _figures.Add(new PathFigure(_currentFigure, VectSharp.Rectangle.NaN, null, _strokeStyle, LineWidth, LineCap, LineJoin, _lineDash, false, this.Tag));
627  }
628  else
629  {
630  _figures.Add(new PathFigure(_currentFigure, GetGraphicsPath(_currentFigure).GetBounds(), null, _strokeStyle, LineWidth, LineCap, LineJoin, _lineDash, false, this.Tag));
631  }
632 
633  _currentFigure = new List<Segment>();
634  }
635 
636  public void SetClippingPath()
637  {
638  _figures.Add(new PathFigure(_currentFigure, VectSharp.Rectangle.NaN, null, null, 0, LineCaps.Butt, LineJoins.Bevel, new LineDash(0, 0, 0), true, this.Tag));
639  _currentFigure = new List<Segment>();
640  }
641 
642  public void CubicBezierTo(double x1, double y1, double x2, double y2, double x3, double y3)
643  {
644  _currentFigure.Add(new CubicBezierSegment(x1, y1, x2, y2, x3, y3));
645  }
646 
647  public void SetLineDash(LineDash dash)
648  {
649  _lineDash = dash;
650  }
651 
652  public Font Font { get; set; }
653 
654  private void PathText(string text, double x, double y)
655  {
656  GraphicsPath textPath = new GraphicsPath().AddText(x, y, text, Font, TextBaseline);
657 
658  for (int j = 0; j < textPath.Segments.Count; j++)
659  {
660  switch (textPath.Segments[j].Type)
661  {
662  case VectSharp.SegmentType.Move:
663  this.MoveTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
664  break;
665  case VectSharp.SegmentType.Line:
666  this.LineTo(textPath.Segments[j].Point.X, textPath.Segments[j].Point.Y);
667  break;
668  case VectSharp.SegmentType.CubicBezier:
669  this.CubicBezierTo(textPath.Segments[j].Points[0].X, textPath.Segments[j].Points[0].Y, textPath.Segments[j].Points[1].X, textPath.Segments[j].Points[1].Y, textPath.Segments[j].Points[2].X, textPath.Segments[j].Points[2].Y);
670  break;
671  case VectSharp.SegmentType.Close:
672  this.Close();
673  break;
674  }
675  }
676  }
677 
678  public void FillText(string text, double x, double y)
679  {
680  if (!_textToPaths)
681  {
682  _figures.Add(new TextFigure(text, Font, new Point(x, y), TextBaseline, _fillStyle, null, 0, LineCaps.Butt, LineJoins.Miter, new LineDash(0, 0, 0), this.Tag));
683  }
684  else
685  {
686  PathText(text, x, y);
687  Fill();
688  }
689  }
690 
691  public TextBaselines TextBaseline { get; set; }
692 
693  public void Restore()
694  {
695  _figures.Add(new TransformFigure(TransformFigure.TransformTypes.Restore, null, this.Tag));
696  }
697 
698  public void Rotate(double angle)
699  {
700  _figures.Add(new TransformFigure(TransformFigure.TransformTypes.Transform, new double[,] { { Math.Cos(angle), Math.Sin(angle), 0 }, { -Math.Sin(angle), Math.Cos(angle), 0 }, { 0, 0, 1 } }, this.Tag));
701  }
702 
703  public void Save()
704  {
705  _figures.Add(new TransformFigure(TransformFigure.TransformTypes.Save, null, this.Tag));
706  }
707 
708 
709  public void StrokeText(string text, double x, double y)
710  {
711  if (!_textToPaths)
712  {
713  _figures.Add(new TextFigure(text, Font, new Point(x, y), TextBaseline, null, _strokeStyle, LineWidth, LineCap, LineJoin, _lineDash, this.Tag));
714  }
715  else
716  {
717  PathText(text, x, y);
718  Stroke();
719  }
720  }
721 
722  public void Translate(double x, double y)
723  {
724  _figures.Add(new TransformFigure(TransformFigure.TransformTypes.Transform, new double[,] { { 1, 0, x }, { 0, 1, y }, { 0, 0, 1 } }, this.Tag));
725  }
726 
727  public void Scale(double scaleX, double scaleY)
728  {
729  _figures.Add(new TransformFigure(TransformFigure.TransformTypes.Transform, new double[,] { { scaleX, 0, 0 }, { 0, scaleY, 0 }, { 0, 0, 1 } }, this.Tag));
730  }
731 
732  public void Transform(double a, double b, double c, double d, double e, double f)
733  {
734  _figures.Add(new TransformFigure(TransformFigure.TransformTypes.Transform, new double[,] { { a, b, e }, { c, d, f }, { 0, 0, 1 } }, this.Tag));
735  }
736 
737  public void DrawRasterImage(int sourceX, int sourceY, int sourceWidth, int sourceHeight, double destinationX, double destinationY, double destinationWidth, double destinationHeight, RasterImage image)
738  {
739  Save();
740 
741  MoveTo(destinationX, destinationY);
742  LineTo(destinationX + destinationWidth, destinationY);
743  LineTo(destinationX + destinationWidth, destinationY + destinationHeight);
744  LineTo(destinationX, destinationY + destinationHeight);
745  Close();
746  SetClippingPath();
747 
748  double sourceRectX = (double)sourceX / image.Width;
749  double sourceRectY = 1 - (double)sourceY / image.Height;
750  double sourceRectWidth = (double)sourceWidth / image.Width;
751  double sourceRectHeight = -(double)sourceHeight / image.Height;
752 
753  double scaleX = destinationWidth / sourceRectWidth;
754  double scaleY = destinationHeight / sourceRectHeight;
755 
756  double translationX = destinationX / scaleX - sourceRectX;
757  double translationY = destinationY / scaleY - sourceRectY;
758 
759 
760  Scale(scaleX, scaleY);
761  Translate(translationX, translationY);
762 
763  _figures.Add(new RasterImageFigure(image, this.Tag));
764 
765  Restore();
766  }
767 
768  public void DrawFilteredGraphics(Graphics graphics, IFilter filter)
769  {
770  if (this._filterOption.Operation == PDFContextInterpreter.FilterOption.FilterOperations.RasteriseAll)
771  {
772  double scale = this._filterOption.RasterisationResolution;
773 
774  Rectangle bounds = graphics.GetBounds();
775 
776  bounds = new Rectangle(bounds.Location.X - filter.TopLeftMargin.X, bounds.Location.Y - filter.TopLeftMargin.Y, bounds.Size.Width + filter.TopLeftMargin.X + filter.BottomRightMargin.X, bounds.Size.Height + filter.TopLeftMargin.Y + filter.BottomRightMargin.Y);
777 
778  if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
779  {
780  if (!this._filterOption.RasterisationResolutionRelative)
781  {
782  scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
783  }
784 
785  if (graphics.TryRasterise(bounds, scale, true, out RasterImage rasterised))
786  {
787  RasterImage filtered = null;
788 
789  if (filter is ILocationInvariantFilter locInvFilter)
790  {
791  filtered = locInvFilter.Filter(rasterised, scale);
792  }
793  else if (filter is IFilterWithLocation filterWithLoc)
794  {
795  filtered = filterWithLoc.Filter(rasterised, bounds, scale);
796  }
797 
798  if (filtered != null)
799  {
800  rasterised.Dispose();
801 
802  DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
803  }
804  }
805  else
806  {
807  throw new NotImplementedException(@"The filter could not be rasterised! You can avoid this error by doing one of the following:
808  • Add a reference to VectSharp.Raster or VectSharp.Raster.ImageSharp (you may also need to add a using directive somewhere to force the assembly to be loaded).
809  • Provide your own implementation of Graphics.RasterisationMethod.
810  • Set the FilterOption.Operation to ""IgnoreAll"" or ""SkipAll"".");
811  }
812  }
813  }
814  else if (this._filterOption.Operation == PDFContextInterpreter.FilterOption.FilterOperations.IgnoreAll)
815  {
816  graphics.CopyToIGraphicsContext(this);
817  }
818  else
819  {
820 
821  }
822  }
823  }
824 
825  /// <summary>
826  /// Contains methods to render a <see cref="Document"/> as a PDF document.
827  /// </summary>
828  public static class PDFContextInterpreter
829  {
830  private static string GetKernedString(string text, Font font)
831  {
832  List<(string, Point)> tSpans = new List<(string, Point)>();
833 
834  StringBuilder currentRun = new StringBuilder();
835  Point currentKerning = new Point();
836 
837  Point currentGlyphPlacementDelta = new Point();
838  Point currentGlyphAdvanceDelta = new Point();
839  Point nextGlyphPlacementDelta = new Point();
840  Point nextGlyphAdvanceDelta = new Point();
841 
842  for (int i = 0; i < text.Length; i++)
843  {
844  if (i < text.Length - 1)
845  {
846  currentGlyphPlacementDelta = nextGlyphPlacementDelta;
847  currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
848  nextGlyphAdvanceDelta = new Point();
849  nextGlyphPlacementDelta = new Point();
850 
851  TrueTypeFile.PairKerning kerning = font.FontFamily.TrueTypeFile.Get1000EmKerning(text[i], text[i + 1]);
852 
853  if (kerning != null)
854  {
855  currentGlyphPlacementDelta = new Point(currentGlyphPlacementDelta.X + kerning.Glyph1Placement.X, currentGlyphPlacementDelta.Y + kerning.Glyph1Placement.Y);
856  currentGlyphAdvanceDelta = new Point(currentGlyphAdvanceDelta.X + kerning.Glyph1Advance.X, currentGlyphAdvanceDelta.Y + kerning.Glyph1Advance.Y);
857 
858  nextGlyphPlacementDelta = new Point(nextGlyphPlacementDelta.X + kerning.Glyph2Placement.X, nextGlyphPlacementDelta.Y + kerning.Glyph2Placement.Y);
859  nextGlyphAdvanceDelta = new Point(nextGlyphAdvanceDelta.X + kerning.Glyph2Advance.X, nextGlyphAdvanceDelta.Y + kerning.Glyph2Advance.Y);
860  }
861  }
862 
863  if (currentGlyphPlacementDelta.X != 0 || currentGlyphPlacementDelta.Y != 0 || currentGlyphAdvanceDelta.X != 0 || currentGlyphAdvanceDelta.Y != 0)
864  {
865  if (currentRun.Length > 0)
866  {
867  tSpans.Add((currentRun.ToString(), currentKerning));
868 
869  tSpans.Add((text[i].ToString(), new Point(currentGlyphPlacementDelta.X, currentGlyphPlacementDelta.Y)));
870 
871  currentRun.Clear();
872  currentKerning = new Point(currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X, currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y);
873  }
874  else
875  {
876  tSpans.Add((text[i].ToString(), new Point(currentGlyphPlacementDelta.X + currentKerning.X, currentGlyphPlacementDelta.Y + currentKerning.Y)));
877 
878  currentRun.Clear();
879  currentKerning = new Point(currentGlyphAdvanceDelta.X - currentGlyphPlacementDelta.X, currentGlyphAdvanceDelta.Y - currentGlyphPlacementDelta.Y);
880  }
881  }
882  else
883  {
884  currentRun.Append(text[i]);
885  }
886  }
887 
888  if (currentRun.Length > 0)
889  {
890  tSpans.Add((currentRun.ToString(), currentKerning));
891  }
892 
893  StringBuilder sb = new StringBuilder();
894  sb.Append("[");
895 
896  for (int i = 0; i < tSpans.Count; i++)
897  {
898  if (tSpans[i].Item2.X != 0)
899  {
900  sb.Append((-tSpans[i].Item2.X).ToString("0.################", System.Globalization.CultureInfo.InvariantCulture));
901  }
902 
903  sb.Append("(");
904  sb.Append(EscapeStringForPDF(tSpans[i].Item1));
905  sb.Append(")");
906  }
907 
908  sb.Append("]");
909 
910  return sb.ToString();
911  }
912 
913 
914  private static string EscapeStringForPDF(string str)
915  {
916  StringBuilder sb = new StringBuilder();
917 
918  for (int i = 0; i < str.Length; i++)
919  {
920  char ch = str[i];
921 
922  if (CP1252Chars.Contains(ch))
923  {
924  if ((int)ch < 128)
925  {
926  if (!"\n\r\t\b\f()\\".Contains(ch))
927  {
928  sb.Append(ch);
929  }
930  else
931  {
932  switch (ch)
933  {
934  case '\n':
935  sb.Append("\\n");
936  break;
937  case '\r':
938  sb.Append("\\r");
939  break;
940  case '\t':
941  sb.Append("\\t");
942  break;
943  case '\b':
944  sb.Append("\\b");
945  break;
946  case '\f':
947  sb.Append("\\f");
948  break;
949  case '\\':
950  sb.Append("\\\\");
951  break;
952  case '(':
953  sb.Append("\\(");
954  break;
955  case ')':
956  sb.Append("\\)");
957  break;
958  }
959  }
960  }
961  else
962  {
963  string octal = Convert.ToString((int)ch, 8);
964  while (octal.Length < 3)
965  {
966  octal = "0" + octal;
967  }
968  sb.Append("\\" + octal);
969  }
970  }
971  else
972  {
973  sb.Append('?');
974  }
975  }
976  return sb.ToString();
977  }
978 
979 
980  private static string EscapeSymbolStringForPDF(string str, Dictionary<char, int> glyphIndices)
981  {
982  StringBuilder sb = new StringBuilder();
983 
984  for (int i = 0; i < str.Length; i++)
985  {
986  sb.Append((glyphIndices[str[i]]).ToString("X4"));
987  }
988  return sb.ToString();
989  }
990 
991  private static Dictionary<string, FontFamily> GetFontFamilies(PDFContext[] pdfContexts)
992  {
993  Dictionary<string, FontFamily> tbr = new Dictionary<string, FontFamily>();
994 
995  foreach (PDFContext ctx in pdfContexts)
996  {
997  foreach (IFigure act in ctx._figures)
998  {
999  if (act is TextFigure figure && !tbr.ContainsKey(figure.Font.FontFamily.FamilyName))
1000  {
1001  tbr.Add(figure.Font.FontFamily.FamilyName, FontFamily.ResolveFontFamily(figure.Font.FontFamily.FamilyName));
1002  }
1003  }
1004  }
1005 
1006  return tbr;
1007  }
1008 
1009  private static Dictionary<string, HashSet<char>> GetUsedChars(PDFContext[] pdfContexts)
1010  {
1011  Dictionary<string, HashSet<char>> tbr = new Dictionary<string, HashSet<char>>();
1012 
1013  foreach (PDFContext ctx in pdfContexts)
1014  {
1015  foreach (IFigure act in ctx._figures)
1016  {
1017  if (act is TextFigure figure && !tbr.ContainsKey(figure.Font.FontFamily.FamilyName))
1018  {
1019  tbr.Add(figure.Font.FontFamily.FamilyName, new HashSet<char>(figure.Text));
1020  }
1021  else if (act is TextFigure figure1)
1022  {
1023  string txt = figure1.Text;
1024  for (int i = 0; i < txt.Length; i++)
1025  {
1026  tbr[figure1.Font.FontFamily.FamilyName].Add(txt[i]);
1027  }
1028  }
1029  }
1030  }
1031 
1032  return tbr;
1033  }
1034 
1035 
1036  private static double[] GetAlphas(PDFContext[] pdfContexts)
1037  {
1038  HashSet<double> tbr = new HashSet<double>();
1039 
1040  tbr.Add(1);
1041 
1042  foreach (PDFContext ctx in pdfContexts)
1043  {
1044  foreach (IFigure act in ctx._figures)
1045  {
1046  if (act.Stroke != null)
1047  {
1048  if (act.Stroke is SolidColourBrush solid)
1049  {
1050  tbr.Add(solid.A);
1051  }
1052  else if (act.Stroke is GradientBrush gradient)
1053  {
1054  foreach (GradientStop stop in gradient.GradientStops)
1055  {
1056  tbr.Add(stop.Colour.A);
1057  }
1058  }
1059 
1060  }
1061 
1062  if (act.Fill != null)
1063  {
1064  if (act.Fill is SolidColourBrush solid)
1065  {
1066  tbr.Add(solid.A);
1067  }
1068  else if (act.Fill is GradientBrush gradient)
1069  {
1070  foreach (GradientStop stop in gradient.GradientStops)
1071  {
1072  tbr.Add(stop.Colour.A);
1073  }
1074  }
1075  }
1076  }
1077  }
1078 
1079  return tbr.ToArray();
1080  }
1081 
1082  private static Dictionary<string, RasterImage> GetAllImages(PDFContext[] pdfContexts)
1083  {
1084  Dictionary<string, RasterImage> tbr = new Dictionary<string, RasterImage>();
1085 
1086  foreach (PDFContext ctx in pdfContexts)
1087  {
1088  foreach (IFigure act in ctx._figures)
1089  {
1090  if (act is RasterImageFigure figure && !tbr.ContainsKey(figure.Image.Id))
1091  {
1092  tbr.Add(figure.Image.Id, figure.Image);
1093  }
1094  }
1095  }
1096 
1097  return tbr;
1098  }
1099 
1100 
1101  private static readonly char[] CP1252Chars = new char[] { '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u007F', '\u20AC', '\u25A1', '\u201A', '\u0192', '\u201E', '\u0000', '\u2020', '\u2021', '\u02C6', '\u2030', '\u0160', '\u2039', '\u0152', '\u25A1', '\u017D', '\u25A1', '\u25A1', '\u0000', '\u0000', '\u0000', '\u0000', '\u2022', '\u0000', '\u0000', '\u02DC', '\u2122', '\u0161', '\u203A', '\u0153', '\u25A1', '\u017E', '\u0178', '\u00A0', '\u00A1', '\u00A2', '\u00A3', '\u00A4', '\u00A5', '\u00A6', '\u00A7', '\u00A8', '\u00A9', '\u00AA', '\u00AB', '\u00AC', '\u00AD', '\u00AE', '\u00AF', '\u0000', '\u00B1', '\u00B2', '\u00B3', '\u00B4', '\u00B5', '\u00B6', '\u00B7', '\u00B8', '\u00B9', '\u00BA', '\u00BB', '\u00BC', '\u00BD', '\u00BE', '\u00BF', '\u00C0', '\u00C1', '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6', '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB', '\u00CC', '\u00CD', '\u00CE', '\u00CF', '\u00D0', '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5', '\u00D6', '\u00D7', '\u00D8', '\u00D9', '\u00DA', '\u00DB', '\u00DC', '\u00DD', '\u00DE', '\u00DF', '\u00E0', '\u00E1', '\u00E2', '\u00E3', '\u00E4', '\u00E5', '\u00E6', '\u00E7', '\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u00EC', '\u00ED', '\u00EE', '\u00EF', '\u00F0', '\u00F1', '\u00F2', '\u00F3', '\u00F4', '\u00F5', '\u00F6', '\u00F7', '\u00F8', '\u00F9', '\u00FA', '\u00FB', '\u00FC', '\u00FD', '\u00FE', '\u00FF' };
1102 
1103  /// <summary>
1104  /// Save the document to a PDF file.
1105  /// </summary>
1106  /// <param name="document">The <see cref="Document"/> to save.</param>
1107  /// <param name="fileName">The full path to the file to save. If it exists, it will be overwritten.</param>
1108  /// <param name="textOption">Defines whether the used fonts should be included in the file.</param>
1109  /// <param name="compressStreams">Indicates whether the streams in the PDF file should be compressed.</param>
1110  /// <param name="linkDestinations">A dictionary associating element tags to link targets. If this is provided, objects that have been drawn with a tag contained in the dictionary will become hyperlink to the destination specified in the dictionary. If the destination starts with a hash (#), it is interpreted as the tag of another object in the current document; otherwise, it is interpreted as an external URI.</param>
1111  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
1112  public static void SaveAsPDF(this Document document, string fileName, TextOptions textOption = TextOptions.SubsetFonts, bool compressStreams = true, Dictionary<string, string> linkDestinations = null, FilterOption filterOption = default)
1113  {
1114  using (FileStream stream = new FileStream(fileName, FileMode.Create))
1115  {
1116  document.SaveAsPDF(stream, textOption, compressStreams, linkDestinations, filterOption);
1117  }
1118  }
1119 
1120  /// <summary>
1121  /// Defines whether the used fonts should be included in the file.
1122  /// </summary>
1123  public enum TextOptions
1124  {
1125  /// <summary>
1126  /// Embeds subsetted font files containing only the glyphs for the characters that have been used.
1127  /// </summary>
1128  SubsetFonts,
1129 
1130  /// <summary>
1131  /// Does not embed any font file and converts all text items into paths.
1132  /// </summary>
1133  ConvertIntoPaths
1134  }
1135 
1136  /// <summary>
1137  /// Determines how and whether image filters are rasterised.
1138  /// </summary>
1139  public class FilterOption
1140  {
1141  /// <summary>
1142  /// Defines whether image filters should be rasterised or not.
1143  /// </summary>
1144  public enum FilterOperations
1145  {
1146  /// <summary>
1147  /// Image filters will always be rasterised.
1148  /// </summary>
1149  RasteriseAll,
1150 
1151  /// <summary>
1152  /// All image filters will be ignored.
1153  /// </summary>
1154  IgnoreAll,
1155 
1156  /// <summary>
1157  /// All the images that should be drawn with a filter will be ignored.
1158  /// </summary>
1159  SkipAll
1160  }
1161 
1162  /// <summary>
1163  /// Defines whether image filters should be rasterised or not.
1164  /// </summary>
1165  public FilterOperations Operation { get; } = FilterOperations.RasteriseAll;
1166 
1167  /// <summary>
1168  /// The resolution that will be used to rasterise image filters. Depending on the value of <see cref="RasterisationResolutionRelative"/>, this can either be an absolute resolution (i.e. a size in pixel), or a scale factor that is applied to the image size in graphics units.
1169  /// </summary>
1170  public double RasterisationResolution { get; } = 1;
1171 
1172  /// <summary>
1173  /// Determines whether the value of <see cref="RasterisationResolution"/> is absolute (i.e. a size in pixel), or relative (i.e. a scale factor that is applied to the image size in graphics units).
1174  /// </summary>
1175  public bool RasterisationResolutionRelative { get; } = true;
1176 
1177  /// <summary>
1178  /// The default options for image filter rasterisation.
1179  /// </summary>
1180  public static FilterOption Default = new FilterOption(FilterOperations.RasteriseAll, 1, true);
1181 
1182  /// <summary>
1183  /// Create a new <see cref="FilterOption"/> object.
1184  /// </summary>
1185  /// <param name="operation">Defines whether image filters should be rasterised or not.</param>
1186  /// <param name="rasterisationResolution">The resolution that will be used to rasterise image filters. Depending on the value of <see cref="RasterisationResolutionRelative"/>, this can either be an absolute resolution (i.e. a size in pixel), or a scale factor that is applied to the image size in graphics units.</param>
1187  /// <param name="rasterisationResolutionRelative">Determines whether the value of <see cref="RasterisationResolution"/> is absolute (i.e. a size in pixel), or relative (i.e. a scale factor that is applied to the image size in graphics units).</param>
1188  public FilterOption(FilterOperations operation, double rasterisationResolution, bool rasterisationResolutionRelative)
1189  {
1190  this.Operation = operation;
1191  this.RasterisationResolution = rasterisationResolution;
1192  this.RasterisationResolutionRelative = rasterisationResolutionRelative;
1193  }
1194  }
1195 
1196 
1197 
1198  /// <summary>
1199  /// Save the document to a PDF stream.
1200  /// </summary>
1201  /// <param name="document">The <see cref="Document"/> to save.</param>
1202  /// <param name="stream">The stream to which the PDF data will be written.</param>
1203  /// <param name="textOption">Defines whether the used fonts should be included in the file.</param>
1204  /// <param name="compressStreams">Indicates whether the streams in the PDF file should be compressed.</param>
1205  /// <param name="linkDestinations">A dictionary associating element tags to link targets. If this is provided, objects that have been drawn with a tag contained in the dictionary will become hyperlink to the destination specified in the dictionary. If the destination starts with a hash (#), it is interpreted as the tag of another object in the current document; otherwise, it is interpreted as an external URI.</param>
1206  /// <param name="filterOption">Defines how and whether image filters should be rasterised when rendering the image.</param>
1207  public static void SaveAsPDF(this Document document, Stream stream, TextOptions textOption = TextOptions.SubsetFonts, bool compressStreams = true, Dictionary<string, string> linkDestinations = null, FilterOption filterOption = default)
1208  {
1209  if (linkDestinations == null)
1210  {
1211  linkDestinations = new Dictionary<string, string>();
1212  }
1213 
1214  if (filterOption == null)
1215  {
1216  filterOption = FilterOption.Default;
1217  }
1218 
1219  long position = 0;
1220 
1221  List<long> objectPositions = new List<long>();
1222 
1223  int objectNum = 1;
1224  string currObject = "";
1225 
1226  int resourceObject = -1;
1227 
1228  StreamWriter sw = new StreamWriter(stream, Encoding.GetEncoding("ISO-8859-1"), 1024, true);
1229 
1230  //Header
1231  sw.Write("%PDF-1.4\n");
1232  position += 9;
1233 
1234  PDFContext[] pageContexts = new PDFContext[document.Pages.Count];
1235 
1236  for (int i = 0; i < document.Pages.Count; i++)
1237  {
1238  pageContexts[i] = new PDFContext(document.Pages[i].Width, document.Pages[i].Height, document.Pages[i].Background, textOption == TextOptions.ConvertIntoPaths, filterOption);
1239  document.Pages[i].Graphics.CopyToIGraphicsContext(pageContexts[i]);
1240  }
1241 
1242  Dictionary<string, FontFamily> allFontFamilies = GetFontFamilies(pageContexts);
1243  Dictionary<string, HashSet<char>> usedChars = GetUsedChars(pageContexts);
1244  Dictionary<string, int> fontObjectNums = new Dictionary<string, int>();
1245  Dictionary<string, string> symbolFontIDs = new Dictionary<string, string>();
1246  Dictionary<string, string> nonSymbolFontIDs = new Dictionary<string, string>();
1247  Dictionary<string, Dictionary<char, int>> symbolGlyphIndices = new Dictionary<string, Dictionary<char, int>>();
1248  double[] alphas = GetAlphas(pageContexts);
1249  Dictionary<string, RasterImage> allImages = GetAllImages(pageContexts);
1250  Dictionary<string, int> imageObjectNums = new Dictionary<string, int>();
1251 
1252  int fontId = 1;
1253 
1254  foreach (KeyValuePair<string, FontFamily> kvp in allFontFamilies)
1255  {
1256  List<char> nonSymbol = new List<char>();
1257  List<char> symbol = new List<char>();
1258 
1259  foreach (char c in usedChars[kvp.Key])
1260  {
1261  if (CP1252Chars.Contains(c))
1262  {
1263  nonSymbol.Add(c);
1264  }
1265  else
1266  {
1267  symbol.Add(c);
1268  }
1269  }
1270 
1271  //Font
1272  if (((kvp.Value.IsStandardFamily && kvp.Value.FileName != "Symbol" && kvp.Value.FileName != "ZapfDingbats") && symbol.Count == 0) || kvp.Value.TrueTypeFile == null)
1273  {
1274  fontObjectNums.Add("nonsymbol: " + kvp.Key, objectNum);
1275  fontObjectNums.Add("symbol: " + kvp.Key, objectNum);
1276  nonSymbolFontIDs.Add(kvp.Key, "F" + fontId.ToString());
1277  symbolFontIDs.Add(kvp.Key, "F" + fontId.ToString());
1278  objectPositions.Add(position);
1279  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /" + kvp.Key + " >>\nendobj\n";
1280  sw.Write(currObject);
1281  position += currObject.Length;
1282  objectNum++;
1283  fontId++;
1284  }
1285  else
1286  {
1287  int fontFileInd = objectNum;
1288 
1289  TrueTypeFile subsettedFont = kvp.Value.TrueTypeFile.SubsetFont(new string(usedChars[kvp.Key].ToArray()));
1290 
1291  Stream compressedStream;
1292 
1293  if (!compressStreams)
1294  {
1295  compressedStream = subsettedFont.FontStream;
1296  }
1297  else
1298  {
1299  compressedStream = ZLibCompress(subsettedFont.FontStream);
1300  }
1301 
1302  long length = compressedStream.Length;
1303 
1304  objectPositions.Add(position);
1305  currObject = objectNum.ToString() + " 0 obj\n<< /Length " + length.ToString() + " /Length1 " + subsettedFont.FontStream.Length.ToString();
1306 
1307  if (compressStreams)
1308  {
1309  currObject += " /Filter [ /FlateDecode ]";
1310  }
1311 
1312  currObject += " >>\nstream\n";
1313  sw.Write(currObject);
1314  position += currObject.Length;
1315  sw.Flush();
1316 
1317  compressedStream.Seek(0, SeekOrigin.Begin);
1318  compressedStream.CopyTo(stream);
1319 
1320  position += length;
1321  currObject = "endstream\nendobj\n";
1322  sw.Write(currObject);
1323  position += currObject.Length;
1324  objectNum++;
1325 
1326 
1327  if (nonSymbol.Count > 0)
1328  {
1329  PDFFontDescriptor desc = new PDFFontDescriptor(subsettedFont, true, false);
1330 
1331  int fontDescriptorInd = objectNum;
1332  objectPositions.Add(position);
1333 
1334  currObject = objectNum.ToString() + " 0 obj\n<< /Type /FontDescriptor /FontName /" + desc.FontName + " /FontFamily (" + EscapeStringForPDF(desc.FontFamily) + ") /Flags " + desc.Flags.ToString();
1335  currObject += " /FontBBox [ " + desc.FontBBox[0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + desc.FontBBox[1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + desc.FontBBox[2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + desc.FontBBox[3].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] /ItalicAngle " + desc.ItalicAngle.ToString();
1336  currObject += " /Ascent " + desc.Ascent.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /Descent " + desc.Descent.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /CapHeight " + desc.CapHeight.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /StemV " + desc.StemV.ToString() + " /StemH " + desc.StemH.ToString() + " /FontFile2 " + fontFileInd.ToString() + " 0 R >>\nendobj\n";
1337  sw.Write(currObject);
1338  position += currObject.Length;
1339  objectNum++;
1340 
1341 
1342  fontObjectNums.Add("nonsymbol: " + kvp.Key, objectNum);
1343  nonSymbolFontIDs.Add(kvp.Key, "F" + fontId.ToString());
1344  objectPositions.Add(position);
1345 
1346  int firstChar = (from el in nonSymbol select Array.IndexOf(CP1252Chars, el)).Min();
1347  int lastChar = (from el in nonSymbol select Array.IndexOf(CP1252Chars, el)).Max();
1348 
1349  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Font /Subtype /TrueType /BaseFont /" + desc.FontName + " /FirstChar " + firstChar.ToString() + " /LastChar " + lastChar.ToString() + " /FontDescriptor " + fontDescriptorInd.ToString() + " 0 R /Encoding /WinAnsiEncoding /Widths [ ";
1350 
1351  for (int i = firstChar; i <= lastChar; i++)
1352  {
1353  if (nonSymbol.Contains(CP1252Chars[i]))
1354  {
1355  currObject += subsettedFont.Get1000EmGlyphWidth(CP1252Chars[i]).ToString() + " ";
1356  }
1357  else
1358  {
1359  currObject += "0 ";
1360  }
1361  }
1362 
1363  currObject += "] >>\nendobj\n";
1364  sw.Write(currObject);
1365  position += currObject.Length;
1366  objectNum++;
1367  fontId++;
1368  }
1369 
1370 
1371  if (symbol.Count > 0)
1372  {
1373  PDFFontDescriptor desc = new PDFFontDescriptor(subsettedFont, true, true);
1374 
1375  int fontDescriptorInd = objectNum;
1376  objectPositions.Add(position);
1377 
1378  currObject = objectNum.ToString() + " 0 obj\n<< /Type /FontDescriptor /FontName /" + desc.FontName + " /FontFamily (" + EscapeStringForPDF(desc.FontFamily) + ") /Flags " + desc.Flags.ToString();
1379  currObject += " /FontBBox [ " + desc.FontBBox[0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + desc.FontBBox[1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + desc.FontBBox[2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + desc.FontBBox[3].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] /ItalicAngle " + desc.ItalicAngle.ToString();
1380  currObject += " /Ascent " + desc.Ascent.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /Descent " + desc.Descent.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /CapHeight " + desc.CapHeight.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /StemV " + desc.StemV.ToString() + " /StemH " + desc.StemH.ToString() + " /FontFile2 " + fontFileInd.ToString() + " 0 R >>\nendobj\n";
1381  sw.Write(currObject);
1382  position += currObject.Length;
1383  objectNum++;
1384 
1385 
1386  Dictionary<char, int> glyphIndices = new Dictionary<char, int>();
1387 
1388  for (int i = 0; i < symbol.Count; i++)
1389  {
1390  glyphIndices.Add(symbol[i], subsettedFont.GetGlyphIndex(symbol[i]));
1391  }
1392 
1393  symbolGlyphIndices.Add(kvp.Key, glyphIndices);
1394 
1395  int descendantFontInd = objectNum;
1396  objectPositions.Add(position);
1397  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Font /Subtype /CIDFontType2 /BaseFont /" + desc.FontName + " /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor " + fontDescriptorInd.ToString() + " 0 R ";
1398  currObject += "/W [ ";
1399 
1400  for (int i = 0; i < symbol.Count; i++)
1401  {
1402  currObject += glyphIndices[symbol[i]].ToString() + " [ ";
1403  currObject += subsettedFont.Get1000EmGlyphWidth(symbol[i]).ToString() + " ] ";
1404  }
1405 
1406  currObject += "] >>\nendobj\n";
1407  sw.Write(currObject);
1408  position += currObject.Length;
1409  objectNum++;
1410 
1411 
1412  string toUnicodeStream = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
1413  toUnicodeStream += "/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <ffff>\nendcodespacerange\n1 beginbfchar\n";
1414  for (int i = 0; i < symbol.Count; i++)
1415  {
1416  toUnicodeStream += "<" + glyphIndices[symbol[i]].ToString("X4") + "> <" + ((int)symbol[i]).ToString("X4") + ">\n";
1417  }
1418  toUnicodeStream += "endbfchar\nendcmap\nCmapName currentdict /CMap defineresource pop\nend\nend\n";
1419 
1420  MemoryStream uncompressedUnicode = new MemoryStream();
1421 
1422  using (StreamWriter usw = new StreamWriter(uncompressedUnicode, Encoding.ASCII, 1024, true))
1423  {
1424  usw.Write(toUnicodeStream);
1425  }
1426 
1427  uncompressedUnicode.Seek(0, SeekOrigin.Begin);
1428 
1429  MemoryStream compressedToUnicode;
1430 
1431  if (!compressStreams)
1432  {
1433  compressedToUnicode = uncompressedUnicode;
1434  }
1435  else
1436  {
1437  compressedToUnicode = ZLibCompress(uncompressedUnicode);
1438  }
1439 
1440  long unicodeLength = compressedToUnicode.Length;
1441 
1442  int toUnicodeInd = objectNum;
1443  objectPositions.Add(position);
1444  currObject = objectNum.ToString() + " 0 obj\n<< /Length " + unicodeLength;
1445 
1446  if (compressStreams)
1447  {
1448  currObject += " /Filter [ /FlateDecode ]";
1449  }
1450 
1451  currObject += " >>\nstream\n";
1452 
1453  sw.Write(currObject);
1454  position += currObject.Length;
1455  sw.Flush();
1456 
1457  compressedToUnicode.WriteTo(stream);
1458  position += unicodeLength;
1459 
1460  currObject = "endstream\nendobj\n";
1461  sw.Write(currObject);
1462  position += currObject.Length;
1463  objectNum++;
1464 
1465 
1466  fontObjectNums.Add("symbol: " + kvp.Key, objectNum);
1467  symbolFontIDs.Add(kvp.Key, "F" + fontId.ToString());
1468  objectPositions.Add(position);
1469  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Font /Subtype /Type0 /BaseFont /" + desc.FontName + " /Encoding /Identity-H /DescendantFonts [ " + descendantFontInd.ToString() + " 0 R ] /ToUnicode " + toUnicodeInd.ToString() + " 0 R >>\nendobj\n";
1470  sw.Write(currObject);
1471  position += currObject.Length;
1472  objectNum++;
1473  fontId++;
1474  }
1475  }
1476  }
1477 
1478  foreach (KeyValuePair<string, RasterImage> img in allImages)
1479  {
1480  RasterImage image = img.Value;
1481  int stride = image.Width * (image.HasAlpha ? 4 : 3);
1482 
1483  string filter = "";
1484 
1485  if (image.HasAlpha)
1486  {
1487  objectPositions.Add(position);
1488 
1489  filter = "";
1490  MemoryStream alphaStream = new MemoryStream();
1491 
1492  unsafe
1493  {
1494  byte* dataPointer = (byte*)image.ImageDataAddress;
1495 
1496  if (compressStreams)
1497  {
1498  filter = "/FlateDecode";
1499 
1500  for (int y = 0; y < image.Height; y++)
1501  {
1502  for (int x = 0; x < image.Width; x++)
1503  {
1504  dataPointer += 3;
1505  alphaStream.WriteByte(*dataPointer);
1506  dataPointer++;
1507  }
1508  }
1509 
1510  alphaStream.Seek(0, SeekOrigin.Begin);
1511  MemoryStream compressed = ZLibCompress(alphaStream);
1512  alphaStream.Dispose();
1513  alphaStream = compressed;
1514  }
1515  else
1516  {
1517  filter = "/ASCIIHexDecode";
1518 
1519  using (StreamWriter imageWriter = new StreamWriter(alphaStream, Encoding.ASCII, 1024, true))
1520  {
1521  for (int y = 0; y < image.Height; y++)
1522  {
1523  for (int x = 0; x < image.Width; x++)
1524  {
1525  dataPointer += 3;
1526  imageWriter.Write((*dataPointer).ToString("X2"));
1527  dataPointer++;
1528  }
1529  }
1530  }
1531  }
1532  }
1533 
1534  currObject = objectNum.ToString() + " 0 obj\n<< /Type /XObject /Subtype /Image /Width " + image.Width.ToString() + " /Height " + image.Height.ToString() + " /ColorSpace /DeviceGray /BitsPerComponent 8 /Interpolate " + (image.Interpolate ? "true" : "false") + " /Filter " + filter + " /Length " + alphaStream.Length + " >>\nstream\n";
1535 
1536  sw.Write(currObject);
1537  position += currObject.Length;
1538  sw.Flush();
1539 
1540  alphaStream.Seek(0, SeekOrigin.Begin);
1541  alphaStream.CopyTo(stream);
1542  position += alphaStream.Length;
1543 
1544  currObject = "\nendstream\nendobj\n";
1545  sw.Write(currObject);
1546  position += currObject.Length;
1547  objectNum++;
1548 
1549  alphaStream.Dispose();
1550 
1551  }
1552 
1553  objectPositions.Add(position);
1554  int imageObjectNum = objectNum;
1555 
1556  filter = "";
1557  MemoryStream imageStream = new MemoryStream();
1558 
1559  unsafe
1560  {
1561  byte* dataPointer = (byte*)image.ImageDataAddress;
1562 
1563  if (compressStreams)
1564  {
1565  filter = "/FlateDecode";
1566 
1567  if (image.HasAlpha)
1568  {
1569  for (int y = 0; y < image.Height; y++)
1570  {
1571  for (int x = 0; x < image.Width; x++)
1572  {
1573  imageStream.WriteByte(*dataPointer);
1574  dataPointer++;
1575  imageStream.WriteByte(*dataPointer);
1576  dataPointer++;
1577  imageStream.WriteByte(*dataPointer);
1578  dataPointer++;
1579  dataPointer++;
1580  }
1581  }
1582  }
1583  else
1584  {
1585  for (int y = 0; y < image.Height; y++)
1586  {
1587  for (int x = 0; x < image.Width; x++)
1588  {
1589  imageStream.WriteByte(*dataPointer);
1590  dataPointer++;
1591  imageStream.WriteByte(*dataPointer);
1592  dataPointer++;
1593  imageStream.WriteByte(*dataPointer);
1594  dataPointer++;
1595  }
1596  }
1597  }
1598 
1599  imageStream.Seek(0, SeekOrigin.Begin);
1600  MemoryStream compressed = ZLibCompress(imageStream);
1601  imageStream.Dispose();
1602  imageStream = compressed;
1603  }
1604  else
1605  {
1606  filter = "/ASCIIHexDecode";
1607 
1608  using (StreamWriter imageWriter = new StreamWriter(imageStream, Encoding.ASCII, 1024, true))
1609  {
1610  if (image.HasAlpha)
1611  {
1612  for (int y = 0; y < image.Height; y++)
1613  {
1614  for (int x = 0; x < image.Width; x++)
1615  {
1616  imageWriter.Write((*dataPointer).ToString("X2"));
1617  dataPointer++;
1618  imageWriter.Write((*dataPointer).ToString("X2"));
1619  dataPointer++;
1620  imageWriter.Write((*dataPointer).ToString("X2"));
1621  dataPointer++;
1622  dataPointer++;
1623  }
1624  }
1625  }
1626  else
1627  {
1628  for (int y = 0; y < image.Height; y++)
1629  {
1630  for (int x = 0; x < image.Width; x++)
1631  {
1632  imageWriter.Write((*dataPointer).ToString("X2"));
1633  dataPointer++;
1634  imageWriter.Write((*dataPointer).ToString("X2"));
1635  dataPointer++;
1636  imageWriter.Write((*dataPointer).ToString("X2"));
1637  dataPointer++;
1638  }
1639  }
1640  }
1641  }
1642  }
1643  }
1644 
1645  currObject = objectNum.ToString() + " 0 obj\n<< /Type /XObject /Subtype /Image /Width " + image.Width.ToString() + " /Height " + image.Height.ToString() + " /ColorSpace /DeviceRGB /BitsPerComponent 8 /Interpolate " + (image.Interpolate ? "true" : "false") + " /Filter " + filter + " /Length " + imageStream.Length + (image.HasAlpha ? " /SMask " + (objectNum - 1) + " 0 R" : "") + " >>\nstream\n";
1646 
1647  sw.Write(currObject);
1648  position += currObject.Length;
1649  sw.Flush();
1650 
1651  imageStream.Seek(0, SeekOrigin.Begin);
1652  imageStream.CopyTo(stream);
1653  position += imageStream.Length;
1654 
1655  currObject = "\nendstream\nendobj\n";
1656  sw.Write(currObject);
1657  position += currObject.Length;
1658  objectNum++;
1659 
1660  imageStream.Dispose();
1661 
1662  imageObjectNums.Add(img.Key, imageObjectNum);
1663  }
1664 
1665  int fontListObject = -1;
1666 
1667  if (allFontFamilies.Count > 0)
1668  {
1669  //Fonts
1670  objectPositions.Add(position);
1671  fontListObject = objectNum;
1672  currObject = objectNum.ToString() + " 0 obj\n<< ";
1673  foreach (KeyValuePair<string, string> kvp in nonSymbolFontIDs)
1674  {
1675  currObject += "/" + kvp.Value + " " + fontObjectNums["nonsymbol: " + kvp.Key].ToString() + " 0 R ";
1676  }
1677  foreach (KeyValuePair<string, string> kvp in symbolFontIDs)
1678  {
1679  currObject += "/" + kvp.Value + " " + fontObjectNums["symbol: " + kvp.Key].ToString() + " 0 R ";
1680  }
1681  currObject += ">>\nendobj\n";
1682  sw.Write(currObject);
1683  position += currObject.Length;
1684  objectNum++;
1685  }
1686 
1687 
1688  int[] pageContentInd = new int[document.Pages.Count];
1689 
1690 
1691  List<(string, List<(double, double, double, double)>)>[] taggedObjectRectsByPage = new List<(string, List<(double, double, double, double)>)>[document.Pages.Count];
1692  Dictionary<string, int>[] taggedObjectRectsIndicesByPage = new Dictionary<string, int>[document.Pages.Count];
1693  List<(GradientBrush, double[,], IFigure)> gradients = new System.Collections.Generic.List<(GradientBrush, double[,], IFigure)>();
1694 
1695 
1696  for (int pageInd = 0; pageInd < document.Pages.Count; pageInd++)
1697  {
1698  taggedObjectRectsByPage[pageInd] = new List<(string, List<(double, double, double, double)>)>();
1699  taggedObjectRectsIndicesByPage[pageInd] = new Dictionary<string, int>();
1700 
1701  double[,] transformationMatrix = new double[3, 3] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
1702  Stack<double[,]> savedStates = new Stack<double[,]>();
1703 
1704  MemoryStream contentStream = new MemoryStream();
1705 
1706  using (StreamWriter ctW = new StreamWriter(contentStream, Encoding.ASCII, 1024, true))
1707  {
1708  for (int i = 0; i < pageContexts[pageInd]._figures.Count; i++)
1709  {
1710  bool isTransform = pageContexts[pageInd]._figures[i] is TransformFigure;
1711 
1712  bool isClip = pageContexts[pageInd]._figures[i] is PathFigure pathFig && pathFig.IsClipping;
1713 
1714  if (!string.IsNullOrEmpty(pageContexts[pageInd]._figures[i].Tag) && !isTransform && !isClip)
1715  {
1716  (double, double, double, double) boundingRect = MeasureFigure(pageContexts[pageInd]._figures[i], ref transformationMatrix, savedStates);
1717 
1718  if (!taggedObjectRectsIndicesByPage[pageInd].TryGetValue(pageContexts[pageInd]._figures[i].Tag, out int index))
1719  {
1720  taggedObjectRectsByPage[pageInd].Add((pageContexts[pageInd]._figures[i].Tag, new List<(double, double, double, double)> { boundingRect }));
1721  taggedObjectRectsIndicesByPage[pageInd][pageContexts[pageInd]._figures[i].Tag] = taggedObjectRectsByPage[pageInd].Count - 1;
1722  }
1723  else
1724  {
1725  (string, List<(double, double, double, double)>) previousRect = taggedObjectRectsByPage[pageInd][index];
1726  taggedObjectRectsByPage[pageInd][index].Item2.Add(boundingRect);
1727  }
1728  }
1729  else if (isTransform)
1730  {
1731  MeasureFigure(pageContexts[pageInd]._figures[i], ref transformationMatrix, savedStates);
1732  }
1733 
1734  ctW.Write(FigureAsPDFString(pageContexts[pageInd]._figures[i], nonSymbolFontIDs, symbolFontIDs, symbolGlyphIndices, alphas, imageObjectNums, transformationMatrix, gradients));
1735  }
1736  }
1737 
1738  //Contents
1739  objectPositions.Add(position);
1740  contentStream.Seek(0, SeekOrigin.Begin);
1741 
1742  MemoryStream compressedStream;
1743 
1744  if (!compressStreams)
1745  {
1746  compressedStream = contentStream;
1747  }
1748  else
1749  {
1750  compressedStream = ZLibCompress(contentStream);
1751  }
1752 
1753  long streamLength = compressedStream.Length;
1754 
1755  pageContentInd[pageInd] = objectNum;
1756  currObject = objectNum.ToString() + " 0 obj\n<< /Length " + streamLength.ToString(System.Globalization.CultureInfo.InvariantCulture);
1757 
1758  if (compressStreams)
1759  {
1760  currObject += " /Filter [ /FlateDecode ]";
1761  }
1762 
1763  currObject += " >>\nstream\n";
1764 
1765  sw.Write(currObject);
1766  sw.Flush();
1767 
1768  position += currObject.Length;
1769  compressedStream.WriteTo(stream);
1770  position += streamLength;
1771 
1772  compressedStream.Dispose();
1773 
1774  currObject = "endstream\nendobj\n";
1775  sw.Write(currObject);
1776  position += currObject.Length;
1777 
1778  objectNum++;
1779  }
1780 
1781  List<int> gradientIndices = new List<int>(gradients.Count);
1782  List<int> gradientAlphaIndices = new List<int>(gradients.Count);
1783  List<int> gradientMaskIndices = new List<int>(gradients.Count);
1784 
1785  if (gradients.Count > 0)
1786  {
1787  for (int i = 0; i < gradients.Count; i++)
1788  {
1789  (GradientBrush gradient, double[,] matrix, IFigure figure) = gradients[i];
1790 
1791  //int functionObject = -1;
1792 
1793  bool hasAlpha = false;
1794 
1795  /*if (gradient.GradientStops.Count == 2)
1796  {
1797  objectPositions.Add(position);
1798 
1799  currObject = objectNum.ToString() + " 0 obj\n<< /FunctionType 2 /Domain [ 0 1 ] /C0 [ " + gradient.GradientStops[0].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[0].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[0].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
1800  currObject += "/C1 [ " + gradient.GradientStops[1].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[1].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[1].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] /N 1 >>\nendobj\n";
1801  sw.Write(currObject);
1802  functionObject = objectNum;
1803 
1804  position += currObject.Length;
1805  objectNum++;
1806 
1807  hasAlpha = gradient.GradientStops[0].Colour.A != 1 || gradient.GradientStops[1].Colour.A != 1;
1808  }
1809  else
1810  {
1811  List<double> bounds = new List<double>();
1812  List<int> functionIndices = new List<int>();
1813 
1814  for (int j = 0; j < gradient.GradientStops.Count - 1; j++)
1815  {
1816  objectPositions.Add(position);
1817 
1818  currObject = objectNum.ToString() + " 0 obj\n<< /FunctionType 2 /Domain [ 0 1 ] /C0 [ " + gradient.GradientStops[j].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
1819  currObject += "/C1 [ " + gradient.GradientStops[j + 1].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j + 1].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j + 1].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] /N 1 >>\nendobj\n";
1820  sw.Write(currObject);
1821  functionIndices.Add(objectNum);
1822 
1823  if (j < gradient.GradientStops.Count - 2)
1824  {
1825  bounds.Add(gradient.GradientStops[j + 1].Offset);
1826  }
1827 
1828  position += currObject.Length;
1829  objectNum++;
1830 
1831  if (gradient.GradientStops[j].Colour.A != 1)
1832  {
1833  hasAlpha = true;
1834  }
1835  }
1836 
1837  objectPositions.Add(position);
1838 
1839  currObject = objectNum.ToString() + " 0 obj\n<< /FunctionType 3 /Domain [ 0 1 ] /Functions [ ";
1840 
1841  for (int j = 0; j < functionIndices.Count; j++)
1842  {
1843  currObject += functionIndices[j].ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R ";
1844  }
1845 
1846  currObject += "] /Bounds [ ";
1847 
1848  for (int j = 0; j < bounds.Count; j++)
1849  {
1850  currObject += bounds[j].ToString(System.Globalization.CultureInfo.InvariantCulture) + " ";
1851  }
1852 
1853  currObject += "] /Encode [ ";
1854 
1855  for (int j = 0; j < functionIndices.Count; j++)
1856  {
1857  currObject += "0 1 ";
1858  }
1859 
1860  currObject += "] >>\nendobj\n";
1861 
1862 
1863  sw.Write(currObject);
1864  functionObject = objectNum;
1865 
1866  position += currObject.Length;
1867  objectNum++;
1868 
1869  }
1870 
1871  if (gradient is LinearGradientBrush linear)
1872  {
1873  objectPositions.Add(position);
1874 
1875  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Pattern /PatternType 2 /Matrix [ " +
1876 
1877  matrix[0, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1878  matrix[1, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1879  matrix[0, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1880  matrix[1, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1881  matrix[0, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1882  matrix[1, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] ";
1883 
1884  currObject += "/Shading << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [ " + linear.StartPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + linear.StartPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + linear.EndPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + linear.EndPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
1885 
1886  currObject += "/Domain [ 0 1 ] /Extend [ true true ] /Function " + functionObject.ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R >> >>\nendobj\n";
1887  sw.Write(currObject);
1888  gradientIndices.Add(objectNum);
1889 
1890  position += currObject.Length;
1891  objectNum++;
1892  }
1893  else if (gradient is RadialGradientBrush radial)
1894  {
1895  objectPositions.Add(position);
1896 
1897  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Pattern /PatternType 2 /Matrix [ " +
1898 
1899  matrix[0, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1900  matrix[1, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1901  matrix[0, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1902  matrix[1, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1903  matrix[0, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1904  matrix[1, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] ";
1905 
1906  currObject += "/Shading << /ShadingType 3 /ColorSpace /DeviceRGB /Coords [ " + radial.FocalPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + radial.FocalPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 " + radial.Centre.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + radial.Centre.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + radial.Radius.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
1907 
1908  currObject += "/Domain [ 0 1 ] /Extend [ true true ] /Function " + functionObject.ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R >> >>\nendobj\n";
1909  sw.Write(currObject);
1910  gradientIndices.Add(objectNum);
1911 
1912  position += currObject.Length;
1913  objectNum++;
1914  }*/
1915 
1916  WriteGradient(true, ref gradient, ref objectPositions, ref position, ref currObject, ref objectNum, ref sw, ref hasAlpha, ref matrix, ref gradientIndices);
1917 
1918  if (!hasAlpha)
1919  {
1920  gradientAlphaIndices.Add(-1);
1921  gradientMaskIndices.Add(-1);
1922  }
1923  else
1924  {
1925  /*objectPositions.Add(position);
1926  int alphaGradientIndex = objectNum;
1927 
1928 
1929  **/
1930 
1931  GradientBrush alphaGradient = (GradientBrush)PDFContext.GetAlphaBrush(gradient);
1932 
1933  bool hasAlpha2 = false;
1934 
1935  WriteGradient(false, ref alphaGradient, ref objectPositions, ref position, ref currObject, ref objectNum, ref sw, ref hasAlpha2, ref matrix, ref gradientAlphaIndices);
1936 
1937  int alphaGradientIndex = gradientAlphaIndices[gradientAlphaIndices.Count - 1];
1938 
1939  Rectangle bbox = figure.GetBounds();
1940 
1941  MemoryStream contentStream = new MemoryStream();
1942 
1943  using (StreamWriter ctW = new StreamWriter(contentStream, Encoding.ASCII, 1024, true))
1944  {
1945  ctW.Write("q\n");
1946  ctW.Write(bbox.Location.X.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + bbox.Location.Y.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + (bbox.Location.X + bbox.Size.Width).ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + (bbox.Location.Y + bbox.Size.Height).ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " re\n");
1947  ctW.Write("/Pattern cs\n");
1948  ctW.Write("/pa" + gradientAlphaIndices.Count + " scn\n");
1949  ctW.Write("f\n");
1950  ctW.Write("Q\n");
1951  }
1952 
1953  contentStream.Seek(0, SeekOrigin.Begin);
1954 
1955  MemoryStream compressedStream;
1956 
1957  if (!compressStreams)
1958  {
1959  compressedStream = contentStream;
1960  }
1961  else
1962  {
1963  compressedStream = ZLibCompress(contentStream);
1964  }
1965 
1966  long streamLength = compressedStream.Length;
1967 
1968  objectPositions.Add(position);
1969  int maskIndex = objectNum;
1970 
1971  currObject = objectNum.ToString() + " 0 obj\n<< /Type /XObject /Subtype /Form " +
1972  "/Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> " +
1973  "/BBox [ " +
1974  bbox.Location.X.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + bbox.Location.Y.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + (bbox.Location.X + bbox.Size.Width).ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + (bbox.Location.Y + bbox.Size.Height).ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) +
1975  " ] " + /*"/Matrix [ " +
1976  matrix[0, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1977  matrix[1, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1978  matrix[0, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1979  matrix[1, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1980  matrix[0, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
1981  matrix[1, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] " +*/
1982  "/Resources << /Pattern << /pa" + gradientAlphaIndices.Count + " " + alphaGradientIndex.ToString() + " 0 R >> >> " +
1983 
1984  "/Length " + streamLength.ToString();
1985 
1986  if (compressStreams)
1987  {
1988  if (compressStreams)
1989  {
1990  currObject += " /Filter [ /FlateDecode ]";
1991  }
1992  }
1993 
1994  currObject += " >>\nstream\n";
1995 
1996  sw.Write(currObject);
1997  sw.Flush();
1998 
1999  position += currObject.Length;
2000  compressedStream.WriteTo(stream);
2001  position += streamLength;
2002 
2003  compressedStream.Dispose();
2004 
2005  currObject = "endstream\nendobj\n";
2006  sw.Write(currObject);
2007  position += currObject.Length;
2008 
2009  objectNum++;
2010 
2011 
2012  objectPositions.Add(position);
2013  int actualMaskIndex = objectNum;
2014 
2015  gradientMaskIndices.Add(actualMaskIndex);
2016 
2017  currObject = objectNum.ToString() + " 0 obj\n<< /Type /ExtGState /SMask << /Type /Mask /S /Luminosity /G " + maskIndex.ToString() + " 0 R >> >>\nendobj\n";
2018  sw.Write(currObject);
2019  position += currObject.Length;
2020  objectNum++;
2021  }
2022 
2023  }
2024  }
2025 
2026  if (allFontFamilies.Count > 0)
2027  {
2028 
2029  //Resources
2030  objectPositions.Add(position);
2031  resourceObject = objectNum;
2032  currObject = objectNum.ToString() + " 0 obj\n<< /Font " + fontListObject.ToString() + " 0 R";
2033 
2034  if (alphas.Length > 0 || gradientMaskIndices.Where(x => x >= 0).Any())
2035  {
2036  currObject += " /ExtGState <<\n";
2037 
2038  for (int i = 0; i < alphas.Length; i++)
2039  {
2040  currObject += "/a" + i.ToString() + " << /CA " + alphas[i].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /ca " + alphas[i].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " >>\n";
2041  }
2042 
2043  for (int i = 0; i < gradientMaskIndices.Count; i++)
2044  {
2045  if (gradientMaskIndices[i] >= 0)
2046  {
2047  currObject += "/ma" + i.ToString() + " " + gradientMaskIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R\n";
2048  }
2049  }
2050 
2051  currObject += ">>";
2052  }
2053 
2054  if (imageObjectNums.Count > 0)
2055  {
2056  currObject += " /XObject <<";
2057 
2058  foreach (KeyValuePair<string, int> kvp in imageObjectNums)
2059  {
2060  currObject += " /Img" + kvp.Value.ToString() + " " + kvp.Value.ToString() + " 0 R";
2061  }
2062 
2063  currObject += " >>";
2064  }
2065 
2066  if (gradientIndices.Count > 0)
2067  {
2068  currObject += " /Pattern << ";
2069 
2070  for (int i = 0; i < gradientIndices.Count; i++)
2071  {
2072  currObject += "/p" + i.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradientIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R ";
2073  }
2074 
2075  currObject += ">>";
2076  }
2077 
2078  currObject += " >>\nendobj\n";
2079  sw.Write(currObject);
2080  position += currObject.Length;
2081  objectNum++;
2082  }
2083  else
2084  {
2085  //Resources
2086  objectPositions.Add(position);
2087  resourceObject = objectNum;
2088  currObject = objectNum.ToString() + " 0 obj\n<<";
2089 
2090  if (alphas.Length > 0 || gradientMaskIndices.Where(x => x >= 0).Any())
2091  {
2092  currObject += " /ExtGState <<\n";
2093 
2094  for (int i = 0; i < alphas.Length; i++)
2095  {
2096  currObject += "/a" + i.ToString() + " << /CA " + alphas[i].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " /ca " + alphas[i].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " >>\n";
2097  }
2098 
2099  for (int i = 0; i < gradientMaskIndices.Count; i++)
2100  {
2101  if (gradientMaskIndices[i] >= 0)
2102  {
2103  currObject += "/ma" + i.ToString() + " " + gradientMaskIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R\n";
2104  }
2105  }
2106 
2107  currObject += ">>";
2108  }
2109 
2110  if (imageObjectNums.Count > 0)
2111  {
2112  currObject += " /XObject <<";
2113 
2114  foreach (KeyValuePair<string, int> kvp in imageObjectNums)
2115  {
2116  currObject += " /Img" + kvp.Value.ToString() + " " + kvp.Value.ToString() + " 0 R";
2117  }
2118 
2119  currObject += " >>";
2120  }
2121 
2122  if (gradientIndices.Count > 0)
2123  {
2124  currObject += " /Pattern << ";
2125 
2126  for (int i = 0; i < gradientIndices.Count; i++)
2127  {
2128  currObject += "/p" + i.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradientIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R ";
2129  }
2130 
2131  currObject += ">>";
2132  }
2133 
2134  currObject += " >>\nendobj\n";
2135  sw.Write(currObject);
2136  position += currObject.Length;
2137  objectNum++;
2138  }
2139 
2140  //Catalog
2141  objectPositions.Add(position);
2142  int rootObject = objectNum;
2143  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Catalog /Pages " + (objectNum + 1).ToString() + " 0 R >>\nendobj\n";
2144  sw.Write(currObject);
2145  position += currObject.Length;
2146  objectNum++;
2147 
2148  objectPositions.Add(position);
2149  int pageParent = objectNum;
2150  objectNum++;
2151 
2152  List<int> pageObjectNums = new List<int>();
2153 
2154  //We do not have enough information to resolve all relative links yet (we need the object number for all the pages).
2155  List<(int annotationObjectNum, (double, double, double, double) annotationOrigin, int annotationDestinationPage, (double, double, double, double) annotationDestination)> postponedAnnotations = new List<(int, (double, double, double, double), int, (double, double, double, double))>();
2156 
2157  //Page
2158  for (int i = 0; i < document.Pages.Count; i++)
2159  {
2160  List<int> annotationsToInclude = new List<int>();
2161 
2162  //Annotations
2163  for (int j = 0; j < taggedObjectRectsByPage[i].Count; j++)
2164  {
2165  if (linkDestinations.TryGetValue(taggedObjectRectsByPage[i][j].Item1, out string destination))
2166  {
2167  if (destination.StartsWith("#"))
2168  {
2169  //Leave these for later, once we have computed the object number for all the pages. But we need to include the annotation number in the page, so we start processing the annotation now.
2170  for (int k = 0; k < taggedObjectRectsIndicesByPage.Length; k++)
2171  {
2172  if (taggedObjectRectsIndicesByPage[k].TryGetValue(destination.Substring(1), out int index))
2173  {
2174  for (int l = 0; l < taggedObjectRectsByPage[i][j].Item2.Count; l++)
2175  {
2176  objectPositions.Add(position);
2177  annotationsToInclude.Add(objectNum);
2178  postponedAnnotations.Add((objectNum, taggedObjectRectsByPage[i][j].Item2[l], k, taggedObjectRectsByPage[k][index].Item2[0]));
2179  objectNum++;
2180  }
2181 
2182  //Only consider the first match for the local destination.
2183  break;
2184  }
2185  }
2186  }
2187  else
2188  {
2189  for (int l = 0; l < taggedObjectRectsByPage[i][j].Item2.Count; l++)
2190  {
2191  objectPositions.Add(position);
2192  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Annot /Subtype /Link /Rect [" + taggedObjectRectsByPage[i][j].Item2[l].Item1.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + taggedObjectRectsByPage[i][j].Item2[l].Item2.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + taggedObjectRectsByPage[i][j].Item2[l].Item3.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + taggedObjectRectsByPage[i][j].Item2[l].Item4.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + "] " +
2193  "/A << /Type /Action /S /URI /URI (" + destination + ") >> >>\nendobj\n";
2194  sw.Write(currObject);
2195  annotationsToInclude.Add(objectNum);
2196  objectNum++;
2197  position += currObject.Length;
2198  }
2199  }
2200  }
2201  }
2202 
2203  //Page
2204  objectPositions.Add(position);
2205  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Page /Parent " + pageParent.ToString() + " 0 R /MediaBox [0 0 " + document.Pages[i].Width.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + document.Pages[i].Height.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + "] /Resources " + resourceObject.ToString() + " 0 R /Contents " + pageContentInd[i].ToString() + " 0 R ";
2206 
2207  if (annotationsToInclude.Count > 0)
2208  {
2209  StringBuilder annotations = new StringBuilder();
2210  annotations.Append("/Annots [ ");
2211  for (int j = 0; j < annotationsToInclude.Count; j++)
2212  {
2213  annotations.Append(annotationsToInclude[j].ToString() + " 0 R ");
2214  }
2215  annotations.Append("] ");
2216  currObject += annotations.ToString();
2217  }
2218 
2219  currObject += ">>\nendobj\n";
2220 
2221  sw.Write(currObject);
2222  pageObjectNums.Add(objectNum);
2223  objectNum++;
2224  position += currObject.Length;
2225  }
2226 
2227  //Now we have enough information for the postponed annotations.
2228  foreach ((int annotationObjectNum, (double, double, double, double) annotationOrigin, int annotationDestinationPage, (double, double, double, double) annotationDestination) in postponedAnnotations)
2229  {
2230  objectPositions[annotationObjectNum - 1] = position;
2231  currObject = annotationObjectNum.ToString() + " 0 obj\n<< /Type /Annot /Subtype /Link /Rect [" + annotationOrigin.Item1.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + annotationOrigin.Item2.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + annotationOrigin.Item3.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + annotationOrigin.Item4.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + "] " +
2232  "/Dest [ " + pageObjectNums[annotationDestinationPage].ToString() + " 0 R /XYZ " + annotationDestination.Item1.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + annotationDestination.Item4.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " 0 ] >>\nendobj\n";
2233  sw.Write(currObject);
2234  position += currObject.Length;
2235  }
2236 
2237  //Pages
2238  objectPositions[pageParent - 1] = position;
2239  currObject = pageParent.ToString() + " 0 obj\n<< /Type /Pages /Kids [ ";
2240  for (int i = 0; i < document.Pages.Count; i++)
2241  {
2242  //currObject += (pageParent + i + 1).ToString() + " 0 R ";
2243  currObject += pageObjectNums[i].ToString() + " 0 R ";
2244  }
2245  currObject += "] /Count " + document.Pages.Count + " >>\nendobj\n\n";
2246  sw.Write(currObject);
2247  position += currObject.Length;
2248 
2249  //XRef
2250  sw.Write("xref\n0 " + (objectPositions.Count + 1).ToString() + "\n0000000000 65535 f \n");
2251  for (int i = 0; i < objectPositions.Count; i++)
2252  {
2253  sw.Write(objectPositions[i].ToString("0000000000", System.Globalization.CultureInfo.InvariantCulture) + " 00000 n \n");
2254  }
2255 
2256  //Trailer
2257  sw.Write("trailer\n<< /Size " + (objectPositions.Count + 1).ToString() + " /Root " + rootObject.ToString() + " 0 R >>\nstartxref\n" + position.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + "\n%%EOF\n");
2258 
2259  sw.Flush();
2260  sw.Dispose();
2261  }
2262 
2263  private static void WriteGradient(bool includeMatrix, ref GradientBrush gradient, ref List<long> objectPositions, ref long position, ref string currObject, ref int objectNum, ref StreamWriter sw, ref bool hasAlpha, ref double[,] matrix, ref List<int> gradientIndices)
2264  {
2265  int functionObject = -1;
2266 
2267  if (gradient.GradientStops.Count == 2)
2268  {
2269  objectPositions.Add(position);
2270 
2271  currObject = objectNum.ToString() + " 0 obj\n<< /FunctionType 2 /Domain [ 0 1 ] /C0 [ " + gradient.GradientStops[0].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[0].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[0].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
2272  currObject += "/C1 [ " + gradient.GradientStops[1].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[1].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[1].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] /N 1 >>\nendobj\n";
2273  sw.Write(currObject);
2274  functionObject = objectNum;
2275 
2276  position += currObject.Length;
2277  objectNum++;
2278 
2279  hasAlpha = gradient.GradientStops[0].Colour.A != 1 || gradient.GradientStops[1].Colour.A != 1;
2280  }
2281  else
2282  {
2283  List<double> bounds = new List<double>();
2284  List<int> functionIndices = new List<int>();
2285 
2286  for (int j = 0; j < gradient.GradientStops.Count - 1; j++)
2287  {
2288  objectPositions.Add(position);
2289 
2290  currObject = objectNum.ToString() + " 0 obj\n<< /FunctionType 2 /Domain [ 0 1 ] /C0 [ " + gradient.GradientStops[j].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
2291  currObject += "/C1 [ " + gradient.GradientStops[j + 1].Colour.R.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j + 1].Colour.G.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + gradient.GradientStops[j + 1].Colour.B.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] /N 1 >>\nendobj\n";
2292  sw.Write(currObject);
2293  functionIndices.Add(objectNum);
2294 
2295  if (j < gradient.GradientStops.Count - 2)
2296  {
2297  bounds.Add(gradient.GradientStops[j + 1].Offset);
2298  }
2299 
2300  position += currObject.Length;
2301  objectNum++;
2302 
2303  if (gradient.GradientStops[j].Colour.A != 1)
2304  {
2305  hasAlpha = true;
2306  }
2307  }
2308 
2309  if (gradient.GradientStops[gradient.GradientStops.Count - 1].Colour.A != 1)
2310  {
2311  hasAlpha = true;
2312  }
2313 
2314  objectPositions.Add(position);
2315 
2316  currObject = objectNum.ToString() + " 0 obj\n<< /FunctionType 3 /Domain [ 0 1 ] /Functions [ ";
2317 
2318  for (int j = 0; j < functionIndices.Count; j++)
2319  {
2320  currObject += functionIndices[j].ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R ";
2321  }
2322 
2323  currObject += "] /Bounds [ ";
2324 
2325  for (int j = 0; j < bounds.Count; j++)
2326  {
2327  currObject += bounds[j].ToString(System.Globalization.CultureInfo.InvariantCulture) + " ";
2328  }
2329 
2330  currObject += "] /Encode [ ";
2331 
2332  for (int j = 0; j < functionIndices.Count; j++)
2333  {
2334  currObject += "0 1 ";
2335  }
2336 
2337  currObject += "] >>\nendobj\n";
2338 
2339 
2340  sw.Write(currObject);
2341  functionObject = objectNum;
2342 
2343  position += currObject.Length;
2344  objectNum++;
2345 
2346  }
2347 
2348  if (gradient is LinearGradientBrush linear)
2349  {
2350  objectPositions.Add(position);
2351 
2352  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Pattern /PatternType 2 ";
2353 
2354  if (includeMatrix)
2355  {
2356  currObject += "/Matrix [ " +
2357 
2358  matrix[0, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2359  matrix[1, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2360  matrix[0, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2361  matrix[1, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2362  matrix[0, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2363  matrix[1, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] ";
2364  }
2365 
2366  currObject += "/Shading << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [ " + linear.StartPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + linear.StartPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + linear.EndPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + linear.EndPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
2367 
2368  currObject += "/Domain [ 0 1 ] /Extend [ true true ] /Function " + functionObject.ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R >> >>\nendobj\n";
2369  sw.Write(currObject);
2370  gradientIndices.Add(objectNum);
2371 
2372  position += currObject.Length;
2373  objectNum++;
2374  }
2375  else if (gradient is RadialGradientBrush radial)
2376  {
2377  objectPositions.Add(position);
2378 
2379  currObject = objectNum.ToString() + " 0 obj\n<< /Type /Pattern /PatternType 2 ";
2380 
2381  if (includeMatrix)
2382  {
2383  currObject += "/Matrix [ " +
2384 
2385  matrix[0, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2386  matrix[1, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2387  matrix[0, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2388  matrix[1, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2389  matrix[0, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " +
2390  matrix[1, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] ";
2391  }
2392 
2393  currObject += "/Shading << /ShadingType 3 /ColorSpace /DeviceRGB /Coords [ " + radial.FocalPoint.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + radial.FocalPoint.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 " + radial.Centre.X.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + radial.Centre.Y.ToString(System.Globalization.CultureInfo.InvariantCulture) + " " + radial.Radius.ToString(System.Globalization.CultureInfo.InvariantCulture) + " ] ";
2394 
2395  currObject += "/Domain [ 0 1 ] /Extend [ true true ] /Function " + functionObject.ToString(System.Globalization.CultureInfo.InvariantCulture) + " 0 R >> >>\nendobj\n";
2396  sw.Write(currObject);
2397  gradientIndices.Add(objectNum);
2398 
2399  position += currObject.Length;
2400  objectNum++;
2401  }
2402  }
2403 
2404  private static (double, double, double, double) MeasureFigure(IFigure figure, ref double[,] transformationMatrix, Stack<double[,]> savedStates)
2405  {
2406  if (figure is PathFigure)
2407  {
2408  PathFigure fig = figure as PathFigure;
2409 
2410  double minX = double.MaxValue;
2411  double maxX = double.MinValue;
2412  double minY = double.MaxValue;
2413  double maxY = double.MinValue;
2414 
2415  for (int i = 0; i < fig.Segments.Length; i++)
2416  {
2417  switch (fig.Segments[i].Type)
2418  {
2419  case SegmentType.Move:
2420  {
2421  Point pt = transformationMatrix.Multiply(fig.Segments[i].Point);
2422  minX = Math.Min(minX, pt.X);
2423  minY = Math.Min(minY, pt.Y);
2424  maxX = Math.Max(maxX, pt.X);
2425  maxY = Math.Max(maxY, pt.Y);
2426  }
2427  break;
2428  case SegmentType.Line:
2429  {
2430  Point pt = transformationMatrix.Multiply(fig.Segments[i].Point);
2431  minX = Math.Min(minX, pt.X);
2432  minY = Math.Min(minY, pt.Y);
2433  maxX = Math.Max(maxX, pt.X);
2434  maxY = Math.Max(maxY, pt.Y);
2435  }
2436  break;
2437  case SegmentType.CubicBezier:
2438  for (int j = 0; j < fig.Segments[i].Points.Length; j++)
2439  {
2440  Point pt = transformationMatrix.Multiply(fig.Segments[i].Points[j]);
2441  minX = Math.Min(minX, pt.X);
2442  minY = Math.Min(minY, pt.Y);
2443  maxX = Math.Max(maxX, pt.X);
2444  maxY = Math.Max(maxY, pt.Y);
2445  }
2446  break;
2447  case SegmentType.Close:
2448  break;
2449  }
2450  }
2451 
2452  return (minX, minY, maxX, maxY);
2453  }
2454  else if (figure is TextFigure)
2455  {
2456  TextFigure fig = figure as TextFigure;
2457 
2458  double realX = fig.Position.X;
2459 
2460  if (fig.Font.FontFamily.TrueTypeFile != null)
2461  {
2462  realX = fig.Position.X - fig.Font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(fig.Text[0]).LeftSideBearing * fig.Font.FontSize / 1000;
2463  }
2464 
2465  double yMax = 0;
2466  double yMin = 0;
2467 
2468  if (fig.Font.FontFamily.TrueTypeFile != null)
2469  {
2470  for (int i = 0; i < fig.Text.Length; i++)
2471  {
2472  TrueTypeFile.VerticalMetrics vMet = fig.Font.FontFamily.TrueTypeFile.Get1000EmGlyphVerticalMetrics(fig.Text[i]);
2473  yMin = Math.Min(yMin, vMet.YMin * fig.Font.FontSize / 1000);
2474  yMax = Math.Max(yMax, vMet.YMax * fig.Font.FontSize / 1000);
2475  }
2476  }
2477 
2478  double realY = fig.Position.Y;
2479 
2480  if (fig.TextBaseline == TextBaselines.Bottom)
2481  {
2482  realY -= yMax;
2483  }
2484  else if (fig.TextBaseline == TextBaselines.Top)
2485  {
2486  realY -= yMin;
2487  }
2488  else if (fig.TextBaseline == TextBaselines.Middle)
2489  {
2490  realY -= (yMax + yMin) * 0.5;
2491  }
2492  else if (fig.TextBaseline == TextBaselines.Baseline)
2493  {
2494  realY -= yMax + yMin;
2495  }
2496 
2497  Font.DetailedFontMetrics metrics = fig.Font.MeasureTextAdvanced(fig.Text);
2498 
2499  Point corner1 = new Point(fig.Position.X - metrics.LeftSideBearing, realY + metrics.Top);
2500  Point corner2 = new Point(fig.Position.X + metrics.Width, realY + metrics.Top);
2501  Point corner3 = new Point(fig.Position.X + metrics.Width, realY + metrics.Bottom);
2502  Point corner4 = new Point(fig.Position.X - metrics.LeftSideBearing, realY + metrics.Bottom);
2503 
2504  corner1 = transformationMatrix.Multiply(corner1);
2505  corner2 = transformationMatrix.Multiply(corner2);
2506  corner3 = transformationMatrix.Multiply(corner3);
2507  corner4 = transformationMatrix.Multiply(corner4);
2508 
2509  return (Math.Min(corner1.X, Math.Min(corner2.X, Math.Min(corner3.X, corner4.X))), Math.Min(corner1.Y, Math.Min(corner2.Y, Math.Min(corner3.Y, corner4.Y))), Math.Max(corner1.X, Math.Max(corner2.X, Math.Max(corner3.X, corner4.X))), Math.Max(corner1.Y, Math.Max(corner2.Y, Math.Max(corner3.Y, corner4.Y))));
2510  }
2511  else if (figure is TransformFigure transf)
2512  {
2513  if (transf.TransformType == TransformFigure.TransformTypes.Transform)
2514  {
2515  transformationMatrix = transformationMatrix.Multiply(transf.TransformationMatrix);
2516  }
2517  else if (transf.TransformType == TransformFigure.TransformTypes.Save)
2518  {
2519  savedStates.Push(transformationMatrix);
2520  }
2521  else if (transf.TransformType == TransformFigure.TransformTypes.Restore)
2522  {
2523  transformationMatrix = savedStates.Pop();
2524  }
2525 
2526  return (0, 0, 0, 0);
2527  }
2528  else if (figure is RasterImageFigure)
2529  {
2530  Point corner1 = new Point(0, 0);
2531  Point corner2 = new Point(0, 1);
2532  Point corner3 = new Point(1, 1);
2533  Point corner4 = new Point(1, 0);
2534 
2535  corner1 = transformationMatrix.Multiply(corner1);
2536  corner2 = transformationMatrix.Multiply(corner2);
2537  corner3 = transformationMatrix.Multiply(corner3);
2538  corner4 = transformationMatrix.Multiply(corner4);
2539 
2540  return (Math.Min(corner1.X, Math.Min(corner2.X, Math.Min(corner3.X, corner4.X))), Math.Min(corner1.Y, Math.Min(corner2.Y, Math.Min(corner3.Y, corner4.Y))), Math.Max(corner1.X, Math.Max(corner2.X, Math.Max(corner3.X, corner4.X))), Math.Max(corner1.Y, Math.Max(corner2.Y, Math.Max(corner3.Y, corner4.Y))));
2541  }
2542  else
2543  {
2544  return (0, 0, 0, 0);
2545  }
2546  }
2547 
2548  private static double[,] Multiply(this double[,] matrix, double[,] matrix2)
2549  {
2550  double[,] tbr = new double[3, 3];
2551 
2552  tbr[0, 0] = matrix[0, 0] * matrix2[0, 0] - matrix[0, 1] * matrix2[1, 0] + matrix[0, 2] * matrix2[2, 0];
2553  tbr[0, 1] = -matrix[0, 0] * matrix2[0, 1] + matrix[0, 1] * matrix2[1, 1] + matrix[0, 2] * matrix2[2, 1];
2554  tbr[0, 2] = matrix[0, 0] * matrix2[0, 2] + matrix[0, 1] * matrix2[1, 2] + matrix[0, 2] * matrix2[2, 2];
2555 
2556  tbr[1, 0] = matrix[1, 0] * matrix2[0, 0] - matrix[1, 1] * matrix2[1, 0] + matrix[1, 2] * matrix2[2, 0];
2557  tbr[1, 1] = -matrix[1, 0] * matrix2[0, 1] + matrix[1, 1] * matrix2[1, 1] + matrix[1, 2] * matrix2[2, 1];
2558  tbr[1, 2] = matrix[1, 0] * matrix2[0, 2] + matrix[1, 1] * matrix2[1, 2] + matrix[1, 2] * matrix2[2, 2];
2559 
2560  tbr[2, 0] = matrix[2, 0] * matrix2[0, 0] - matrix[2, 1] * matrix2[1, 0] + matrix[2, 2] * matrix2[2, 0];
2561  tbr[2, 1] = -matrix[2, 0] * matrix2[0, 1] + matrix[2, 1] * matrix2[1, 1] + matrix[2, 2] * matrix2[2, 1];
2562  tbr[2, 2] = matrix[2, 0] * matrix2[0, 2] + matrix[2, 1] * matrix2[1, 2] + matrix[2, 2] * matrix2[2, 2];
2563 
2564  return tbr;
2565  }
2566 
2567  private static Point Multiply(this double[,] matrix, Point point)
2568  {
2569  double[] transPt = new double[3];
2570 
2571  transPt[0] = matrix[0, 0] * point.X + matrix[0, 1] * point.Y + matrix[0, 2];
2572  transPt[1] = matrix[1, 0] * point.X + matrix[1, 1] * point.Y + matrix[1, 2];
2573  transPt[2] = matrix[2, 0] * point.X + matrix[2, 1] * point.Y + matrix[2, 2];
2574 
2575  return new Point(transPt[0] / transPt[2], transPt[1] / transPt[2]);
2576  }
2577 
2578  private static string FigureAsPDFString(IFigure figure, Dictionary<string, string> nonSymbolFontIds, Dictionary<string, string> symbolFontIds, Dictionary<string, Dictionary<char, int>> symbolGlyphIndices, double[] alphas, Dictionary<string, int> imageObjectNums, double[,] transformationMatrix, List<(GradientBrush, double[,], IFigure)> gradients)
2579  {
2580 
2581  StringBuilder sb = new StringBuilder();
2582 
2583  if (figure.Fill != null)
2584  {
2585  if (figure.Fill is SolidColourBrush solid)
2586  {
2587  sb.Append(solid.R.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + solid.G.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + solid.B.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " rg\n");
2588  sb.Append("/a" + Array.IndexOf(alphas, solid.A).ToString() + " gs\n");
2589  }
2590  else if (figure.Fill is GradientBrush gradient)
2591  {
2592  int brushIndex = gradients.Count;
2593  double[,] clonedMatrix = new double[3, 3] { { transformationMatrix[0, 0], transformationMatrix[0, 1], transformationMatrix[0, 2] }, { transformationMatrix[1, 0], transformationMatrix[1, 1], transformationMatrix[1, 2] }, { transformationMatrix[2, 0], transformationMatrix[2, 1], transformationMatrix[2, 2] } };
2594 
2595  gradients.Add((gradient, clonedMatrix, figure));
2596 
2597  if (!PDFContext.IsCompatible(gradient))
2598  {
2599  sb.Append("/ma" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) + " gs ");
2600  }
2601 
2602  sb.Append("/Pattern cs /p" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) + " scn /a" + Array.IndexOf(alphas, 1.0).ToString() + " gs\n");
2603  }
2604  }
2605 
2606  if (figure.Stroke != null)
2607  {
2608  if (figure.Stroke is SolidColourBrush solid)
2609  {
2610  sb.Append(solid.R.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + solid.G.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + solid.B.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " RG\n");
2611  sb.Append("/a" + Array.IndexOf(alphas, solid.A).ToString() + " gs\n");
2612  }
2613  else if (figure.Stroke is GradientBrush gradient)
2614  {
2615  int brushIndex = gradients.Count;
2616  double[,] clonedMatrix = new double[3, 3] { { transformationMatrix[0, 0], transformationMatrix[0, 1], transformationMatrix[0, 2] }, { transformationMatrix[1, 0], transformationMatrix[1, 1], transformationMatrix[1, 2] }, { transformationMatrix[2, 0], transformationMatrix[2, 1], transformationMatrix[2, 2] } };
2617 
2618  gradients.Add((gradient, clonedMatrix, figure));
2619 
2620  if (!PDFContext.IsCompatible(gradient))
2621  {
2622  sb.Append("/ma" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) + " gs ");
2623  }
2624 
2625  sb.Append("/Pattern CS /p" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) + " SCN /a" + Array.IndexOf(alphas, 1.0).ToString() + " gs\n");
2626  }
2627 
2628  sb.Append(figure.LineWidth.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " w\n");
2629  sb.Append(((int)figure.LineCap).ToString() + " J\n");
2630  sb.Append(((int)figure.LineJoin).ToString() + " j\n");
2631  if (figure.LineDash.UnitsOff != 0 || figure.LineDash.UnitsOn != 0)
2632  {
2633  sb.Append("[ " + figure.LineDash.UnitsOn.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + figure.LineDash.UnitsOff.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ] " + figure.LineDash.Phase.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " d\n");
2634  }
2635  else
2636  {
2637  sb.Append("[] 0 d\n");
2638  }
2639  }
2640 
2641  if (figure is PathFigure)
2642  {
2643  PathFigure fig = figure as PathFigure;
2644 
2645  for (int i = 0; i < fig.Segments.Length; i++)
2646  {
2647  switch (fig.Segments[i].Type)
2648  {
2649  case SegmentType.Move:
2650  {
2651  sb.Append(fig.Segments[i].Point.X.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + fig.Segments[i].Point.Y.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " m ");
2652  }
2653  break;
2654  case SegmentType.Line:
2655  {
2656  sb.Append(fig.Segments[i].Point.X.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + fig.Segments[i].Point.Y.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " l ");
2657  }
2658  break;
2659  case SegmentType.CubicBezier:
2660  for (int j = 0; j < fig.Segments[i].Points.Length; j++)
2661  {
2662  sb.Append(fig.Segments[i].Points[j].X.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + fig.Segments[i].Points[j].Y.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ");
2663  }
2664  sb.Append("c ");
2665  break;
2666  case SegmentType.Close:
2667  sb.Append("h ");
2668  break;
2669  }
2670  }
2671 
2672  if (fig.IsClipping)
2673  {
2674  sb.Append("W n\n");
2675  }
2676  else
2677  {
2678  if (fig.Fill != null)
2679  {
2680  sb.Append("f*\n");
2681  }
2682 
2683  if (fig.Stroke != null)
2684  {
2685  sb.Append("S\n");
2686  }
2687  }
2688  }
2689  else if (figure is TextFigure)
2690  {
2691  TextFigure fig = figure as TextFigure;
2692 
2693  List<(string txt, bool isSymbolic)> segments = new List<(string txt, bool isSymbolic)>();
2694 
2695  StringBuilder currSeg = new StringBuilder();
2696  bool currSymbolic = false;
2697 
2698  for (int i = 0; i < fig.Text.Length; i++)
2699  {
2700  if (CP1252Chars.Contains(fig.Text[i]))
2701  {
2702  if (!currSymbolic)
2703  {
2704  currSeg.Append(fig.Text[i]);
2705  }
2706  else
2707  {
2708  if (currSeg.Length > 0)
2709  {
2710  segments.Add((currSeg.ToString(), currSymbolic));
2711  }
2712 
2713  currSeg = new StringBuilder();
2714  currSymbolic = false;
2715  currSeg.Append(fig.Text[i]);
2716  }
2717  }
2718  else
2719  {
2720  if (currSymbolic)
2721  {
2722  currSeg.Append(fig.Text[i]);
2723  }
2724  else
2725  {
2726  if (currSeg.Length > 0)
2727  {
2728  segments.Add((currSeg.ToString(), currSymbolic));
2729  }
2730 
2731  currSeg = new StringBuilder();
2732  currSymbolic = true;
2733  currSeg.Append(fig.Text[i]);
2734  }
2735  }
2736  }
2737 
2738  if (currSeg.Length > 0)
2739  {
2740  segments.Add((currSeg.ToString(), currSymbolic));
2741  }
2742 
2743 
2744 
2745  double realX = fig.Position.X;
2746 
2747  if (fig.Font.FontFamily.TrueTypeFile != null)
2748  {
2749  realX = fig.Position.X - fig.Font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(fig.Text[0]).LeftSideBearing * fig.Font.FontSize / 1000;
2750  }
2751 
2752  double yMax = 0;
2753  double yMin = 0;
2754 
2755  if (fig.Font.FontFamily.TrueTypeFile != null)
2756  {
2757  for (int i = 0; i < fig.Text.Length; i++)
2758  {
2759  TrueTypeFile.VerticalMetrics vMet = fig.Font.FontFamily.TrueTypeFile.Get1000EmGlyphVerticalMetrics(fig.Text[i]);
2760  yMin = Math.Min(yMin, vMet.YMin * fig.Font.FontSize / 1000);
2761  yMax = Math.Max(yMax, vMet.YMax * fig.Font.FontSize / 1000);
2762  }
2763  }
2764 
2765  double realY = fig.Position.Y;
2766 
2767  if (fig.TextBaseline == TextBaselines.Bottom)
2768  {
2769  realY -= yMax;
2770  }
2771  else if (fig.TextBaseline == TextBaselines.Top)
2772  {
2773  realY -= yMin;
2774  }
2775  else if (fig.TextBaseline == TextBaselines.Middle)
2776  {
2777  realY -= (yMax + yMin) * 0.5;
2778  }
2779  else if (fig.TextBaseline == TextBaselines.Baseline)
2780  {
2781  realY -= yMax + yMin;
2782  }
2783 
2784  double middleY = realY + (yMax + yMin) * 0.5;
2785 
2786 
2787 
2788  sb.Append("q\n1 0 0 1 0 " + (middleY).ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " cm\n");
2789  sb.Append("1 0 0 -1 0 0 cm\n");
2790  sb.Append("1 0 0 1 0 " + (-middleY).ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " cm\n");
2791 
2792  sb.Append("BT\n");
2793 
2794  if (figure.Stroke != null && figure.Fill != null)
2795  {
2796  sb.Append("2 Tr\n");
2797  }
2798  else if (figure.Stroke != null)
2799  {
2800  sb.Append("1 Tr\n");
2801  }
2802  else if (figure.Fill != null)
2803  {
2804  sb.Append("0 Tr\n");
2805  }
2806 
2807  sb.Append(realX.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " " + realY.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " Td\n");
2808 
2809  for (int i = 0; i < segments.Count; i++)
2810  {
2811  if (!segments[i].isSymbolic)
2812  {
2813  sb.Append("/" + nonSymbolFontIds[fig.Font.FontFamily.FamilyName] + " " + fig.Font.FontSize.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " Tf\n");
2814  sb.Append(GetKernedString(segments[i].txt, fig.Font) + " TJ\n");
2815  }
2816  else
2817  {
2818  sb.Append("/" + symbolFontIds[fig.Font.FontFamily.FamilyName] + " " + fig.Font.FontSize.ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " Tf\n");
2819  sb.Append("<" + EscapeSymbolStringForPDF(segments[i].txt, symbolGlyphIndices[fig.Font.FontFamily.FamilyName]) + "> Tj\n");
2820  }
2821  }
2822 
2823  sb.Append("ET\nQ\n");
2824  }
2825  else if (figure is TransformFigure transf)
2826  {
2827  if (transf.TransformType == TransformFigure.TransformTypes.Transform)
2828  {
2829  sb.Append(transf.TransformationMatrix[0, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ");
2830  sb.Append(transf.TransformationMatrix[0, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ");
2831  sb.Append(transf.TransformationMatrix[1, 0].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ");
2832  sb.Append(transf.TransformationMatrix[1, 1].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ");
2833  sb.Append(transf.TransformationMatrix[0, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " ");
2834  sb.Append(transf.TransformationMatrix[1, 2].ToString("0.################", System.Globalization.CultureInfo.InvariantCulture) + " cm\n");
2835  }
2836  else if (transf.TransformType == TransformFigure.TransformTypes.Save)
2837  {
2838  sb.Append("q\n");
2839  }
2840  else if (transf.TransformType == TransformFigure.TransformTypes.Restore)
2841  {
2842  sb.Append("Q\n");
2843  }
2844  }
2845  else if (figure is RasterImageFigure fig)
2846  {
2847  sb.Append("/a" + Array.IndexOf(alphas, 1).ToString() + " gs\n");
2848 
2849  int imageNum = imageObjectNums[fig.Image.Id];
2850 
2851  sb.Append("/Img" + imageNum.ToString() + " Do\n");
2852  }
2853 
2854  return sb.ToString();
2855  }
2856 
2857  internal static MemoryStream ZLibCompress(Stream contentStream)
2858  {
2859  MemoryStream compressedStream = new MemoryStream();
2860  compressedStream.Write(new byte[] { 0x78, 0x01 }, 0, 2);
2861 
2862  using (DeflateStream deflate = new DeflateStream(compressedStream, CompressionLevel.Optimal, true))
2863  {
2864  contentStream.CopyTo(deflate);
2865  }
2866  contentStream.Seek(0, SeekOrigin.Begin);
2867 
2868  uint checksum = Adler32(contentStream);
2869 
2870  compressedStream.Write(new byte[] { (byte)((checksum >> 24) & 255), (byte)((checksum >> 16) & 255), (byte)((checksum >> 8) & 255), (byte)(checksum & 255) }, 0, 4);
2871 
2872  compressedStream.Seek(0, SeekOrigin.Begin);
2873 
2874  return compressedStream;
2875  }
2876 
2877  internal static uint Adler32(Stream contentStream)
2878  {
2879  uint s1 = 1;
2880  uint s2 = 0;
2881 
2882  int readByte;
2883 
2884  while ((readByte = contentStream.ReadByte()) >= 0)
2885  {
2886  s1 = (s1 + (byte)readByte) % 65521U;
2887  s2 = (s2 + s1) % 65521U;
2888  }
2889 
2890  return (s2 << 16) + s1;
2891  }
2892  }
2893 }
VectSharp.Rectangle
Represents a rectangle.
Definition: Point.cs:173
VectSharp.TrueTypeFile
Represents a font file in TrueType format. Reference: http://stevehanov.ca/blog/?id=143,...
Definition: TrueType.cs:31
VectSharp.PDF.PDFContextInterpreter.FilterOption.FilterOperations
FilterOperations
Defines whether image filters should be rasterised or not.
Definition: PDFContext.cs:1145
VectSharp.Filters.IFilter.TopLeftMargin
Point TopLeftMargin
Determines how much the area of the filter's subject should be expanded on the top-left to accommodat...
Definition: Filters.cs:30
VectSharp.Graphics.FillPath
void FillPath(GraphicsPath path, Brush fillColour, string tag=null)
Fill a GraphicsPath.
Definition: Graphics.cs:276
VectSharp.Graphics.DrawGraphics
void DrawGraphics(Point origin, Graphics graphics)
Draws a Graphics object on the current Graphics object.
Definition: Graphics.cs:802
VectSharp.Graphics.StrokePath
void StrokePath(GraphicsPath path, Brush strokeColour, double lineWidth=1, LineCaps lineCap=LineCaps.Butt, LineJoins lineJoin=LineJoins.Miter, LineDash? lineDash=null, string tag=null)
Stroke a GraphicsPath.
Definition: Graphics.cs:292
VectSharp.PDF.PDFContextInterpreter.FilterOption.FilterOption
FilterOption(FilterOperations operation, double rasterisationResolution, bool rasterisationResolutionRelative)
Create a new FilterOption object.
Definition: PDFContext.cs:1188
VectSharp.TrueTypeFile.Get1000EmKerning
PairKerning Get1000EmKerning(char glyph1, char glyph2)
Gets the kerning between two glyphs.
Definition: TrueType.cs:2490
VectSharp.Colour
Represents an RGB colour.
Definition: Colour.cs:26
VectSharp.TrueTypeFile.Get1000EmYMax
double Get1000EmYMax()
Computes the maximum height over the baseline of the font, in thousandths of em unit.
Definition: TrueType.cs:2150
VectSharp.RasterImage
Represents a raster image, created from raw pixel data. Consider using the derived classes included i...
Definition: RasterImage.cs:99
VectSharp.GraphicsPath
Represents a graphics path that can be filled or stroked.
Definition: GraphicsPath.cs:29
VectSharp
Definition: Brush.cs:26
VectSharp.Filters.IFilter
Represents a filter. Do not implement this interface directly; instead, implement ILocationInvariantF...
Definition: Filters.cs:26
VectSharp.TrueTypeFile.GetGlyphIndex
int GetGlyphIndex(char glyph)
Determines the index of the glyph corresponding to a certain character.
Definition: TrueType.cs:2010
VectSharp.Document
Represents a collection of pages.
Definition: Document.cs:28
VectSharp.Font.MeasureTextAdvanced
DetailedFontMetrics MeasureTextAdvanced(string text)
Measure all the metrics of a text string when typeset with this font.
Definition: Font.cs:358
VectSharp.RasterImage.Width
int Width
The width in pixels of the image.
Definition: RasterImage.cs:123
VectSharp.Font.DetailedFontMetrics.Top
double Top
Height of the tallest glyph in the string over the baseline. Always >= 0.
Definition: Font.cs:126
VectSharp.Brush
Represents a brush used to fill or stroke graphics elements. This could be a solid colour,...
Definition: Brush.cs:31
VectSharp.RadialGradientBrush
Represents a brush painting with a radial gradient.
Definition: Brush.cs:368
VectSharp.Colour.A
double A
Alpha component of the colour. Range: [0, 1].
Definition: Colour.cs:45
VectSharp.Size.Height
double Height
Height of the object.
Definition: Point.cs:155
VectSharp.SegmentType.Close
@ Close
The segment represents the closing segment of a figure.
VectSharp.TrueTypeFile.IsFixedPitch
bool IsFixedPitch()
Determines whether the typeface is fixed-pitch (aka monospaces) or not.
Definition: TrueType.cs:1976
VectSharp.LineCaps
LineCaps
Represents line caps.
Definition: Enums.cs:71
VectSharp.TrueTypeFile.Get1000EmXMin
double Get1000EmXMin()
Computes the maximum distance to the left of the glyph origin of the font, in thousandths of em unit.
Definition: TrueType.cs:2177
VectSharp.Colours.White
static Colour White
White #FFFFFF
Definition: StandardColours.cs:775
VectSharp.Graphics.FillRectangle
void FillRectangle(Point topLeft, Size size, Brush fillColour, string tag=null)
Fill a rectangle.
Definition: Graphics.cs:401
VectSharp.Font
Represents a typeface with a specific size.
Definition: Font.cs:29
VectSharp.PDF.PDFContextInterpreter.SaveAsPDF
static void SaveAsPDF(this Document document, string fileName, TextOptions textOption=TextOptions.SubsetFonts, bool compressStreams=true, Dictionary< string, string > linkDestinations=null, FilterOption filterOption=default)
Save the document to a PDF file.
Definition: PDFContext.cs:1112
VectSharp.TextBaselines
TextBaselines
Represent text baselines.
Definition: Enums.cs:24
VectSharp.RasterImage.Interpolate
bool Interpolate
Determines whether the image should be interpolated when it is resized.
Definition: RasterImage.cs:133
VectSharp.TrueTypeFile.Get1000EmXMax
double Get1000EmXMax()
Computes the maximum distance to the right of the glyph origin of the font, in thousandths of em unit...
Definition: TrueType.cs:2168
VectSharp.Graphics
Represents an abstract drawing surface.
Definition: Graphics.cs:262
VectSharp.Rectangle.Size
Size Size
The size of the rectangle.
Definition: Point.cs:187
VectSharp.FontFamily.ResolveFontFamily
static FontFamily ResolveFontFamily(string fontFamily)
Create a new font family from the specified family name or true type file. If the family name or the ...
VectSharp.GraphicsPath.AddText
GraphicsPath AddText(double originX, double originY, string text, Font font, TextBaselines textBaseline=TextBaselines.Top)
Add the contour of a text string to the current path.
Definition: GraphicsPath.cs:312
VectSharp.LineJoins
LineJoins
Represents line joining options.
Definition: Enums.cs:92
VectSharp.RasterImage.HasAlpha
bool HasAlpha
Determines whether the image has an alpha channel.
Definition: RasterImage.cs:118
VectSharp.TrueTypeFile.IsSerif
bool IsSerif()
Determines whether the typeface is serifed or not.
Definition: TrueType.cs:1987
VectSharp.FontFamily
Represents a typeface.
Definition: Font.cs:421
VectSharp.Rectangle.NaN
static readonly Rectangle NaN
A rectangle whose dimensions are all double.NaN.
Definition: Point.cs:177
VectSharp.GradientStop
Represents a colour stop in a gradient.
Definition: Brush.cs:110
VectSharp.GradientBrush
Represents a brush painting with a gradient.
Definition: Brush.cs:232
VectSharp.PDF.PDFContextInterpreter.TextOptions
TextOptions
Defines whether the used fonts should be included in the file.
Definition: PDFContext.cs:1124
VectSharp.PDF
Definition: PDFContext.cs:29
VectSharp.TrueTypeFile.Get1000EmYMin
double Get1000EmYMin()
Computes the maximum depth below the baseline of the font, in thousandths of em unit.
Definition: TrueType.cs:2159
VectSharp.PDF.PDFContextInterpreter.FilterOption.Default
static FilterOption Default
The default options for image filter rasterisation.
Definition: PDFContext.cs:1180
VectSharp.SegmentType.Line
@ Line
The segment represents a straight line from the current point to a new point.
VectSharp.TrueTypeFile.Get1000EmAscent
double Get1000EmAscent()
Computes the font ascent, in thousandths of em unit.
Definition: TrueType.cs:2131
VectSharp.Font.DetailedFontMetrics
Represents detailed information about the metrics of a text string when drawn with a certain font.
Definition: Font.cs:102
VectSharp.SegmentType
SegmentType
Types of Segment.
Definition: Enums.cs:152
VectSharp.Point.X
double X
Horizontal (x) coordinate, measured to the right of the origin.
Definition: Point.cs:32
VectSharp.RasterImage.Height
int Height
The height in pixels of the image.
Definition: RasterImage.cs:128
VectSharp.PDF.PDFContextInterpreter
Contains methods to render a Document as a PDF document.
Definition: PDFContext.cs:829
VectSharp.TrueTypeFile.Get1000EmGlyphWidth
double Get1000EmGlyphWidth(char glyph)
Computes the advance width of a glyph, in thousandths of em unit.
Definition: TrueType.cs:2082
VectSharp.SegmentType.CubicBezier
@ CubicBezier
The segment represents a cubic bezier curve from the current point to a new point.
VectSharp.Document.Pages
List< Page > Pages
The pages in the document.
Definition: Document.cs:32
VectSharp.TrueTypeFile.IsScript
bool IsScript()
Determines whether the typeface is a script typeface or not.
Definition: TrueType.cs:1998
VectSharp.TrueTypeFile.FontStream
Stream FontStream
A stream pointing to the TrueType file source (either on disk or in memory). Never dispose this strea...
Definition: TrueType.cs:47
VectSharp.FontFamily.StandardFontFamilies
StandardFontFamilies
The 14 standard font families.
Definition: Font.cs:500
VectSharp.Font.FontFamily
FontFamily FontFamily
Font typeface.
Definition: Font.cs:158
VectSharp.LinearGradientBrush
Represents a brush painting with a linear gradient.
Definition: Brush.cs:245
VectSharp.PDF.PDFContextInterpreter.FilterOption
Determines how and whether image filters are rasterised.
Definition: PDFContext.cs:1140
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
VectSharp.Size
Represents the size of an object.
Definition: Point.cs:146
VectSharp.TrueTypeFile.GetFontName
string GetFontName()
Obtains the PostScript font name from the TrueType file.
Definition: TrueType.cs:1901
VectSharp.LineDash
Represents instructions on how to paint a dashed line.
Definition: Enums.cs:113
VectSharp.Rectangle.Location
Point Location
The top-left corner of the rectangle.
Definition: Point.cs:182
VectSharp.Size.Width
double Width
Width of the object.
Definition: Point.cs:150
VectSharp.Filters.IFilter.BottomRightMargin
Point BottomRightMargin
Determines how much the area of the filter's subject should be expanded on the bottom-right to accomm...
Definition: Filters.cs:35
VectSharp.TrueTypeFile.PairKerning
Contains information describing how the position of two glyphs in a kerning pair should be altered.
Definition: TrueType.cs:4252
VectSharp.Filters.IFilterWithLocation
Represents a filter whose results depend on the position of the subject image on the graphics surface...
Definition: Filters.cs:56
VectSharp.Filters.ILocationInvariantFilter
Represents a filter that can be applied to an image regardless of its location on the graphics surfac...
Definition: Filters.cs:42
VectSharp.SolidColourBrush
Represents a brush painting with a single solid colour.
Definition: Brush.cs:55
VectSharp.IGraphicsContext
This interface should be implemented by classes intended to provide graphics output capability to a G...
Definition: Graphics.cs:36
VectSharp.RasterImage.ImageDataAddress
IntPtr ImageDataAddress
The memory address of the image pixel data.
Definition: RasterImage.cs:103
VectSharp.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.Graphics.GetBounds
Rectangle GetBounds()
Computes the rectangular bounds of the region affected by the drawing operations performed on the Gra...
Definition: Graphics.cs:1313
VectSharp.TrueTypeFile.IsBold
bool IsBold()
Determines whether the typeface is Bold or not.
Definition: TrueType.cs:1965
VectSharp.Colours
Standard colours.
Definition: StandardColours.cs:183
VectSharp.TrueTypeFile.Get1000EmDescent
double Get1000EmDescent()
Computes the font descent, in thousandths of em unit.
Definition: TrueType.cs:2141
VectSharp.PDF.PDFContextInterpreter.SaveAsPDF
static void SaveAsPDF(this Document document, Stream stream, TextOptions textOption=TextOptions.SubsetFonts, bool compressStreams=true, Dictionary< string, string > linkDestinations=null, FilterOption filterOption=default)
Save the document to a PDF stream.
Definition: PDFContext.cs:1207
VectSharp.Colour.FromRgb
static Colour FromRgb(double r, double g, double b)
Create a new colour from RGB (red, green and blue) values.
Definition: Colour.cs:62
VectSharp.TrueTypeFile.GetFontFamilyName
string GetFontFamilyName()
Obtains the font family name from the TrueType file.
Definition: TrueType.cs:1846
VectSharp.SegmentType.Move
@ Move
The segment represents a move from the current point to a new point.
VectSharp.Point.Y
double Y
Vertical (y) coordinate, measured to the bottom of the origin.
Definition: Point.cs:37
VectSharp.FontFamily.TrueTypeFile
TrueTypeFile TrueTypeFile
Parsed TrueType font file for this font family. See also: VectSharp.TrueTypeFile.
Definition: Font.cs:586
VectSharp.GradientStop.Colour
Colour Colour
The Colour at the gradient stop.
Definition: Brush.cs:114
VectSharp.GraphicsPath.Segments
List< Segment > Segments
The segments that make up the path.
Definition: GraphicsPath.cs:33
VectSharp.Colour.FromRgba
static Colour FromRgba(double r, double g, double b, double a)
Create a new colour from RGBA (red, green, blue and alpha) values.
Definition: Colour.cs:99