19 using System.Collections;
20 using System.Collections.Generic;
22 using System.IO.Compression;
24 using System.Runtime.InteropServices;
30 internal enum SegmentType
35 internal abstract class Segment
37 public abstract SegmentType Type {
get; }
38 public Point[] Points {
get;
protected set; }
44 return Points[Points.Length - 1];
48 public abstract Segment Clone();
51 internal class MoveSegment : Segment
53 public override SegmentType Type => SegmentType.Move;
55 public MoveSegment(
Point p)
57 this.Points =
new Point[] { p };
60 public MoveSegment(
double x,
double y)
65 public override Segment Clone()
67 return new MoveSegment(this.
Point);
71 internal class LineSegment : Segment
73 public override SegmentType Type => SegmentType.Line;
75 public LineSegment(
Point p)
77 this.Points =
new Point[] { p };
80 public LineSegment(
double x,
double y)
85 public override Segment Clone()
87 return new LineSegment(this.
Point);
91 internal class CloseSegment : Segment
93 public override SegmentType Type => SegmentType.Close;
95 public CloseSegment() { }
97 public override Segment Clone()
99 return new CloseSegment();
103 internal class CubicBezierSegment : Segment
105 public override SegmentType Type => SegmentType.CubicBezier;
106 public CubicBezierSegment(
double x1,
double y1,
double x2,
double y2,
double x3,
double y3)
113 Points =
new Point[] { p1, p2, p3 };
116 public override Segment Clone()
118 return new CubicBezierSegment(Points[0], Points[1], Points[2]);
122 internal interface IFigure
125 Brush Stroke {
get; }
126 double LineWidth {
get; }
130 bool IsClipping {
get; }
135 internal class TransformFigure : IFigure
137 public enum TransformTypes
139 Transform, Save, Restore
142 public TransformTypes TransformType {
get; }
144 public Brush Fill {
get; }
145 public Brush Stroke {
get; }
147 public bool IsClipping {
get; }
148 public double LineWidth {
get; }
150 public double[,] TransformationMatrix {
get; }
157 public string Tag {
get; }
159 public TransformFigure(TransformTypes type,
double[,] transformationMatrix,
string tag)
161 this.TransformType = type;
162 this.TransformationMatrix = transformationMatrix;
172 internal class RasterImageFigure : IFigure
175 public Brush Fill {
get; }
176 public Brush Stroke {
get; }
178 public bool IsClipping {
get; }
179 public double LineWidth {
get; }
181 public double[,] TransformationMatrix {
get; }
191 public string Tag {
get; }
193 public RasterImageFigure(
RasterImage image,
string tag)
205 internal class PathFigure : IFigure
207 public Brush Fill {
get; }
208 public Brush Stroke {
get; }
209 public bool IsClipping {
get; }
210 public double LineWidth {
get; }
217 public Segment[] Segments {
get; }
219 public string Tag {
get; }
225 List<Segment> segs =
new List<Segment>();
227 foreach (Segment s
in segments)
232 this.Segments = segs.ToArray();
236 LineWidth = lineWidth;
240 IsClipping = isClipping;
245 this.Bounds = bounds;
259 internal class TextFigure : IFigure
261 public Brush Fill {
get; }
262 public Brush Stroke {
get; }
263 public double LineWidth {
get; }
264 public bool IsClipping {
get; }
271 public string Text {
get; }
275 public Point Position {
get; }
279 public string Tag {
get; }
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)
286 TextBaseline = textBaseline;
290 LineWidth = lineWidth;
301 switch (this.TextBaseline)
304 return new Rectangle(this.Position,
new Size(metrics.Width, metrics.Height));
306 return new Rectangle(this.Position.X,
this.Position.Y - metrics.Height, metrics.Width, metrics.Height);
308 return new Rectangle(this.Position.X,
this.Position.Y - metrics.Height * 0.5, metrics.Width, metrics.Height);
310 return new Rectangle(this.Position.X,
this.Position.Y - metrics.Top, metrics.Width, metrics.Height);
312 throw new System.ArgumentOutOfRangeException(nameof(TextBaseline), this.TextBaseline,
"Invalid text baseline!");
317 internal class PDFFontDescriptor
319 private static readonly Random TagGenerator =
new Random();
320 private static readonly List<string> TagCache =
new List<string>();
322 public string FontName {
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;
333 public PDFFontDescriptor(
TrueTypeFile ttf,
bool isSubset,
bool isSymbolic)
346 bool italic = ttf.
IsBold();
350 bool smallCap =
false;
352 bool forceBold =
false;
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);
367 string randString =
"";
369 while (randString.Length == 0 || TagCache.Contains(randString))
372 for (
int i = 0; i < 6; i++)
374 randString += (char)TagGenerator.Next(65, 91);
378 this.FontName = randString +
"+" + this.FontName;
385 public string Tag {
get;
set; }
386 public double Width {
get; }
387 public double Height {
get; }
390 private List<Segment> _currentFigure;
392 internal List<IFigure> _figures;
394 private Brush _strokeStyle;
395 private Brush _fillStyle;
398 private readonly
bool _textToPaths;
405 this.Height = height;
407 _currentFigure =
new List<Segment>();
408 _figures =
new List<IFigure>();
418 _textToPaths = textToPaths;
424 this.Translate(0, height);
428 this.SetFillStyle(background);
433 this._filterOption = filterOption;
437 public void MoveTo(
double x,
double y)
439 _currentFigure.Add(
new MoveSegment(x, y));
442 public void LineTo(
double x,
double y)
444 _currentFigure.Add(
new LineSegment(x, y));
449 _currentFigure.Add(
new CloseSegment());
452 public void Rectangle(
double x0,
double y0,
double width,
double height)
455 LineTo(x0 + width, y0);
456 LineTo(x0 + width, y0 + height);
457 LineTo(x0, y0 + height);
460 public void SetStrokeStyle((
int r,
int g,
int b,
double a) style)
462 _strokeStyle =
Colour.
FromRgba(style.r, style.g, style.b, style.a);
465 public void SetStrokeStyle(
Brush style)
467 _strokeStyle = style;
470 public void SetFillStyle((
int r,
int g,
int b,
double a) style)
475 public void SetFillStyle(
Brush style)
481 public Brush FillStyle {
get {
return _fillStyle; } }
482 public Brush StrokeStyle {
get {
return _strokeStyle; } }
484 public double LineWidth {
get;
set; }
486 public LineCaps LineCap {
get;
set; }
489 internal static bool IsCompatible(
Brush brush)
520 return new LinearGradientBrush(linear.StartPoint, linear.EndPoint, from el in linear.GradientStops select
new GradientStop(el.Colour.WithAlpha(1.0), el.Offset));
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));
532 internal static Brush GetAlphaBrush(
Brush brush)
552 private static GraphicsPath GetGraphicsPath(IEnumerable<Segment> segments)
556 foreach (Segment seg
in segments)
560 case SegmentType.Close:
563 case SegmentType.Move:
564 tbr.MoveTo(seg.Point);
566 case SegmentType.Line:
567 tbr.LineTo(seg.Point);
569 case SegmentType.CubicBezier:
570 tbr.CubicBezierTo(seg.Points[0], seg.Points[1], seg.Points[2]);
578 private Graphics GetCurrentFigureMask(
Brush brush,
bool stroke,
bool blackBackground =
false)
590 gpr.
StrokePath(path, brush, LineWidth, LineCap, LineJoin, _lineDash);
610 if (IsCompatible(_fillStyle))
612 _figures.Add(
new PathFigure(_currentFigure,
VectSharp.
Rectangle.
NaN, _fillStyle,
null, 0,
LineCaps.Butt,
LineJoins.Bevel,
new LineDash(0, 0, 0),
false, this.Tag));
616 _figures.Add(
new PathFigure(_currentFigure, GetGraphicsPath(_currentFigure).GetBounds(), _fillStyle,
null, 0,
LineCaps.Butt,
LineJoins.Bevel,
new LineDash(0, 0, 0),
false, this.Tag));
619 _currentFigure =
new List<Segment>();
624 if (IsCompatible(_strokeStyle))
626 _figures.Add(
new PathFigure(_currentFigure,
VectSharp.
Rectangle.
NaN,
null, _strokeStyle, LineWidth, LineCap, LineJoin, _lineDash,
false,
this.Tag));
630 _figures.Add(
new PathFigure(_currentFigure, GetGraphicsPath(_currentFigure).GetBounds(),
null, _strokeStyle, LineWidth, LineCap, LineJoin, _lineDash,
false, this.Tag));
633 _currentFigure =
new List<Segment>();
636 public void SetClippingPath()
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>();
642 public void CubicBezierTo(
double x1,
double y1,
double x2,
double y2,
double x3,
double y3)
644 _currentFigure.Add(
new CubicBezierSegment(x1, y1, x2, y2, x3, y3));
647 public void SetLineDash(
LineDash dash)
654 private void PathText(
string text,
double x,
double y)
658 for (
int j = 0; j < textPath.
Segments.Count; j++)
678 public void FillText(
string text,
double x,
double y)
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));
686 PathText(text, x, y);
693 public void Restore()
695 _figures.Add(
new TransformFigure(TransformFigure.TransformTypes.Restore,
null,
this.Tag));
698 public void Rotate(
double angle)
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));
705 _figures.Add(
new TransformFigure(TransformFigure.TransformTypes.Save,
null,
this.Tag));
709 public void StrokeText(
string text,
double x,
double y)
713 _figures.Add(
new TextFigure(text,
Font,
new Point(x, y), TextBaseline,
null, _strokeStyle, LineWidth, LineCap, LineJoin, _lineDash, this.Tag));
717 PathText(text, x, y);
722 public void Translate(
double x,
double y)
724 _figures.Add(
new TransformFigure(TransformFigure.TransformTypes.Transform,
new double[,] { { 1, 0, x }, { 0, 1, y }, { 0, 0, 1 } }, this.Tag));
727 public void Scale(
double scaleX,
double scaleY)
729 _figures.Add(
new TransformFigure(TransformFigure.TransformTypes.Transform,
new double[,] { { scaleX, 0, 0 }, { 0, scaleY, 0 }, { 0, 0, 1 } }, this.Tag));
732 public void Transform(
double a,
double b,
double c,
double d,
double e,
double f)
734 _figures.Add(
new TransformFigure(TransformFigure.TransformTypes.Transform,
new double[,] { { a, b, e }, { c, d, f }, { 0, 0, 1 } }, this.Tag));
737 public void DrawRasterImage(
int sourceX,
int sourceY,
int sourceWidth,
int sourceHeight,
double destinationX,
double destinationY,
double destinationWidth,
double destinationHeight, RasterImage image)
741 MoveTo(destinationX, destinationY);
742 LineTo(destinationX + destinationWidth, destinationY);
743 LineTo(destinationX + destinationWidth, destinationY + destinationHeight);
744 LineTo(destinationX, destinationY + destinationHeight);
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;
753 double scaleX = destinationWidth / sourceRectWidth;
754 double scaleY = destinationHeight / sourceRectHeight;
756 double translationX = destinationX / scaleX - sourceRectX;
757 double translationY = destinationY / scaleY - sourceRectY;
760 Scale(scaleX, scaleY);
761 Translate(translationX, translationY);
763 _figures.Add(
new RasterImageFigure(image, this.Tag));
768 public void DrawFilteredGraphics(Graphics graphics,
IFilter filter)
770 if (this._filterOption.Operation == PDFContextInterpreter.FilterOption.FilterOperations.RasteriseAll)
772 double scale = this._filterOption.RasterisationResolution;
774 Rectangle bounds = graphics.GetBounds();
778 if (bounds.Size.Width > 0 && bounds.Size.Height > 0)
780 if (!this._filterOption.RasterisationResolutionRelative)
782 scale = scale / Math.Min(bounds.Size.Width, bounds.Size.Height);
785 if (graphics.TryRasterise(bounds, scale,
true, out RasterImage rasterised))
787 RasterImage filtered =
null;
791 filtered = locInvFilter.Filter(rasterised, scale);
795 filtered = filterWithLoc.Filter(rasterised, bounds, scale);
798 if (filtered !=
null)
800 rasterised.Dispose();
802 DrawRasterImage(0, 0, filtered.Width, filtered.Height, bounds.Location.X, bounds.Location.Y, bounds.Size.Width, bounds.Size.Height, filtered);
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"".");
814 else if (this._filterOption.Operation == PDFContextInterpreter.FilterOption.FilterOperations.IgnoreAll)
816 graphics.CopyToIGraphicsContext(
this);
830 private static string GetKernedString(
string text,
Font font)
832 List<(string,
Point)> tSpans =
new List<(
string,
Point)>();
834 StringBuilder currentRun =
new StringBuilder();
837 Point currentGlyphPlacementDelta =
new Point();
842 for (
int i = 0; i < text.Length; i++)
844 if (i < text.Length - 1)
846 currentGlyphPlacementDelta = nextGlyphPlacementDelta;
847 currentGlyphAdvanceDelta = nextGlyphAdvanceDelta;
848 nextGlyphAdvanceDelta =
new Point();
849 nextGlyphPlacementDelta =
new Point();
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);
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);
863 if (currentGlyphPlacementDelta.
X != 0 || currentGlyphPlacementDelta.
Y != 0 || currentGlyphAdvanceDelta.
X != 0 || currentGlyphAdvanceDelta.
Y != 0)
865 if (currentRun.Length > 0)
867 tSpans.Add((currentRun.ToString(), currentKerning));
869 tSpans.Add((text[i].ToString(),
new Point(currentGlyphPlacementDelta.
X, currentGlyphPlacementDelta.
Y)));
872 currentKerning =
new Point(currentGlyphAdvanceDelta.
X - currentGlyphPlacementDelta.
X, currentGlyphAdvanceDelta.
Y - currentGlyphPlacementDelta.
Y);
876 tSpans.Add((text[i].ToString(),
new Point(currentGlyphPlacementDelta.
X + currentKerning.
X, currentGlyphPlacementDelta.
Y + currentKerning.
Y)));
879 currentKerning =
new Point(currentGlyphAdvanceDelta.
X - currentGlyphPlacementDelta.
X, currentGlyphAdvanceDelta.
Y - currentGlyphPlacementDelta.
Y);
884 currentRun.Append(text[i]);
888 if (currentRun.Length > 0)
890 tSpans.Add((currentRun.ToString(), currentKerning));
893 StringBuilder sb =
new StringBuilder();
896 for (
int i = 0; i < tSpans.Count; i++)
898 if (tSpans[i].Item2.X != 0)
900 sb.Append((-tSpans[i].Item2.X).ToString(
"0.################", System.Globalization.CultureInfo.InvariantCulture));
904 sb.Append(EscapeStringForPDF(tSpans[i].Item1));
910 return sb.ToString();
914 private static string EscapeStringForPDF(
string str)
916 StringBuilder sb =
new StringBuilder();
918 for (
int i = 0; i < str.Length; i++)
922 if (CP1252Chars.Contains(ch))
926 if (!
"\n\r\t\b\f()\\".Contains(ch))
963 string octal = Convert.ToString((
int)ch, 8);
964 while (octal.Length < 3)
968 sb.Append(
"\\" + octal);
976 return sb.ToString();
980 private static string EscapeSymbolStringForPDF(
string str, Dictionary<char, int> glyphIndices)
982 StringBuilder sb =
new StringBuilder();
984 for (
int i = 0; i < str.Length; i++)
986 sb.Append((glyphIndices[str[i]]).ToString(
"X4"));
988 return sb.ToString();
991 private static Dictionary<string, FontFamily> GetFontFamilies(PDFContext[] pdfContexts)
993 Dictionary<string, FontFamily> tbr =
new Dictionary<string, FontFamily>();
995 foreach (PDFContext ctx
in pdfContexts)
997 foreach (IFigure act
in ctx._figures)
999 if (act is TextFigure figure && !tbr.ContainsKey(figure.Font.FontFamily.FamilyName))
1009 private static Dictionary<string, HashSet<char>> GetUsedChars(PDFContext[] pdfContexts)
1011 Dictionary<string, HashSet<char>> tbr =
new Dictionary<string, HashSet<char>>();
1013 foreach (PDFContext ctx
in pdfContexts)
1015 foreach (IFigure act
in ctx._figures)
1017 if (act is TextFigure figure && !tbr.ContainsKey(figure.Font.FontFamily.FamilyName))
1019 tbr.Add(figure.Font.FontFamily.FamilyName,
new HashSet<char>(figure.Text));
1021 else if (act is TextFigure figure1)
1023 string txt = figure1.Text;
1024 for (
int i = 0; i < txt.Length; i++)
1026 tbr[figure1.Font.FontFamily.FamilyName].Add(txt[i]);
1036 private static double[] GetAlphas(PDFContext[] pdfContexts)
1038 HashSet<double> tbr =
new HashSet<double>();
1042 foreach (PDFContext ctx
in pdfContexts)
1044 foreach (IFigure act
in ctx._figures)
1046 if (act.Stroke !=
null)
1062 if (act.Fill !=
null)
1079 return tbr.ToArray();
1082 private static Dictionary<string, RasterImage> GetAllImages(PDFContext[] pdfContexts)
1084 Dictionary<string, RasterImage> tbr =
new Dictionary<string, RasterImage>();
1086 foreach (PDFContext ctx
in pdfContexts)
1088 foreach (IFigure act
in ctx._figures)
1090 if (act is RasterImageFigure figure && !tbr.ContainsKey(figure.Image.Id))
1092 tbr.Add(figure.Image.Id, figure.Image);
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' };
1114 using (FileStream stream =
new FileStream(fileName, FileMode.Create))
1116 document.SaveAsPDF(stream, textOption, compressStreams, linkDestinations, filterOption);
1170 public double RasterisationResolution {
get; } = 1;
1175 public bool RasterisationResolutionRelative {
get; } =
true;
1190 this.Operation = operation;
1191 this.RasterisationResolution = rasterisationResolution;
1192 this.RasterisationResolutionRelative = rasterisationResolutionRelative;
1209 if (linkDestinations ==
null)
1211 linkDestinations =
new Dictionary<string, string>();
1214 if (filterOption ==
null)
1221 List<long> objectPositions =
new List<long>();
1224 string currObject =
"";
1226 int resourceObject = -1;
1228 StreamWriter sw =
new StreamWriter(stream, Encoding.GetEncoding(
"ISO-8859-1"), 1024,
true);
1231 sw.Write(
"%PDF-1.4\n");
1234 PDFContext[] pageContexts =
new PDFContext[document.
Pages.Count];
1236 for (
int i = 0; i < document.
Pages.Count; i++)
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]);
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>();
1254 foreach (KeyValuePair<string, FontFamily> kvp
in allFontFamilies)
1256 List<char> nonSymbol =
new List<char>();
1257 List<char> symbol =
new List<char>();
1259 foreach (
char c
in usedChars[kvp.Key])
1261 if (CP1252Chars.Contains(c))
1272 if (((kvp.Value.IsStandardFamily && kvp.Value.FileName !=
"Symbol" && kvp.Value.FileName !=
"ZapfDingbats") && symbol.Count == 0) || kvp.Value.TrueTypeFile ==
null)
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;
1287 int fontFileInd = objectNum;
1289 TrueTypeFile subsettedFont = kvp.Value.TrueTypeFile.SubsetFont(
new string(usedChars[kvp.Key].ToArray()));
1291 Stream compressedStream;
1293 if (!compressStreams)
1299 compressedStream = ZLibCompress(subsettedFont.
FontStream);
1302 long length = compressedStream.Length;
1304 objectPositions.Add(position);
1305 currObject = objectNum.ToString() +
" 0 obj\n<< /Length " + length.ToString() +
" /Length1 " + subsettedFont.
FontStream.Length.ToString();
1307 if (compressStreams)
1309 currObject +=
" /Filter [ /FlateDecode ]";
1312 currObject +=
" >>\nstream\n";
1313 sw.Write(currObject);
1314 position += currObject.Length;
1317 compressedStream.Seek(0, SeekOrigin.Begin);
1318 compressedStream.CopyTo(stream);
1321 currObject =
"endstream\nendobj\n";
1322 sw.Write(currObject);
1323 position += currObject.Length;
1327 if (nonSymbol.Count > 0)
1329 PDFFontDescriptor desc =
new PDFFontDescriptor(subsettedFont,
true,
false);
1331 int fontDescriptorInd = objectNum;
1332 objectPositions.Add(position);
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;
1342 fontObjectNums.Add(
"nonsymbol: " + kvp.Key, objectNum);
1343 nonSymbolFontIDs.Add(kvp.Key,
"F" + fontId.ToString());
1344 objectPositions.Add(position);
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();
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 [ ";
1351 for (
int i = firstChar; i <= lastChar; i++)
1353 if (nonSymbol.Contains(CP1252Chars[i]))
1363 currObject +=
"] >>\nendobj\n";
1364 sw.Write(currObject);
1365 position += currObject.Length;
1371 if (symbol.Count > 0)
1373 PDFFontDescriptor desc =
new PDFFontDescriptor(subsettedFont,
true,
true);
1375 int fontDescriptorInd = objectNum;
1376 objectPositions.Add(position);
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;
1386 Dictionary<char, int> glyphIndices =
new Dictionary<char, int>();
1388 for (
int i = 0; i < symbol.Count; i++)
1390 glyphIndices.Add(symbol[i], subsettedFont.
GetGlyphIndex(symbol[i]));
1393 symbolGlyphIndices.Add(kvp.Key, glyphIndices);
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 [ ";
1400 for (
int i = 0; i < symbol.Count; i++)
1402 currObject += glyphIndices[symbol[i]].ToString() +
" [ ";
1406 currObject +=
"] >>\nendobj\n";
1407 sw.Write(currObject);
1408 position += currObject.Length;
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++)
1416 toUnicodeStream +=
"<" + glyphIndices[symbol[i]].ToString(
"X4") +
"> <" + ((int)symbol[i]).ToString(
"X4") +
">\n";
1418 toUnicodeStream +=
"endbfchar\nendcmap\nCmapName currentdict /CMap defineresource pop\nend\nend\n";
1420 MemoryStream uncompressedUnicode =
new MemoryStream();
1422 using (StreamWriter usw =
new StreamWriter(uncompressedUnicode, Encoding.ASCII, 1024,
true))
1424 usw.Write(toUnicodeStream);
1427 uncompressedUnicode.Seek(0, SeekOrigin.Begin);
1429 MemoryStream compressedToUnicode;
1431 if (!compressStreams)
1433 compressedToUnicode = uncompressedUnicode;
1437 compressedToUnicode = ZLibCompress(uncompressedUnicode);
1440 long unicodeLength = compressedToUnicode.Length;
1442 int toUnicodeInd = objectNum;
1443 objectPositions.Add(position);
1444 currObject = objectNum.ToString() +
" 0 obj\n<< /Length " + unicodeLength;
1446 if (compressStreams)
1448 currObject +=
" /Filter [ /FlateDecode ]";
1451 currObject +=
" >>\nstream\n";
1453 sw.Write(currObject);
1454 position += currObject.Length;
1457 compressedToUnicode.WriteTo(stream);
1458 position += unicodeLength;
1460 currObject =
"endstream\nendobj\n";
1461 sw.Write(currObject);
1462 position += currObject.Length;
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;
1478 foreach (KeyValuePair<string, RasterImage> img
in allImages)
1487 objectPositions.Add(position);
1490 MemoryStream alphaStream =
new MemoryStream();
1496 if (compressStreams)
1498 filter =
"/FlateDecode";
1500 for (
int y = 0; y < image.
Height; y++)
1502 for (
int x = 0; x < image.
Width; x++)
1505 alphaStream.WriteByte(*dataPointer);
1510 alphaStream.Seek(0, SeekOrigin.Begin);
1511 MemoryStream compressed = ZLibCompress(alphaStream);
1512 alphaStream.Dispose();
1513 alphaStream = compressed;
1517 filter =
"/ASCIIHexDecode";
1519 using (StreamWriter imageWriter =
new StreamWriter(alphaStream, Encoding.ASCII, 1024,
true))
1521 for (
int y = 0; y < image.
Height; y++)
1523 for (
int x = 0; x < image.
Width; x++)
1526 imageWriter.Write((*dataPointer).ToString(
"X2"));
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";
1536 sw.Write(currObject);
1537 position += currObject.Length;
1540 alphaStream.Seek(0, SeekOrigin.Begin);
1541 alphaStream.CopyTo(stream);
1542 position += alphaStream.Length;
1544 currObject =
"\nendstream\nendobj\n";
1545 sw.Write(currObject);
1546 position += currObject.Length;
1549 alphaStream.Dispose();
1553 objectPositions.Add(position);
1554 int imageObjectNum = objectNum;
1557 MemoryStream imageStream =
new MemoryStream();
1563 if (compressStreams)
1565 filter =
"/FlateDecode";
1569 for (
int y = 0; y < image.
Height; y++)
1571 for (
int x = 0; x < image.
Width; x++)
1573 imageStream.WriteByte(*dataPointer);
1575 imageStream.WriteByte(*dataPointer);
1577 imageStream.WriteByte(*dataPointer);
1585 for (
int y = 0; y < image.
Height; y++)
1587 for (
int x = 0; x < image.
Width; x++)
1589 imageStream.WriteByte(*dataPointer);
1591 imageStream.WriteByte(*dataPointer);
1593 imageStream.WriteByte(*dataPointer);
1599 imageStream.Seek(0, SeekOrigin.Begin);
1600 MemoryStream compressed = ZLibCompress(imageStream);
1601 imageStream.Dispose();
1602 imageStream = compressed;
1606 filter =
"/ASCIIHexDecode";
1608 using (StreamWriter imageWriter =
new StreamWriter(imageStream, Encoding.ASCII, 1024,
true))
1612 for (
int y = 0; y < image.
Height; y++)
1614 for (
int x = 0; x < image.
Width; x++)
1616 imageWriter.Write((*dataPointer).ToString(
"X2"));
1618 imageWriter.Write((*dataPointer).ToString(
"X2"));
1620 imageWriter.Write((*dataPointer).ToString(
"X2"));
1628 for (
int y = 0; y < image.
Height; y++)
1630 for (
int x = 0; x < image.
Width; x++)
1632 imageWriter.Write((*dataPointer).ToString(
"X2"));
1634 imageWriter.Write((*dataPointer).ToString(
"X2"));
1636 imageWriter.Write((*dataPointer).ToString(
"X2"));
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";
1647 sw.Write(currObject);
1648 position += currObject.Length;
1651 imageStream.Seek(0, SeekOrigin.Begin);
1652 imageStream.CopyTo(stream);
1653 position += imageStream.Length;
1655 currObject =
"\nendstream\nendobj\n";
1656 sw.Write(currObject);
1657 position += currObject.Length;
1660 imageStream.Dispose();
1662 imageObjectNums.Add(img.Key, imageObjectNum);
1665 int fontListObject = -1;
1667 if (allFontFamilies.Count > 0)
1670 objectPositions.Add(position);
1671 fontListObject = objectNum;
1672 currObject = objectNum.ToString() +
" 0 obj\n<< ";
1673 foreach (KeyValuePair<string, string> kvp
in nonSymbolFontIDs)
1675 currObject +=
"/" + kvp.Value +
" " + fontObjectNums[
"nonsymbol: " + kvp.Key].ToString() +
" 0 R ";
1677 foreach (KeyValuePair<string, string> kvp
in symbolFontIDs)
1679 currObject +=
"/" + kvp.Value +
" " + fontObjectNums[
"symbol: " + kvp.Key].ToString() +
" 0 R ";
1681 currObject +=
">>\nendobj\n";
1682 sw.Write(currObject);
1683 position += currObject.Length;
1688 int[] pageContentInd =
new int[document.Pages.Count];
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)>();
1696 for (
int pageInd = 0; pageInd < document.Pages.Count; pageInd++)
1698 taggedObjectRectsByPage[pageInd] =
new List<(string, List<(double, double, double, double)>)>();
1699 taggedObjectRectsIndicesByPage[pageInd] =
new Dictionary<string, int>();
1701 double[,] transformationMatrix =
new double[3, 3] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
1702 Stack<double[,]> savedStates =
new Stack<double[,]>();
1704 MemoryStream contentStream =
new MemoryStream();
1706 using (StreamWriter ctW =
new StreamWriter(contentStream, Encoding.ASCII, 1024,
true))
1708 for (
int i = 0; i < pageContexts[pageInd]._figures.Count; i++)
1710 bool isTransform = pageContexts[pageInd]._figures[i] is TransformFigure;
1712 bool isClip = pageContexts[pageInd]._figures[i] is PathFigure pathFig && pathFig.IsClipping;
1714 if (!
string.IsNullOrEmpty(pageContexts[pageInd]._figures[i].Tag) && !isTransform && !isClip)
1716 (double, double, double, double) boundingRect = MeasureFigure(pageContexts[pageInd]._figures[i], ref transformationMatrix, savedStates);
1718 if (!taggedObjectRectsIndicesByPage[pageInd].TryGetValue(pageContexts[pageInd]._figures[i].Tag, out
int index))
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;
1725 (string, List<(double, double, double, double)>) previousRect = taggedObjectRectsByPage[pageInd][index];
1726 taggedObjectRectsByPage[pageInd][index].Item2.Add(boundingRect);
1729 else if (isTransform)
1731 MeasureFigure(pageContexts[pageInd]._figures[i], ref transformationMatrix, savedStates);
1734 ctW.Write(FigureAsPDFString(pageContexts[pageInd]._figures[i], nonSymbolFontIDs, symbolFontIDs, symbolGlyphIndices, alphas, imageObjectNums, transformationMatrix, gradients));
1739 objectPositions.Add(position);
1740 contentStream.Seek(0, SeekOrigin.Begin);
1742 MemoryStream compressedStream;
1744 if (!compressStreams)
1746 compressedStream = contentStream;
1750 compressedStream = ZLibCompress(contentStream);
1753 long streamLength = compressedStream.Length;
1755 pageContentInd[pageInd] = objectNum;
1756 currObject = objectNum.ToString() +
" 0 obj\n<< /Length " + streamLength.ToString(System.Globalization.CultureInfo.InvariantCulture);
1758 if (compressStreams)
1760 currObject +=
" /Filter [ /FlateDecode ]";
1763 currObject +=
" >>\nstream\n";
1765 sw.Write(currObject);
1768 position += currObject.Length;
1769 compressedStream.WriteTo(stream);
1770 position += streamLength;
1772 compressedStream.Dispose();
1774 currObject =
"endstream\nendobj\n";
1775 sw.Write(currObject);
1776 position += currObject.Length;
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);
1785 if (gradients.Count > 0)
1787 for (
int i = 0; i < gradients.Count; i++)
1789 (
GradientBrush gradient,
double[,] matrix, IFigure figure) = gradients[i];
1793 bool hasAlpha =
false;
1916 WriteGradient(
true, ref gradient, ref objectPositions, ref position, ref currObject, ref objectNum, ref sw, ref hasAlpha, ref matrix, ref gradientIndices);
1920 gradientAlphaIndices.Add(-1);
1921 gradientMaskIndices.Add(-1);
1933 bool hasAlpha2 =
false;
1935 WriteGradient(
false, ref alphaGradient, ref objectPositions, ref position, ref currObject, ref objectNum, ref sw, ref hasAlpha2, ref matrix, ref gradientAlphaIndices);
1937 int alphaGradientIndex = gradientAlphaIndices[gradientAlphaIndices.Count - 1];
1941 MemoryStream contentStream =
new MemoryStream();
1943 using (StreamWriter ctW =
new StreamWriter(contentStream, Encoding.ASCII, 1024,
true))
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");
1953 contentStream.Seek(0, SeekOrigin.Begin);
1955 MemoryStream compressedStream;
1957 if (!compressStreams)
1959 compressedStream = contentStream;
1963 compressedStream = ZLibCompress(contentStream);
1966 long streamLength = compressedStream.Length;
1968 objectPositions.Add(position);
1969 int maskIndex = objectNum;
1971 currObject = objectNum.ToString() +
" 0 obj\n<< /Type /XObject /Subtype /Form " +
1972 "/Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> " +
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) +
1982 "/Resources << /Pattern << /pa" + gradientAlphaIndices.Count +
" " + alphaGradientIndex.ToString() +
" 0 R >> >> " +
1984 "/Length " + streamLength.ToString();
1986 if (compressStreams)
1988 if (compressStreams)
1990 currObject +=
" /Filter [ /FlateDecode ]";
1994 currObject +=
" >>\nstream\n";
1996 sw.Write(currObject);
1999 position += currObject.Length;
2000 compressedStream.WriteTo(stream);
2001 position += streamLength;
2003 compressedStream.Dispose();
2005 currObject =
"endstream\nendobj\n";
2006 sw.Write(currObject);
2007 position += currObject.Length;
2012 objectPositions.Add(position);
2013 int actualMaskIndex = objectNum;
2015 gradientMaskIndices.Add(actualMaskIndex);
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;
2026 if (allFontFamilies.Count > 0)
2030 objectPositions.Add(position);
2031 resourceObject = objectNum;
2032 currObject = objectNum.ToString() +
" 0 obj\n<< /Font " + fontListObject.ToString() +
" 0 R";
2034 if (alphas.Length > 0 || gradientMaskIndices.Where(x => x >= 0).Any())
2036 currObject +=
" /ExtGState <<\n";
2038 for (
int i = 0; i < alphas.Length; i++)
2040 currObject +=
"/a" + i.ToString() +
" << /CA " + alphas[i].ToString(
"0.################", System.Globalization.CultureInfo.InvariantCulture) +
" /ca " + alphas[i].ToString(
"0.################", System.Globalization.CultureInfo.InvariantCulture) +
" >>\n";
2043 for (
int i = 0; i < gradientMaskIndices.Count; i++)
2045 if (gradientMaskIndices[i] >= 0)
2047 currObject +=
"/ma" + i.ToString() +
" " + gradientMaskIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) +
" 0 R\n";
2054 if (imageObjectNums.Count > 0)
2056 currObject +=
" /XObject <<";
2058 foreach (KeyValuePair<string, int> kvp
in imageObjectNums)
2060 currObject +=
" /Img" + kvp.Value.ToString() +
" " + kvp.Value.ToString() +
" 0 R";
2063 currObject +=
" >>";
2066 if (gradientIndices.Count > 0)
2068 currObject +=
" /Pattern << ";
2070 for (
int i = 0; i < gradientIndices.Count; i++)
2072 currObject +=
"/p" + i.ToString(System.Globalization.CultureInfo.InvariantCulture) +
" " + gradientIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) +
" 0 R ";
2078 currObject +=
" >>\nendobj\n";
2079 sw.Write(currObject);
2080 position += currObject.Length;
2086 objectPositions.Add(position);
2087 resourceObject = objectNum;
2088 currObject = objectNum.ToString() +
" 0 obj\n<<";
2090 if (alphas.Length > 0 || gradientMaskIndices.Where(x => x >= 0).Any())
2092 currObject +=
" /ExtGState <<\n";
2094 for (
int i = 0; i < alphas.Length; i++)
2096 currObject +=
"/a" + i.ToString() +
" << /CA " + alphas[i].ToString(
"0.################", System.Globalization.CultureInfo.InvariantCulture) +
" /ca " + alphas[i].ToString(
"0.################", System.Globalization.CultureInfo.InvariantCulture) +
" >>\n";
2099 for (
int i = 0; i < gradientMaskIndices.Count; i++)
2101 if (gradientMaskIndices[i] >= 0)
2103 currObject +=
"/ma" + i.ToString() +
" " + gradientMaskIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) +
" 0 R\n";
2110 if (imageObjectNums.Count > 0)
2112 currObject +=
" /XObject <<";
2114 foreach (KeyValuePair<string, int> kvp
in imageObjectNums)
2116 currObject +=
" /Img" + kvp.Value.ToString() +
" " + kvp.Value.ToString() +
" 0 R";
2119 currObject +=
" >>";
2122 if (gradientIndices.Count > 0)
2124 currObject +=
" /Pattern << ";
2126 for (
int i = 0; i < gradientIndices.Count; i++)
2128 currObject +=
"/p" + i.ToString(System.Globalization.CultureInfo.InvariantCulture) +
" " + gradientIndices[i].ToString(System.Globalization.CultureInfo.InvariantCulture) +
" 0 R ";
2134 currObject +=
" >>\nendobj\n";
2135 sw.Write(currObject);
2136 position += currObject.Length;
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;
2148 objectPositions.Add(position);
2149 int pageParent = objectNum;
2152 List<int> pageObjectNums =
new List<int>();
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))>();
2158 for (
int i = 0; i < document.Pages.Count; i++)
2160 List<int> annotationsToInclude =
new List<int>();
2163 for (
int j = 0; j < taggedObjectRectsByPage[i].Count; j++)
2165 if (linkDestinations.TryGetValue(taggedObjectRectsByPage[i][j].Item1, out
string destination))
2167 if (destination.StartsWith(
"#"))
2170 for (
int k = 0; k < taggedObjectRectsIndicesByPage.Length; k++)
2172 if (taggedObjectRectsIndicesByPage[k].TryGetValue(destination.Substring(1), out
int index))
2174 for (
int l = 0; l < taggedObjectRectsByPage[i][j].Item2.Count; l++)
2176 objectPositions.Add(position);
2177 annotationsToInclude.Add(objectNum);
2178 postponedAnnotations.Add((objectNum, taggedObjectRectsByPage[i][j].Item2[l], k, taggedObjectRectsByPage[k][index].Item2[0]));
2189 for (
int l = 0; l < taggedObjectRectsByPage[i][j].Item2.Count; l++)
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);
2197 position += currObject.Length;
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 ";
2207 if (annotationsToInclude.Count > 0)
2209 StringBuilder annotations =
new StringBuilder();
2210 annotations.Append(
"/Annots [ ");
2211 for (
int j = 0; j < annotationsToInclude.Count; j++)
2213 annotations.Append(annotationsToInclude[j].ToString() +
" 0 R ");
2215 annotations.Append(
"] ");
2216 currObject += annotations.ToString();
2219 currObject +=
">>\nendobj\n";
2221 sw.Write(currObject);
2222 pageObjectNums.Add(objectNum);
2224 position += currObject.Length;
2228 foreach ((
int annotationObjectNum, (
double,
double,
double,
double) annotationOrigin,
int annotationDestinationPage, (
double,
double,
double,
double) annotationDestination) in postponedAnnotations)
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;
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++)
2243 currObject += pageObjectNums[i].ToString() +
" 0 R ";
2245 currObject +=
"] /Count " + document.Pages.Count +
" >>\nendobj\n\n";
2246 sw.Write(currObject);
2247 position += currObject.Length;
2250 sw.Write(
"xref\n0 " + (objectPositions.Count + 1).ToString() +
"\n0000000000 65535 f \n");
2251 for (
int i = 0; i < objectPositions.Count; i++)
2253 sw.Write(objectPositions[i].ToString(
"0000000000", System.Globalization.CultureInfo.InvariantCulture) +
" 00000 n \n");
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");
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)
2265 int functionObject = -1;
2267 if (gradient.GradientStops.Count == 2)
2269 objectPositions.Add(position);
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;
2276 position += currObject.Length;
2279 hasAlpha = gradient.GradientStops[0].Colour.A != 1 || gradient.GradientStops[1].Colour.A != 1;
2283 List<double> bounds =
new List<double>();
2284 List<int> functionIndices =
new List<int>();
2286 for (
int j = 0; j < gradient.GradientStops.Count - 1; j++)
2288 objectPositions.Add(position);
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);
2295 if (j < gradient.GradientStops.Count - 2)
2297 bounds.Add(gradient.GradientStops[j + 1].Offset);
2300 position += currObject.Length;
2303 if (gradient.GradientStops[j].Colour.A != 1)
2309 if (gradient.GradientStops[gradient.GradientStops.Count - 1].Colour.A != 1)
2314 objectPositions.Add(position);
2316 currObject = objectNum.ToString() +
" 0 obj\n<< /FunctionType 3 /Domain [ 0 1 ] /Functions [ ";
2318 for (
int j = 0; j < functionIndices.Count; j++)
2320 currObject += functionIndices[j].ToString(System.Globalization.CultureInfo.InvariantCulture) +
" 0 R ";
2323 currObject +=
"] /Bounds [ ";
2325 for (
int j = 0; j < bounds.Count; j++)
2327 currObject += bounds[j].ToString(System.Globalization.CultureInfo.InvariantCulture) +
" ";
2330 currObject +=
"] /Encode [ ";
2332 for (
int j = 0; j < functionIndices.Count; j++)
2334 currObject +=
"0 1 ";
2337 currObject +=
"] >>\nendobj\n";
2340 sw.Write(currObject);
2341 functionObject = objectNum;
2343 position += currObject.Length;
2348 if (gradient is LinearGradientBrush linear)
2350 objectPositions.Add(position);
2352 currObject = objectNum.ToString() +
" 0 obj\n<< /Type /Pattern /PatternType 2 ";
2356 currObject +=
"/Matrix [ " +
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) +
" ] ";
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) +
" ] ";
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);
2372 position += currObject.Length;
2375 else if (gradient is RadialGradientBrush radial)
2377 objectPositions.Add(position);
2379 currObject = objectNum.ToString() +
" 0 obj\n<< /Type /Pattern /PatternType 2 ";
2383 currObject +=
"/Matrix [ " +
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) +
" ] ";
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) +
" ] ";
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);
2399 position += currObject.Length;
2404 private static (double, double, double, double) MeasureFigure(IFigure figure, ref
double[,] transformationMatrix, Stack<
double[,]> savedStates)
2406 if (figure is PathFigure)
2408 PathFigure fig = figure as PathFigure;
2410 double minX =
double.MaxValue;
2411 double maxX =
double.MinValue;
2412 double minY =
double.MaxValue;
2413 double maxY =
double.MinValue;
2415 for (
int i = 0; i < fig.Segments.Length; i++)
2417 switch (fig.Segments[i].Type)
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);
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);
2438 for (
int j = 0; j < fig.Segments[i].Points.Length; j++)
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);
2452 return (minX, minY, maxX, maxY);
2454 else if (figure is TextFigure)
2456 TextFigure fig = figure as TextFigure;
2458 double realX = fig.Position.X;
2460 if (fig.Font.FontFamily.TrueTypeFile !=
null)
2462 realX = fig.Position.X - fig.Font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(fig.Text[0]).LeftSideBearing * fig.Font.FontSize / 1000;
2468 if (fig.Font.FontFamily.TrueTypeFile !=
null)
2470 for (
int i = 0; i < fig.Text.Length; i++)
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);
2478 double realY = fig.Position.Y;
2490 realY -= (yMax + yMin) * 0.5;
2494 realY -= yMax + yMin;
2497 Font.DetailedFontMetrics metrics = fig.Font.MeasureTextAdvanced(fig.Text);
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);
2504 corner1 = transformationMatrix.Multiply(corner1);
2505 corner2 = transformationMatrix.Multiply(corner2);
2506 corner3 = transformationMatrix.Multiply(corner3);
2507 corner4 = transformationMatrix.Multiply(corner4);
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))));
2511 else if (figure is TransformFigure transf)
2513 if (transf.TransformType == TransformFigure.TransformTypes.Transform)
2515 transformationMatrix = transformationMatrix.Multiply(transf.TransformationMatrix);
2517 else if (transf.TransformType == TransformFigure.TransformTypes.Save)
2519 savedStates.Push(transformationMatrix);
2521 else if (transf.TransformType == TransformFigure.TransformTypes.Restore)
2523 transformationMatrix = savedStates.Pop();
2526 return (0, 0, 0, 0);
2528 else if (figure is RasterImageFigure)
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);
2535 corner1 = transformationMatrix.Multiply(corner1);
2536 corner2 = transformationMatrix.Multiply(corner2);
2537 corner3 = transformationMatrix.Multiply(corner3);
2538 corner4 = transformationMatrix.Multiply(corner4);
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))));
2544 return (0, 0, 0, 0);
2548 private static double[,] Multiply(
this double[,] matrix,
double[,] matrix2)
2550 double[,] tbr =
new double[3, 3];
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];
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];
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];
2567 private static Point Multiply(
this double[,] matrix, Point point)
2569 double[] transPt =
new double[3];
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];
2575 return new Point(transPt[0] / transPt[2], transPt[1] / transPt[2]);
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)
2581 StringBuilder sb =
new StringBuilder();
2583 if (figure.Fill !=
null)
2585 if (figure.Fill is SolidColourBrush solid)
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");
2590 else if (figure.Fill is GradientBrush gradient)
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] } };
2595 gradients.Add((gradient, clonedMatrix, figure));
2597 if (!PDFContext.IsCompatible(gradient))
2599 sb.Append(
"/ma" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) +
" gs ");
2602 sb.Append(
"/Pattern cs /p" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) +
" scn /a" + Array.IndexOf(alphas, 1.0).ToString() +
" gs\n");
2606 if (figure.Stroke !=
null)
2608 if (figure.Stroke is SolidColourBrush solid)
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");
2613 else if (figure.Stroke is GradientBrush gradient)
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] } };
2618 gradients.Add((gradient, clonedMatrix, figure));
2620 if (!PDFContext.IsCompatible(gradient))
2622 sb.Append(
"/ma" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) +
" gs ");
2625 sb.Append(
"/Pattern CS /p" + brushIndex.ToString(System.Globalization.CultureInfo.InvariantCulture) +
" SCN /a" + Array.IndexOf(alphas, 1.0).ToString() +
" gs\n");
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)
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");
2637 sb.Append(
"[] 0 d\n");
2641 if (figure is PathFigure)
2643 PathFigure fig = figure as PathFigure;
2645 for (
int i = 0; i < fig.Segments.Length; i++)
2647 switch (fig.Segments[i].Type)
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 ");
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 ");
2660 for (
int j = 0; j < fig.Segments[i].Points.Length; j++)
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) +
" ");
2678 if (fig.Fill !=
null)
2683 if (fig.Stroke !=
null)
2689 else if (figure is TextFigure)
2691 TextFigure fig = figure as TextFigure;
2693 List<(
string txt,
bool isSymbolic)> segments =
new List<(
string txt,
bool isSymbolic)>();
2695 StringBuilder currSeg =
new StringBuilder();
2696 bool currSymbolic =
false;
2698 for (
int i = 0; i < fig.Text.Length; i++)
2700 if (CP1252Chars.Contains(fig.Text[i]))
2704 currSeg.Append(fig.Text[i]);
2708 if (currSeg.Length > 0)
2710 segments.Add((currSeg.ToString(), currSymbolic));
2713 currSeg =
new StringBuilder();
2714 currSymbolic =
false;
2715 currSeg.Append(fig.Text[i]);
2722 currSeg.Append(fig.Text[i]);
2726 if (currSeg.Length > 0)
2728 segments.Add((currSeg.ToString(), currSymbolic));
2731 currSeg =
new StringBuilder();
2732 currSymbolic =
true;
2733 currSeg.Append(fig.Text[i]);
2738 if (currSeg.Length > 0)
2740 segments.Add((currSeg.ToString(), currSymbolic));
2745 double realX = fig.Position.X;
2747 if (fig.Font.FontFamily.TrueTypeFile !=
null)
2749 realX = fig.Position.X - fig.Font.FontFamily.TrueTypeFile.Get1000EmGlyphBearings(fig.Text[0]).LeftSideBearing * fig.Font.FontSize / 1000;
2755 if (fig.Font.FontFamily.TrueTypeFile !=
null)
2757 for (
int i = 0; i < fig.Text.Length; i++)
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);
2765 double realY = fig.Position.Y;
2777 realY -= (yMax + yMin) * 0.5;
2781 realY -= yMax + yMin;
2784 double middleY = realY + (yMax + yMin) * 0.5;
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");
2794 if (figure.Stroke !=
null && figure.Fill !=
null)
2796 sb.Append(
"2 Tr\n");
2798 else if (figure.Stroke !=
null)
2800 sb.Append(
"1 Tr\n");
2802 else if (figure.Fill !=
null)
2804 sb.Append(
"0 Tr\n");
2807 sb.Append(realX.ToString(
"0.################", System.Globalization.CultureInfo.InvariantCulture) +
" " + realY.ToString(
"0.################", System.Globalization.CultureInfo.InvariantCulture) +
" Td\n");
2809 for (
int i = 0; i < segments.Count; i++)
2811 if (!segments[i].isSymbolic)
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");
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");
2823 sb.Append(
"ET\nQ\n");
2825 else if (figure is TransformFigure transf)
2827 if (transf.TransformType == TransformFigure.TransformTypes.Transform)
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");
2836 else if (transf.TransformType == TransformFigure.TransformTypes.Save)
2840 else if (transf.TransformType == TransformFigure.TransformTypes.Restore)
2845 else if (figure is RasterImageFigure fig)
2847 sb.Append(
"/a" + Array.IndexOf(alphas, 1).ToString() +
" gs\n");
2849 int imageNum = imageObjectNums[fig.Image.Id];
2851 sb.Append(
"/Img" + imageNum.ToString() +
" Do\n");
2854 return sb.ToString();
2857 internal static MemoryStream ZLibCompress(Stream contentStream)
2859 MemoryStream compressedStream =
new MemoryStream();
2860 compressedStream.Write(
new byte[] { 0x78, 0x01 }, 0, 2);
2862 using (DeflateStream deflate =
new DeflateStream(compressedStream, CompressionLevel.Optimal,
true))
2864 contentStream.CopyTo(deflate);
2866 contentStream.Seek(0, SeekOrigin.Begin);
2868 uint checksum = Adler32(contentStream);
2870 compressedStream.Write(
new byte[] { (byte)((checksum >> 24) & 255), (byte)((checksum >> 16) & 255), (
byte)((checksum >> 8) & 255), (byte)(checksum & 255) }, 0, 4);
2872 compressedStream.Seek(0, SeekOrigin.Begin);
2874 return compressedStream;
2877 internal static uint Adler32(Stream contentStream)
2884 while ((readByte = contentStream.ReadByte()) >= 0)
2886 s1 = (s1 + (byte)readByte) % 65521U;
2887 s2 = (s2 + s1) % 65521U;
2890 return (s2 << 16) + s1;