diff --git a/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs b/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs
new file mode 100644
index 00000000..369d099f
--- /dev/null
+++ b/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using UglyToad.PdfPig.Core;
+using UglyToad.PdfPig.Graphics.Operations;
+using UglyToad.PdfPig.Tokens;
+
+namespace UglyToad.PdfPig.Writer
+{
+ ///
+ /// This class would lazily flush all token. Allowing us to make changes to references without need to rewrite the whole stream
+ ///
+ internal class PdfStreamWriter : IDisposable
+ {
+ private readonly SortedSet reservedNumbers = new SortedSet();
+
+ private readonly Dictionary tokenReferences = new Dictionary();
+
+ public int CurrentNumber { get; private set; } = 1;
+
+ public Stream Stream { get; }
+
+ public PdfStreamWriter() : this(new MemoryStream()) { }
+
+ public PdfStreamWriter(Stream baseStream)
+ {
+ Stream = baseStream;
+ }
+
+ public void Flush(decimal version, IndirectReferenceToken catalogReference)
+ {
+ if (catalogReference == null)
+ throw new ArgumentNullException(nameof(catalogReference));
+
+ WriteString($"%PDF-{version:0.0}", Stream);
+
+ Stream.WriteText("%");
+ Stream.WriteByte(169);
+ Stream.WriteByte(205);
+ Stream.WriteByte(196);
+ Stream.WriteByte(210);
+ Stream.WriteNewLine();
+
+ var offsets = new Dictionary();
+ ObjectToken catalogToken = null;
+ foreach(var pair in tokenReferences)
+ {
+ var referenceToken = pair.Key;
+ var token = pair.Value;
+ var offset = Stream.Position;
+ var obj = new ObjectToken(offset, referenceToken.Data, token);
+
+ TokenWriter.WriteToken(obj, Stream);
+
+ offsets.Add(referenceToken.Data, offset);
+
+ if (catalogToken == null && referenceToken == catalogReference)
+ {
+ catalogToken = new ObjectToken(offset, referenceToken.Data, token);
+ }
+ }
+
+ if (catalogToken == null)
+ {
+ throw new Exception("Catalog object wasn't found");
+ }
+
+ // TODO: Support document information
+ TokenWriter.WriteCrossReferenceTable(offsets, catalogToken, Stream, null);
+ }
+
+ public IndirectReferenceToken WriteObject(IToken token, int? reservedNumber = null)
+ {
+ // if you can't consider deduplicating a token.
+ // It must be because it's referenced by his child element, so you must have reserved a number before hand
+ // Example /Pages Obj
+ var canBeDuplicated = !reservedNumber.HasValue;
+ if (!canBeDuplicated)
+ {
+ if (!reservedNumbers.Remove(reservedNumber.Value))
+ {
+ throw new InvalidOperationException("You can't reuse a reserved number");
+ }
+
+ // When we end up writing this token, all of his child would already have been added and checked for duplicate
+ return AddObject(token, reservedNumber.Value);
+ }
+
+ var reference = FindToken(token);
+ if (reference == null)
+ {
+ // TODO: Check his children
+ return AddObject(token, CurrentNumber++);
+ }
+
+ return reference;
+ }
+
+ private IndirectReferenceToken AddObject(IToken token, int reservedNumber)
+ {
+ var reference = new IndirectReference(reservedNumber, 0);
+ var referenceToken = new IndirectReferenceToken(reference);
+ tokenReferences.Add(referenceToken, token);
+ return referenceToken;
+ }
+
+ private IndirectReferenceToken FindToken(IToken token)
+ {
+ foreach(var pair in tokenReferences)
+ {
+ var reference = pair.Key;
+ var storedToken = pair.Value;
+ if (storedToken.Equals(token))
+ {
+ return reference;
+ }
+ }
+
+ return null;
+ }
+
+ public int ReserveNumber()
+ {
+ var reserved = CurrentNumber;
+ reservedNumbers.Add(reserved);
+ CurrentNumber++;
+ return reserved;
+ }
+
+ public IndirectReferenceToken ReserveNumberToken() => new IndirectReferenceToken(new IndirectReference(ReserveNumber(), 0));
+
+ private static void WriteString(string text, Stream stream)
+ {
+ var bytes = OtherEncodings.StringAsLatin1Bytes(text);
+ stream.Write(bytes, 0, bytes.Length);
+ stream.WriteNewLine();
+ }
+
+ public byte[] ToArray()
+ {
+ if (!Stream.CanSeek)
+ throw new NotSupportedException("Stream can't seek");
+
+ var currentPosition = Stream.Position;
+ Stream.Seek(0, SeekOrigin.Begin);
+
+ var bytes = new byte[Stream.Length];
+
+ // Should we slice the reading into smaller chunks?
+ if (Stream.Read(bytes, 0, bytes.Length) != bytes.Length)
+ throw new Exception("Unable to read all the bytes from stream");
+
+ Stream.Seek(currentPosition, SeekOrigin.Begin);
+
+ return bytes;
+ }
+
+ public void Dispose()
+ {
+ Stream.Dispose();
+ }
+ }
+}