public byte[] Build()
        {
            var context      = new BuilderContext();
            var fontsWritten = new Dictionary <Guid, ObjectToken>();

            using (var memory = new MemoryStream())
            {
                // Header
                WriteString("%PDF-1.7", memory);

                // Body
                foreach (var font in fonts)
                {
                    var fontObj = font.Value.FontProgram.WriteFont(font.Value.FontKey.Name, memory, context);
                    fontsWritten.Add(font.Key, fontObj);
                }

                var resources = new Dictionary <NameToken, IToken>
                {
                    { NameToken.ProcSet, new ArrayToken(new [] { NameToken.Create("PDF"), NameToken.Create("Text") }) }
                };

                if (fontsWritten.Count > 0)
                {
                    var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => (fonts[x.Key].FontKey.Name, (IToken) new IndirectReferenceToken(x.Value.Number)))
                                                              .ToDictionary(x => x.Item1, x => x.Item2));

                    var fontsDictionaryRef = context.WriteObject(memory, fontsDictionary);

                    resources.Add(NameToken.Font, new IndirectReferenceToken(fontsDictionaryRef.Number));
                }

                var pageReferences = new List <IndirectReferenceToken>();
                foreach (var page in pages)
                {
                    var pageDictionary = new Dictionary <NameToken, IToken>
                    {
                        { NameToken.Type, NameToken.Page },
                        {
                            NameToken.Resources,
                            new DictionaryToken(resources)
                        },
                        { NameToken.MediaBox, RectangleToArray(page.Value.PageSize) }
                    };

                    if (page.Value.Operations.Count > 0)
                    {
                        var contentStream = WriteContentStream(page.Value.Operations);

                        var contentStreamObj = context.WriteObject(memory, contentStream);

                        pageDictionary[NameToken.Contents] = new IndirectReferenceToken(contentStreamObj.Number);
                    }

                    var pageRef = context.WriteObject(memory, new DictionaryToken(pageDictionary));

                    pageReferences.Add(new IndirectReferenceToken(pageRef.Number));
                }

                var pagesDictionary = new DictionaryToken(new Dictionary <NameToken, IToken>
                {
                    { NameToken.Type, NameToken.Pages },
                    { NameToken.Kids, new ArrayToken(pageReferences) },
                    { NameToken.Count, new NumericToken(1) }
                });

                var pagesRef = context.WriteObject(memory, pagesDictionary);

                var catalog = new DictionaryToken(new Dictionary <NameToken, IToken>
                {
                    { NameToken.Type, NameToken.Catalog },
                    { NameToken.Pages, new IndirectReferenceToken(pagesRef.Number) }
                });

                var catalogRef = context.WriteObject(memory, catalog);

                TokenWriter.WriteCrossReferenceTable(context.ObjectOffsets, catalogRef, memory);

                return(memory.ToArray());
            }
        }
        /// <summary>
        /// Builds a PDF document from the current content of this builder and its pages.
        /// </summary>
        /// <returns>The bytes of the resulting PDF document.</returns>
        public byte[] Build()
        {
            var context      = new BuilderContext();
            var fontsWritten = new Dictionary <Guid, ObjectToken>();

            using (var memory = new MemoryStream())
            {
                // Header
                WriteString("%PDF-1.7", memory);

                // Files with binary data should contain a 2nd comment line followed by 4 bytes with values > 127
                memory.WriteText("%");
                memory.WriteByte(169);
                memory.WriteByte(205);
                memory.WriteByte(196);
                memory.WriteByte(210);
                memory.WriteNewLine();

                // Body
                foreach (var font in fonts)
                {
                    var fontObj = font.Value.FontProgram.WriteFont(font.Value.FontKey.Name, memory, context);
                    fontsWritten.Add(font.Key, fontObj);
                }

                var resources = new Dictionary <NameToken, IToken>
                {
                    { NameToken.ProcSet, new ArrayToken(new [] { NameToken.Create("PDF"), NameToken.Create("Text") }) }
                };

                if (fontsWritten.Count > 0)
                {
                    var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => (fonts[x.Key].FontKey.Name, (IToken) new IndirectReferenceToken(x.Value.Number)))
                                                              .ToDictionary(x => x.Item1, x => x.Item2));

                    var fontsDictionaryRef = context.WriteObject(memory, fontsDictionary);

                    resources.Add(NameToken.Font, new IndirectReferenceToken(fontsDictionaryRef.Number));
                }

                var reserved       = context.ReserveNumber();
                var parentIndirect = new IndirectReferenceToken(new IndirectReference(reserved, 0));

                var pageReferences = new List <IndirectReferenceToken>();
                foreach (var page in pages)
                {
                    var pageDictionary = new Dictionary <NameToken, IToken>
                    {
                        { NameToken.Type, NameToken.Page },
                        {
                            NameToken.Resources,
                            new DictionaryToken(resources)
                        },
                        { NameToken.MediaBox, RectangleToArray(page.Value.PageSize) },
                        { NameToken.Parent, parentIndirect }
                    };

                    if (page.Value.Operations.Count > 0)
                    {
                        var contentStream = WriteContentStream(page.Value.Operations);

                        var contentStreamObj = context.WriteObject(memory, contentStream);

                        pageDictionary[NameToken.Contents] = new IndirectReferenceToken(contentStreamObj.Number);
                    }

                    var pageRef = context.WriteObject(memory, new DictionaryToken(pageDictionary));

                    pageReferences.Add(new IndirectReferenceToken(pageRef.Number));
                }

                var pagesDictionary = new DictionaryToken(new Dictionary <NameToken, IToken>
                {
                    { NameToken.Type, NameToken.Pages },
                    { NameToken.Kids, new ArrayToken(pageReferences) },
                    { NameToken.Count, new NumericToken(pageReferences.Count) }
                });

                var pagesRef = context.WriteObject(memory, pagesDictionary, reserved);

                var catalog = new DictionaryToken(new Dictionary <NameToken, IToken>
                {
                    { NameToken.Type, NameToken.Catalog },
                    { NameToken.Pages, new IndirectReferenceToken(pagesRef.Number) }
                });

                var catalogRef = context.WriteObject(memory, catalog);

                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());
            }
        }
        public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
        {
            var bytes        = fontFileBytes;
            var embeddedFile = new StreamToken(new DictionaryToken(new Dictionary <NameToken, IToken>
            {
                { NameToken.Length, new NumericToken(bytes.Count) }
            }), bytes);

            var fileRef = context.WriteObject(outputStream, embeddedFile);

            var baseFont = NameToken.Create(font.TableRegister.NameTable.GetPostscriptName());

            var charCodeToGlyphId = new CharacterCodeToGlyphIdMapper(font);

            var postscript = font.TableRegister.PostScriptTable;
            var hhead      = font.TableRegister.HorizontalHeaderTable;

            var bbox = font.TableRegister.HeaderTable.Bounds;

            var scaling = 1000m / font.TableRegister.HeaderTable.UnitsPerEm;
            var descriptorDictionary = new Dictionary <NameToken, IToken>
            {
                { NameToken.Type, NameToken.FontDescriptor },
                { NameToken.FontName, baseFont },
                // TODO: get flags TrueTypeEmbedder.java
                { NameToken.Flags, new NumericToken((int)FontFlags.Symbolic) },
                { NameToken.FontBbox, GetBoundingBox(bbox, scaling) },
                { NameToken.ItalicAngle, new NumericToken(postscript.ItalicAngle) },
                { NameToken.Ascent, new NumericToken(hhead.Ascender * scaling) },
                { NameToken.Descent, new NumericToken(hhead.Descender * scaling) },
                { NameToken.CapHeight, new NumericToken(90) },
                { NameToken.StemV, new NumericToken(90) },
                { NameToken.FontFile2, new IndirectReferenceToken(fileRef.Number) }
            };

            var os2 = font.TableRegister.Os2Table;

            if (os2 == null)
            {
                throw new InvalidFontFormatException("Embedding TrueType font requires OS/2 table.");
            }

            if (os2 is Os2Version2To4OpenTypeTable twoPlus)
            {
                descriptorDictionary[NameToken.CapHeight] = new NumericToken(twoPlus.CapHeight);
                descriptorDictionary[NameToken.Xheight]   = new NumericToken(twoPlus.XHeight);
            }

            descriptorDictionary[NameToken.StemV] = new NumericToken(bbox.Width * scaling * 0.13m);

            var metrics = charCodeToGlyphId.GetMetrics();

            var widthsRef = context.WriteObject(outputStream, metrics.Widths);

            var descriptor = context.WriteObject(outputStream, new DictionaryToken(descriptorDictionary));

            var dictionary = new Dictionary <NameToken, IToken>
            {
                { NameToken.Type, NameToken.Font },
                { NameToken.Subtype, NameToken.TrueType },
                { NameToken.BaseFont, baseFont },
                { NameToken.FontDescriptor, new IndirectReferenceToken(descriptor.Number) },
                { NameToken.FirstChar, metrics.FirstChar },
                { NameToken.LastChar, metrics.LastChar },
                { NameToken.Widths, new IndirectReferenceToken(widthsRef.Number) },
                { NameToken.Encoding, NameToken.MacRomanEncoding }
            };

            var token = new DictionaryToken(dictionary);

            var result = context.WriteObject(outputStream, token);

            return(result);
        }