#21 support writing lines, curves and rectangles. add documentinformation to output. rename characterpath

This commit is contained in:
Eliot Jones
2018-12-12 00:09:15 +00:00
parent 29f9885fc4
commit 924fc7b37f
23 changed files with 302 additions and 44 deletions

View File

@@ -10,7 +10,7 @@
[Fact]
public void BezierCurveGeneratesCorrectBoundingBox()
{
var curve = new CharacterPath.BezierCurve(new PdfPoint(60, 105),
var curve = new PdfPath.BezierCurve(new PdfPoint(60, 105),
new PdfPoint(75, 30),
new PdfPoint(215, 115),
new PdfPoint(140, 160));
@@ -28,7 +28,7 @@
[Fact]
public void LoopBezierCurveGeneratesCorrectBoundingBox()
{
var curve = new CharacterPath.BezierCurve(new PdfPoint(166, 142),
var curve = new PdfPath.BezierCurve(new PdfPoint(166, 142),
new PdfPoint(75, 30),
new PdfPoint(215, 115),
new PdfPoint(140, 160));
@@ -47,7 +47,7 @@
[Fact]
public void BezierCurveAddsCorrectSvgCommand()
{
var curve = new CharacterPath.BezierCurve(new PdfPoint(60, 105),
var curve = new PdfPath.BezierCurve(new PdfPoint(60, 105),
new PdfPoint(75, 30),
new PdfPoint(215, 115),
new PdfPoint(140, 160));

View File

@@ -3,6 +3,7 @@
using System.Collections.Generic;
using Content;
using PdfPig.Fonts;
using PdfPig.Geometry;
using PdfPig.Graphics;
using PdfPig.IO;
using PdfPig.Tokens;
@@ -17,9 +18,14 @@
public TextMatrices TextMatrices { get; set; }
= new TextMatrices();
public PdfPath CurrentPath { get; set; }
public PdfPoint CurrentPosition { get; set; }
public TestOperationContext()
{
StateStack.Push(new CurrentGraphicsState());
CurrentPath = new PdfPath();
}
public CurrentGraphicsState GetCurrentState()
@@ -48,6 +54,18 @@
public void ApplyXObject(StreamToken xObjectStream)
{
}
public void BeginSubpath()
{
}
public void StrokePath(bool close)
{
}
public void ClosePath()
{
}
}
internal class TestResourceStore : IResourceStore

View File

@@ -93,6 +93,14 @@
var page = builder.AddPage(PageSize.A4);
page.DrawLine(new PdfPoint(30, 520), new PdfPoint(360, 520));
page.DrawLine(new PdfPoint(360, 520), new PdfPoint(360, 250));
page.DrawLine(new PdfPoint(25, 70), new PdfPoint(100, 70), 3);
page.DrawRectangle(new PdfPoint(30, 100), 250, 100, 0.5m);
page.DrawRectangle(new PdfPoint(30, 200), 250, 100, 0.5m);
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Fonts", "TrueType");
var file = Path.Combine(path, "Andada-Regular.ttf");
@@ -139,6 +147,8 @@
{
var builder = new PdfDocumentBuilder();
builder.DocumentInformation.Title = "Hello Windows!";
var page = builder.AddPage(PageSize.A4);
var file = @"C:\Windows\Fonts\BASKVILL.TTF";

View File

@@ -18,7 +18,7 @@
/// <summary>
/// The current path.
/// </summary>
public CharacterPath Path { get; } = new CharacterPath();
public PdfPath Path { get; } = new PdfPath();
/// <summary>
/// The current location of the active point.

View File

@@ -46,7 +46,7 @@
/// Evaluate the CharString for the character with a given name returning the path constructed for the glyph.
/// </summary>
/// <param name="name">The name of the character to retrieve the CharString for.</param>
/// <returns>A <see cref="CharacterPath"/> for the glyph.</returns>
/// <returns>A <see cref="PdfPath"/> for the glyph.</returns>
public Type2Glyph Generate(string name)
{
Type2Glyph glyph;
@@ -171,7 +171,7 @@
/// The path of the glyph.
/// </summary>
[NotNull]
public CharacterPath Path { get; }
public PdfPath Path { get; }
/// <summary>
/// The width of the glyph as a difference from the nominal width X for the font. Optional.
@@ -181,7 +181,7 @@
/// <summary>
/// Create a new <see cref="Type2Glyph"/>.
/// </summary>
public Type2Glyph(CharacterPath path, decimal? widthDifferenceFromNominal)
public Type2Glyph(PdfPath path, decimal? widthDifferenceFromNominal)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
WidthDifferenceFromNominal = widthDifferenceFromNominal;

View File

@@ -7,7 +7,7 @@ namespace UglyToad.PdfPig.Fonts
using System.Text;
using Geometry;
internal class CharacterPath
internal class PdfPath
{
private readonly List<IPathCommand> commands = new List<IPathCommand>();
private PdfPoint? currentPosition;
@@ -239,8 +239,8 @@ namespace UglyToad.PdfPig.Fonts
double maxX;
if (StartPoint.X <= EndPoint.X)
{
minX = (double) StartPoint.X;
maxX = (double) EndPoint.X;
minX = (double)StartPoint.X;
maxX = (double)EndPoint.X;
}
else
{
@@ -254,9 +254,9 @@ namespace UglyToad.PdfPig.Fonts
{
minY = (double)StartPoint.Y;
maxY = (double)EndPoint.Y;
}
else
{
}
else
{
minY = (double)EndPoint.Y;
maxY = (double)StartPoint.Y;
}
@@ -294,7 +294,7 @@ namespace UglyToad.PdfPig.Fonts
// P' = 3da(1-t)^2 + 6db(1-t)t + 3dct^2
// P' = 3da - 3dat - 3dat + 3dat^2 + 6dbt - 6dbt^2 + 3dct^2
// P' = (3da - 6db + 3dc)t^2 + (6db - 3da - 3da)t + 3da
var p1 = (double)( isX ? StartPoint.X : StartPoint.Y);
var p1 = (double)(isX ? StartPoint.X : StartPoint.Y);
var p2 = (double)(isX ? FirstControlPoint.X : FirstControlPoint.Y);
var p3 = (double)(isX ? SecondControlPoint.X : SecondControlPoint.Y);
var p4 = (double)(isX ? EndPoint.X : EndPoint.Y);
@@ -361,8 +361,8 @@ namespace UglyToad.PdfPig.Fonts
// P = (1t)^3*P_1 + 3(1t)^2*t*P_2 + 3(1t)*t^2*P_3 + t^3*P_4
var oneMinusT = 1 - t;
var p = ((oneMinusT * oneMinusT * oneMinusT) * p1)
+ (3 * (oneMinusT * oneMinusT) * t * p2)
+ (3 * oneMinusT * (t * t) * p3)
+ (3 * (oneMinusT * oneMinusT) * t * p2)
+ (3 * oneMinusT * (t * t) * p3)
+ ((t * t * t) * p4);
return p;
@@ -374,5 +374,9 @@ namespace UglyToad.PdfPig.Fonts
EndPoint.X, EndPoint.Y);
}
}
public void Rectangle(decimal x, decimal y, decimal width, decimal height)
{
}
}
}

View File

@@ -7,7 +7,7 @@
internal class Type1BuildCharContext
{
private readonly Func<int, CharacterPath> characterByIndexFactory;
private readonly Func<int, PdfPath> characterByIndexFactory;
public IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> Subroutines { get; }
public decimal WidthX { get; set; }
@@ -21,7 +21,7 @@
public bool IsFlexing { get; set; }
[NotNull]
public CharacterPath Path { get; private set; } = new CharacterPath();
public PdfPath Path { get; private set; } = new PdfPath();
public PdfPoint CurrentPosition { get; set; }
@@ -32,7 +32,7 @@
public IReadOnlyList<PdfPoint> FlexPoints { get; }
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
Func<int, CharacterPath> characterByIndexFactory)
Func<int, PdfPath> characterByIndexFactory)
{
this.characterByIndexFactory = characterByIndexFactory ?? throw new ArgumentNullException(nameof(characterByIndexFactory));
Subroutines = subroutines ?? throw new ArgumentNullException(nameof(subroutines));
@@ -43,12 +43,12 @@
}
public CharacterPath GetCharacter(int characterCode)
public PdfPath GetCharacter(int characterCode)
{
return characterByIndexFactory(characterCode);
}
public void SetPath(CharacterPath path)
public void SetPath(PdfPath path)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
}

View File

@@ -10,7 +10,7 @@
{
private readonly IReadOnlyDictionary<int, string> charStringIndexToName;
private readonly object locker = new object();
private readonly Dictionary<string, CharacterPath> glyphs = new Dictionary<string, CharacterPath>();
private readonly Dictionary<string, PdfPath> glyphs = new Dictionary<string, PdfPath>();
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
@@ -24,9 +24,9 @@
Subroutines = subroutines ?? throw new ArgumentNullException(nameof(subroutines));
}
public CharacterPath Generate(string name)
public PdfPath Generate(string name)
{
CharacterPath glyph;
PdfPath glyph;
lock (locker)
{
if (glyphs.TryGetValue(name, out var result))
@@ -47,7 +47,7 @@
return glyph;
}
private CharacterPath Run(CommandSequence sequence)
private PdfPath Run(CommandSequence sequence)
{
var context = new Type1BuildCharContext(Subroutines, i =>
{

View File

@@ -16,6 +16,7 @@
internal class ContentStreamProcessor : IOperationContext
{
private readonly List<PdfPath> paths = new List<PdfPath>();
private readonly IResourceStore resourceStore;
private readonly UserSpaceUnit userSpaceUnit;
private readonly bool isLenientParsing;
@@ -26,6 +27,9 @@
public TextMatrices TextMatrices { get; } = new TextMatrices();
public PdfPath CurrentPath { get; private set; }
public PdfPoint CurrentPosition { get; set; }
public int StackSize => graphicsStack.Count;
private readonly Dictionary<XObjectType, List<XObjectContentRecord>> xObjects = new Dictionary<XObjectType, List<XObjectContentRecord>>
@@ -73,7 +77,7 @@
graphicsStack.Push(saved.Peek().DeepClone());
return saved;
}
[DebuggerStepThrough]
public CurrentGraphicsState GetCurrentState()
{
@@ -245,6 +249,26 @@
}
}
public void BeginSubpath()
{
CurrentPath = new PdfPath();
}
public void StrokePath(bool close)
{
if (close)
{
ClosePath();
}
}
public void ClosePath()
{
CurrentPath.ClosePath();
paths.Add(CurrentPath);
CurrentPath = null;
}
private void AdjustTextMatrix(decimal tx, decimal ty)
{
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);

View File

@@ -1,11 +1,19 @@
namespace UglyToad.PdfPig.Graphics
{
using System.Collections.Generic;
using Fonts;
using Geometry;
using IO;
using Tokens;
using Util.JetBrains.Annotations;
internal interface IOperationContext
{
[CanBeNull]
PdfPath CurrentPath { get; }
PdfPoint CurrentPosition { get; set; }
CurrentGraphicsState GetCurrentState();
TextMatrices TextMatrices { get; }
@@ -21,5 +29,11 @@
void ShowPositionedText(IReadOnlyList<IToken> tokens);
void ApplyXObject(StreamToken xObjectStream);
void BeginSubpath();
void StrokePath(bool close);
void ClosePath();
}
}

View File

@@ -17,11 +17,13 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.StrokePath(true);
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -25,7 +25,10 @@
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteDecimal(Width);
stream.WriteWhiteSpace();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -25,11 +25,28 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.CurrentPath.BezierCurveTo(ControlPoint1.X, ControlPoint1.Y,
ControlPoint2.X, ControlPoint2.Y,
End.X, End.Y);
operationContext.CurrentPosition = End;
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteDecimal(ControlPoint1.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(ControlPoint1.Y);
stream.WriteWhiteSpace();
stream.WriteDecimal(ControlPoint2.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(ControlPoint2.Y);
stream.WriteWhiteSpace();
stream.WriteDecimal(End.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(End.Y);
stream.WriteWhiteSpace();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -22,11 +22,26 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.CurrentPath.BezierCurveTo(ControlPoint1.X, ControlPoint1.Y,
End.X,
End.Y,
End.X,
End.Y);
operationContext.CurrentPosition = End;
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteDecimal(ControlPoint1.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(ControlPoint1.Y);
stream.WriteWhiteSpace();
stream.WriteDecimal(End.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(End.Y);
stream.WriteWhiteSpace();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -26,11 +26,23 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.BeginSubpath();
operationContext.CurrentPath.Rectangle(LowerLeft.X, LowerLeft.Y, Width, Height);
operationContext.CurrentPath.ClosePath();
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteDecimal(LowerLeft.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(LowerLeft.Y);
stream.WriteWhiteSpace();
stream.WriteDecimal(Width);
stream.WriteWhiteSpace();
stream.WriteDecimal(Height);
stream.WriteWhiteSpace();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -22,11 +22,27 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.CurrentPath.BezierCurveTo(operationContext.CurrentPosition.X,
operationContext.CurrentPosition.Y,
ControlPoint2.X,
ControlPoint2.Y,
End.X,
End.Y);
operationContext.CurrentPosition = End;
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteDecimal(ControlPoint2.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(ControlPoint2.Y);
stream.WriteWhiteSpace();
stream.WriteDecimal(End.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(End.Y);
stream.WriteWhiteSpace();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -19,11 +19,18 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.CurrentPath.LineTo(End.X, End.Y);
operationContext.CurrentPosition = End;
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteDecimal(End.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(End.Y);
stream.WriteWhiteSpace();
stream.WriteText(Symbol);
stream.WriteWhiteSpace();
}
public override string ToString()

View File

@@ -19,11 +19,18 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.BeginSubpath();
operationContext.CurrentPosition = Point;
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteDecimal(Point.X);
stream.WriteWhiteSpace();
stream.WriteDecimal(Point.Y);
stream.WriteWhiteSpace();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -17,11 +17,13 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.CurrentPath.ClosePath();
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -17,11 +17,13 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.StrokePath(false);
}
public void Write(Stream stream)
{
throw new System.NotImplementedException();
stream.WriteText(Symbol);
stream.WriteNewLine();
}
public override string ToString()

View File

@@ -17,12 +17,15 @@
internal class PdfDocumentBuilder
{
private const byte Break = (byte) '\n';
private const byte Break = (byte)'\n';
private static readonly TrueTypeFontParser Parser = new TrueTypeFontParser();
private readonly Dictionary<int, PdfPageBuilder> pages = new Dictionary<int, PdfPageBuilder>();
private readonly Dictionary<Guid, FontStored> fonts = new Dictionary<Guid, FontStored>();
public bool IncludeDocumentInformation { get; set; } = true;
public DocumentInformationBuilder DocumentInformation { get; } = new DocumentInformationBuilder();
public IReadOnlyDictionary<int, PdfPageBuilder> Pages => pages;
public IReadOnlyDictionary<Guid, IWritingFont> Fonts => fonts.ToDictionary(x => x.Key, x => x.Value.FontProgram);
@@ -219,7 +222,18 @@
var catalogRef = context.WriteObject(memory, catalog);
TokenWriter.WriteCrossReferenceTable(context.ObjectOffsets, catalogRef, memory);
var informationReference = default(IndirectReference?);
if (IncludeDocumentInformation)
{
var informationDictionary = DocumentInformation.ToDictionary();
if (informationDictionary.Count > 0)
{
var dictionary = new DictionaryToken(informationDictionary);
informationReference = context.WriteObject(memory, dictionary).Number;
}
}
TokenWriter.WriteCrossReferenceTable(context.ObjectOffsets, catalogRef, memory, informationReference);
return memory.ToArray();
}
@@ -257,7 +271,7 @@
new NumericToken(rectangle.TopRight.Y)
});
}
private static void WriteString(string text, MemoryStream stream, bool appendBreak = true)
{
var bytes = OtherEncodings.StringAsLatin1Bytes(text);
@@ -295,5 +309,52 @@
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}
internal class DocumentInformationBuilder
{
public string Title { get; set; }
public string Author { get; set; }
public string Subject { get; set; }
public string Keywords { get; set; }
public string Creator { get; set; }
public string Producer { get; set; } = "PdfPig";
internal Dictionary<NameToken, IToken> ToDictionary()
{
var result = new Dictionary<NameToken, IToken>();
if (Title != null)
{
result[NameToken.Title] = new StringToken(Title);
}
if (Author != null)
{
result[NameToken.Author] = new StringToken(Author);
}
if (Subject != null)
{
result[NameToken.Subject] = new StringToken(Subject);
}
if (Keywords != null)
{
result[NameToken.Keywords] = new StringToken(Keywords);
}
if (Creator != null)
{
result[NameToken.Creator] = new StringToken(Creator);
}
if (Producer != null)
{
result[NameToken.Producer] = new StringToken(Producer);
}
return result;
}
}
}
}

View File

@@ -6,6 +6,8 @@
using Core;
using Geometry;
using Graphics.Operations;
using Graphics.Operations.General;
using Graphics.Operations.PathConstruction;
using Graphics.Operations.TextObjects;
using Graphics.Operations.TextPositioning;
using Graphics.Operations.TextShowing;
@@ -28,6 +30,39 @@
PageNumber = number;
}
public void DrawLine(PdfPoint from, PdfPoint to, decimal lineWidth = 1)
{
if (lineWidth != 1)
{
operations.Add(new SetLineWidth(lineWidth));
}
operations.Add(new BeginNewSubpath(from.X, from.Y));
operations.Add(new AppendStraightLineSegment(to.X, to.Y));
operations.Add(StrokePath.Value);
if (lineWidth != 1)
{
operations.Add(new SetLineWidth(1));
}
}
public void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1)
{
if (lineWidth != 1)
{
operations.Add(new SetLineWidth(lineWidth));
}
operations.Add(new AppendRectangle(position.X, position.Y, width, height));
operations.Add(StrokePath.Value);
if (lineWidth != 1)
{
operations.Add(new SetLineWidth(lineWidth));
}
}
public List<Letter> AddText(string text, decimal fontSize, PdfPoint position, PdfDocumentBuilder.AddedFont font)
{
if (font == null)

View File

@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Tokens;
@@ -95,7 +96,8 @@
public static void WriteCrossReferenceTable(IReadOnlyDictionary<IndirectReference, long> objectOffsets,
ObjectToken catalogToken,
Stream outputStream)
Stream outputStream,
IndirectReference? documentInformationReference)
{
if (objectOffsets.Count == 0)
{
@@ -150,11 +152,18 @@
outputStream.Write(Trailer, 0, Trailer.Length);
WriteLineBreak(outputStream);
var trailerDictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
var trailerDictionaryData = new Dictionary<NameToken, IToken>
{
{NameToken.Size, new NumericToken(objectOffsets.Count) },
{NameToken.Root, new IndirectReferenceToken(catalogToken.Number) }
});
{NameToken.Size, new NumericToken(objectOffsets.Count)},
{NameToken.Root, new IndirectReferenceToken(catalogToken.Number)}
};
if (documentInformationReference.HasValue)
{
trailerDictionaryData[NameToken.Info] = new IndirectReferenceToken(documentInformationReference.Value);
}
var trailerDictionary = new DictionaryToken(trailerDictionaryData);
WriteDictionary(trailerDictionary, outputStream);
WriteLineBreak(outputStream);