Skip to content

Commit 696a3ff

Browse files
committed
Add basic shape support
1 parent 9ace674 commit 696a3ff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1393
-91
lines changed

samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Synercoding.FileFormats.Pdf.Extensions;
2+
using Synercoding.FileFormats.Pdf.LowLevel.Graphics;
23
using Synercoding.Primitives;
34
using Synercoding.Primitives.Extensions;
45
using System.IO;
@@ -53,6 +54,45 @@ public static void Main(string[] args)
5354
page.AddImage(eyeStream, new Rectangle(offSet, offSet, width + offSet, height + offSet, Unit.Millimeters));
5455
}
5556
})
57+
// Test shape graphics
58+
.AddPage(page =>
59+
{
60+
page.AddShapes(ctx =>
61+
{
62+
ctx.DefaultState(g =>
63+
{
64+
g.LineWidth = 1;
65+
g.Fill = null;
66+
g.Stroke = null;
67+
g.Dash = new Dash()
68+
{
69+
Array = new double[0],
70+
Phase = 0
71+
};
72+
g.MiterLimit = 10;
73+
g.LineCap = LineCapStyle.ButtCap;
74+
g.LineJoin = LineJoinStyle.MiterJoin;
75+
});
76+
77+
ctx.NewPath(g => { g.Fill = Colors.Red; g.Stroke = Colors.Black; g.LineWidth = 5; })
78+
.Move(100, 100)
79+
.LineTo(200, 100)
80+
.LineTo(200, 200)
81+
.LineTo(100, 200);
82+
ctx.NewPath(g => { g.Fill = Colors.Blue; g.Stroke = null; })
83+
.Move(50, 50)
84+
.LineTo(150, 50)
85+
.LineTo(150, 150)
86+
.LineTo(50, 150)
87+
.Close();
88+
ctx.NewPath(g => { g.Fill = null; g.Stroke = Colors.Yellow; g.LineWidth = 3; g.Dash = new Dash() { Array = new[] { 5d } }; })
89+
.Move(150, 150)
90+
.LineTo(250, 150)
91+
.LineTo(250, 250)
92+
.LineTo(150, 250)
93+
.Close();
94+
});
95+
})
5696
// Test placement using matrix
5797
.AddPage(page =>
5898
{

src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
using Synercoding.FileFormats.Pdf.Internals;
2+
using Synercoding.FileFormats.Pdf.LowLevel;
13
using Synercoding.Primitives;
4+
using System;
25
using System.IO;
36

47
namespace Synercoding.FileFormats.Pdf.Extensions
@@ -123,5 +126,30 @@ public static PdfPage AddImage(this PdfPage page, Stream jpgStream, int original
123126
/// <returns>The same <see cref="PdfPage"/> to chain other calls.</returns>
124127
public static PdfPage AddImage(this PdfPage page, Stream jpgStream, int originalWidth, int originalHeight, Rectangle rectangle)
125128
=> page.AddImage(jpgStream, originalWidth, originalHeight, rectangle.AsPlacementMatrix());
129+
130+
/// <summary>
131+
/// Add shapes to the pdf page
132+
/// </summary>
133+
/// <param name="page">The page to add the shapes to</param>
134+
/// <param name="paintAction">The action painting the shapes</param>
135+
/// <returns>The same <see cref="PdfPage"/> to chain other calls.</returns>
136+
public static PdfPage AddShapes(this PdfPage page, Action<IShapeContext> paintAction)
137+
=> page.AddShapes(paintAction, static (action, context) => action(context));
138+
139+
/// <summary>
140+
/// Add shapes to the pdf page
141+
/// </summary>
142+
/// <typeparam name="T">Type of <paramref name="data"/></typeparam>
143+
/// <param name="page">The page to add the shapes to</param>
144+
/// <param name="data">Data that can be passed to the <paramref name="paintAction"/></param>
145+
/// <param name="paintAction">The action painting the shapes</param>
146+
/// <returns>The same <see cref="PdfPage"/> to chain other calls.</returns>
147+
public static PdfPage AddShapes<T>(this PdfPage page, T data, Action<T, IShapeContext> paintAction)
148+
{
149+
using (var context = new ShapeContext(page.ContentStream))
150+
paintAction(data, context);
151+
152+
return page;
153+
}
126154
}
127155
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Synercoding.Primitives;
2+
3+
namespace Synercoding.FileFormats.Pdf
4+
{
5+
public interface IPath
6+
{
7+
IPath Move(double x, double y);
8+
IPath Move(Point point);
9+
IPath LineTo(double x, double y);
10+
IPath LineTo(Point point);
11+
IPath Rectangle(double x, double y, double width, double height);
12+
IPath Rectangle(Rectangle rectangle);
13+
IPath CurveTo(double cpX1, double cpY1, double cpX2, double cpY2, double finalX, double finalY);
14+
IPath CurveTo(Point cp1, Point cp2, Point final);
15+
IPath CurveToWithStartAnker(double cpX1, double cpY1, double finalX, double finalY);
16+
IPath CurveToWithStartAnker(Point cp, Point final);
17+
IPath CurveToWithEndAnker(double cpX1, double cpY1, double finalX, double finalY);
18+
IPath CurveToWithEndAnker(Point cp, Point final);
19+
20+
IShapeContext Close();
21+
22+
}
23+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Synercoding.FileFormats.Pdf.LowLevel.Graphics;
2+
using System;
3+
4+
namespace Synercoding.FileFormats.Pdf
5+
{
6+
public interface IShapeContext
7+
{
8+
IShapeContext DefaultState(Action<GraphicsState> configureState);
9+
IPath NewPath();
10+
IPath NewPath(Action<GraphicsState> configureState);
11+
}
12+
}

src/Synercoding.FileFormats.Pdf/LowLevel/ByteSizes.cs renamed to src/Synercoding.FileFormats.Pdf/Internals/ByteSizes.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
using System;
1+
using System;
22
using System.Globalization;
33

4-
namespace Synercoding.FileFormats.Pdf.LowLevel
4+
namespace Synercoding.FileFormats.Pdf.Internals
55
{
66
internal static class ByteSizes
77
{
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
4+
namespace Synercoding.FileFormats.Pdf.Internals
5+
{
6+
internal sealed class Map<T1, T2> : IEnumerable<KeyValuePair<T1, T2>>
7+
where T1 : notnull
8+
where T2 : notnull
9+
{
10+
private readonly IDictionary<T1, T2> _forward = new Dictionary<T1, T2>();
11+
private readonly IDictionary<T2, T1> _reverse = new Dictionary<T2, T1>();
12+
13+
public Map()
14+
{
15+
Forward = new Indexer<T1, T2>(_forward);
16+
Reverse = new Indexer<T2, T1>(_reverse);
17+
}
18+
19+
public int Count => _forward.Count;
20+
21+
public void Add(T1 t1, T2 t2)
22+
{
23+
_forward.Add(t1, t2);
24+
_reverse.Add(t2, t1);
25+
}
26+
27+
public IEnumerator<KeyValuePair<T1, T2>> GetEnumerator()
28+
{
29+
return _forward.GetEnumerator();
30+
}
31+
32+
IEnumerator IEnumerable.GetEnumerator()
33+
{
34+
return GetEnumerator();
35+
}
36+
37+
public Indexer<T1, T2> Forward { get; }
38+
public Indexer<T2, T1> Reverse { get; }
39+
40+
public sealed class Indexer<T3, T4>
41+
where T3 : notnull
42+
where T4 : notnull
43+
{
44+
private readonly IDictionary<T3, T4> _dictionary;
45+
46+
public Indexer(IDictionary<T3, T4> dictionary)
47+
{
48+
_dictionary = dictionary;
49+
}
50+
51+
public T4 this[T3 index]
52+
{
53+
get => _dictionary[index];
54+
set => _dictionary[index] = value;
55+
}
56+
57+
public bool Contains(T3 value)
58+
=> _dictionary.ContainsKey(value);
59+
}
60+
}
61+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using Synercoding.FileFormats.Pdf.LowLevel;
2+
using Synercoding.FileFormats.Pdf.LowLevel.Graphics;
3+
using Synercoding.FileFormats.Pdf.LowLevel.Operators.Color;
4+
using Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction;
5+
using Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting;
6+
using Synercoding.FileFormats.Pdf.LowLevel.Operators.State;
7+
using Synercoding.Primitives;
8+
using System;
9+
10+
namespace Synercoding.FileFormats.Pdf.Internals
11+
{
12+
internal class Path : IPath
13+
{
14+
private readonly ShapeContext _context;
15+
private readonly ContentStream _contentStream;
16+
17+
public Path(ShapeContext context, ContentStream contentStream, GraphicsState graphicsState)
18+
{
19+
_context = context;
20+
_contentStream = contentStream;
21+
GraphicsState = graphicsState;
22+
23+
_startPath();
24+
}
25+
26+
internal GraphicsState GraphicsState { get; }
27+
28+
private void _startPath()
29+
{
30+
_contentStream.SaveState();
31+
32+
_contentStream
33+
.Write(new LineWidthOperator(GraphicsState.LineWidth))
34+
.Write(new LineCapOperator(GraphicsState.LineCap))
35+
.Write(new LineJoinOperator(GraphicsState.LineJoin))
36+
.Write(new MiterLimitOperator(GraphicsState.MiterLimit))
37+
.Write(new DashOperator(GraphicsState.Dash.Array, GraphicsState.Dash.Phase));
38+
39+
// write graphic state to stream
40+
}
41+
42+
internal void FinishPath()
43+
{
44+
// TODO: set colorspace for stroke and/or fill
45+
46+
if (GraphicsState.Stroke is not null && GraphicsState.Fill is not null)
47+
{
48+
if (GraphicsState.Stroke is GrayColor gs)
49+
_contentStream.Write(new GrayStrokingColorOperator(gs));
50+
else if (GraphicsState.Stroke is RgbColor rs)
51+
_contentStream.Write(new RgbStrokingColorOperator(rs));
52+
else if (GraphicsState.Stroke is CmykColor cs)
53+
_contentStream.Write(new CmykStrokingColorOperator(cs));
54+
else
55+
throw new NotImplementedException($"The color type {GraphicsState.Stroke.GetType().Name} is not implemented.");
56+
57+
if (GraphicsState.Fill is GrayColor gf)
58+
_contentStream.Write(new GrayNonStrokingColorOperator(gf));
59+
else if (GraphicsState.Fill is RgbColor rf)
60+
_contentStream.Write(new RgbNonStrokingColorOperator(rf));
61+
else if (GraphicsState.Fill is CmykColor cf)
62+
_contentStream.Write(new CmykNonStrokingColorOperator(cf));
63+
else
64+
throw new NotImplementedException($"The color type {GraphicsState.Fill.GetType().Name} is not implemented.");
65+
66+
_contentStream.Write(new FillAndStrokeOperator(GraphicsState.FillRule));
67+
}
68+
else if (GraphicsState.Fill is not null)
69+
{
70+
if (GraphicsState.Fill is GrayColor gf)
71+
_contentStream.Write(new GrayNonStrokingColorOperator(gf));
72+
else if (GraphicsState.Fill is RgbColor rf)
73+
_contentStream.Write(new RgbNonStrokingColorOperator(rf));
74+
else if (GraphicsState.Fill is CmykColor cf)
75+
_contentStream.Write(new CmykNonStrokingColorOperator(cf));
76+
else
77+
throw new NotImplementedException($"The color type {GraphicsState.Fill.GetType().Name} is not implemented.");
78+
79+
_contentStream.Write(new FillOperator(GraphicsState.FillRule));
80+
}
81+
else if (GraphicsState.Stroke is not null)
82+
{
83+
if (GraphicsState.Stroke is GrayColor gs)
84+
_contentStream.Write(new GrayStrokingColorOperator(gs));
85+
else if (GraphicsState.Stroke is RgbColor rs)
86+
_contentStream.Write(new RgbStrokingColorOperator(rs));
87+
else if (GraphicsState.Stroke is CmykColor cs)
88+
_contentStream.Write(new CmykStrokingColorOperator(cs));
89+
else
90+
throw new NotImplementedException($"The color type {GraphicsState.Stroke.GetType().Name} is not implemented.");
91+
92+
_contentStream.Write(new StrokeOperator());
93+
}
94+
else
95+
{
96+
_contentStream.Write(new EndPathOperator());
97+
}
98+
99+
_contentStream.RestoreState();
100+
}
101+
102+
public IShapeContext Close()
103+
{
104+
_contentStream.Write(new CloseOperator());
105+
106+
return _context;
107+
}
108+
109+
public IPath CurveTo(double cpX1, double cpY1, double cpX2, double cpY2, double finalX, double finalY)
110+
{
111+
_contentStream.Write(new CubicBezierCurveDualControlPointsOperator(cpX1, cpY1, cpX2, cpY2, finalX, finalY));
112+
return this;
113+
}
114+
115+
public IPath CurveTo(Point cp1, Point cp2, Point final)
116+
{
117+
_contentStream.Write(new CubicBezierCurveDualControlPointsOperator(cp1, cp2, final));
118+
return this;
119+
}
120+
121+
public IPath CurveToWithEndAnker(double cpX, double cpY, double finalX, double finalY)
122+
{
123+
_contentStream.Write(new CubicBezierCurveFinalControlPointsOperator(cpX, cpY, finalX, finalY));
124+
return this;
125+
}
126+
127+
public IPath CurveToWithEndAnker(Point cp, Point final)
128+
{
129+
_contentStream.Write(new CubicBezierCurveFinalControlPointsOperator(cp, final));
130+
return this;
131+
}
132+
133+
public IPath CurveToWithStartAnker(double cpX, double cpY, double finalX, double finalY)
134+
{
135+
_contentStream.Write(new CubicBezierCurveInitialControlPointsOperator(cpX, cpY, finalX, finalY));
136+
return this;
137+
}
138+
139+
public IPath CurveToWithStartAnker(Point cp, Point final)
140+
{
141+
_contentStream.Write(new CubicBezierCurveInitialControlPointsOperator(cp, final));
142+
return this;
143+
}
144+
145+
public IPath LineTo(double x, double y)
146+
{
147+
_contentStream.Write(new LineOperator(x, y));
148+
return this;
149+
}
150+
151+
public IPath LineTo(Point point)
152+
{
153+
_contentStream.Write(new LineOperator(point));
154+
return this;
155+
}
156+
157+
public IPath Move(double x, double y)
158+
{
159+
_contentStream.Write(new MoveOperator(x, y));
160+
return this;
161+
}
162+
163+
public IPath Move(Point point)
164+
{
165+
_contentStream.Write(new MoveOperator(point));
166+
return this;
167+
}
168+
169+
public IPath Rectangle(double x, double y, double width, double height)
170+
{
171+
_contentStream.Write(new RectangleOperator(x, y, width, height));
172+
return this;
173+
}
174+
175+
public IPath Rectangle(Rectangle rectangle)
176+
{
177+
_contentStream.Write(new RectangleOperator(rectangle));
178+
return this;
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)