#region PDFsharp - A .NET library for processing PDF // // Authors: // Stefan Lange // // Copyright (c) 2005-2017 empira Software GmbH, Cologne Area (Germany) // // http://www.pdfsharp.com // http://sourceforge.net/projects/pdfsharp // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text; using System.IO; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Security; using PdfSharp.Pdf.Internal; namespace PdfSharp.Pdf.IO { /// /// Represents a writer for generation of PDF streams. /// internal class PdfWriter { public PdfWriter(Stream pdfStream, PdfStandardSecurityHandler securityHandler) { _stream = pdfStream; _securityHandler = securityHandler; //System.Xml.XmlTextWriter #if DEBUG _layout = PdfWriterLayout.Verbose; #endif } public void Close(bool closeUnderlyingStream) { if (_stream != null && closeUnderlyingStream) { #if UWP _stream.Dispose(); #else _stream.Close(); #endif } _stream = null; } public void Close() { Close(true); } public int Position { get { return (int)_stream.Position; } } /// /// Gets or sets the kind of layout. /// public PdfWriterLayout Layout { get { return _layout; } set { _layout = value; } } PdfWriterLayout _layout; public PdfWriterOptions Options { get { return _options; } set { _options = value; } } PdfWriterOptions _options; // ----------------------------------------------------------- /// /// Writes the specified value to the PDF stream. /// public void Write(bool value) { WriteSeparator(CharCat.Character); WriteRaw(value ? bool.TrueString : bool.FalseString); _lastCat = CharCat.Character; } /// /// Writes the specified value to the PDF stream. /// public void Write(PdfBoolean value) { WriteSeparator(CharCat.Character); WriteRaw(value.Value ? "true" : "false"); _lastCat = CharCat.Character; } /// /// Writes the specified value to the PDF stream. /// public void Write(int value) { WriteSeparator(CharCat.Character); WriteRaw(value.ToString(CultureInfo.InvariantCulture)); _lastCat = CharCat.Character; } /// /// Writes the specified value to the PDF stream. /// public void Write(uint value) { WriteSeparator(CharCat.Character); WriteRaw(value.ToString(CultureInfo.InvariantCulture)); _lastCat = CharCat.Character; } /// /// Writes the specified value to the PDF stream. /// public void Write(PdfInteger value) { WriteSeparator(CharCat.Character); _lastCat = CharCat.Character; WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture)); } /// /// Writes the specified value to the PDF stream. /// public void Write(PdfUInteger value) { WriteSeparator(CharCat.Character); _lastCat = CharCat.Character; WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture)); } /// /// Writes the specified value to the PDF stream. /// public void Write(double value) { WriteSeparator(CharCat.Character); WriteRaw(value.ToString(Config.SignificantFigures7, CultureInfo.InvariantCulture)); _lastCat = CharCat.Character; } /// /// Writes the specified value to the PDF stream. /// public void Write(PdfReal value) { WriteSeparator(CharCat.Character); WriteRaw(value.Value.ToString(Config.SignificantFigures7, CultureInfo.InvariantCulture)); _lastCat = CharCat.Character; } /// /// Writes the specified value to the PDF stream. /// public void Write(PdfString value) { WriteSeparator(CharCat.Delimiter); #if true PdfStringEncoding encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask); string pdf = (value.Flags & PdfStringFlags.HexLiteral) == 0 ? PdfEncoders.ToStringLiteral(value.Value, encoding, SecurityHandler) : PdfEncoders.ToHexStringLiteral(value.Value, encoding, SecurityHandler); WriteRaw(pdf); #else switch (value.Flags & PdfStringFlags.EncodingMask) { case PdfStringFlags.Undefined: case PdfStringFlags.PDFDocEncoding: if ((value.Flags & PdfStringFlags.HexLiteral) == 0) WriteRaw(PdfEncoders.DocEncode(value.Value, false)); else WriteRaw(PdfEncoders.DocEncodeHex(value.Value, false)); break; case PdfStringFlags.WinAnsiEncoding: throw new NotImplementedException("Unexpected encoding: WinAnsiEncoding"); case PdfStringFlags.Unicode: if ((value.Flags & PdfStringFlags.HexLiteral) == 0) WriteRaw(PdfEncoders.DocEncode(value.Value, true)); else WriteRaw(PdfEncoders.DocEncodeHex(value.Value, true)); break; case PdfStringFlags.StandardEncoding: case PdfStringFlags.MacRomanEncoding: case PdfStringFlags.MacExpertEncoding: default: throw new NotImplementedException("Unexpected encoding"); } #endif _lastCat = CharCat.Delimiter; } /// /// Writes the specified value to the PDF stream. /// public void Write(PdfName value) { WriteSeparator(CharCat.Delimiter, '/'); string name = value.Value; StringBuilder pdf = new StringBuilder("/"); for (int idx = 1; idx < name.Length; idx++) { char ch = name[idx]; Debug.Assert(ch < 256); if (ch > ' ') switch (ch) { // TODO: is this all? case '%': case '/': case '<': case '>': case '(': case ')': case '[': case ']': case '#': break; default: pdf.Append(name[idx]); continue; } pdf.AppendFormat("#{0:X2}", (int)name[idx]); } WriteRaw(pdf.ToString()); _lastCat = CharCat.Character; } public void Write(PdfLiteral value) { WriteSeparator(CharCat.Character); WriteRaw(value.Value); _lastCat = CharCat.Character; } public void Write(PdfRectangle rect) { const string format = Config.SignificantFigures3; WriteSeparator(CharCat.Delimiter, '/'); WriteRaw(PdfEncoders.Format("[{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "}]", rect.X1, rect.Y1, rect.X2, rect.Y2)); _lastCat = CharCat.Delimiter; } public void Write(PdfReference iref) { WriteSeparator(CharCat.Character); WriteRaw(iref.ToString()); _lastCat = CharCat.Character; } public void WriteDocString(string text, bool unicode) { WriteSeparator(CharCat.Delimiter); //WriteRaw(PdfEncoders.DocEncode(text, unicode)); byte[] bytes; if (!unicode) bytes = PdfEncoders.DocEncoding.GetBytes(text); else bytes = PdfEncoders.UnicodeEncoding.GetBytes(text); bytes = PdfEncoders.FormatStringLiteral(bytes, unicode, true, false, _securityHandler); Write(bytes); _lastCat = CharCat.Delimiter; } public void WriteDocString(string text) { WriteSeparator(CharCat.Delimiter); //WriteRaw(PdfEncoders.DocEncode(text, false)); byte[] bytes = PdfEncoders.DocEncoding.GetBytes(text); bytes = PdfEncoders.FormatStringLiteral(bytes, false, false, false, _securityHandler); Write(bytes); _lastCat = CharCat.Delimiter; } public void WriteDocStringHex(string text) { WriteSeparator(CharCat.Delimiter); //WriteRaw(PdfEncoders.DocEncodeHex(text)); byte[] bytes = PdfEncoders.DocEncoding.GetBytes(text); bytes = PdfEncoders.FormatStringLiteral(bytes, false, false, true, _securityHandler); _stream.Write(bytes, 0, bytes.Length); _lastCat = CharCat.Delimiter; } /// /// Begins a direct or indirect dictionary or array. /// public void WriteBeginObject(PdfObject obj) { bool indirect = obj.IsIndirect; if (indirect) { WriteObjectAddress(obj); if (_securityHandler != null) _securityHandler.SetHashKey(obj.ObjectID); } _stack.Add(new StackItem(obj)); if (indirect) { if (obj is PdfArray) WriteRaw("[\n"); else if (obj is PdfDictionary) WriteRaw("<<\n"); _lastCat = CharCat.NewLine; } else { if (obj is PdfArray) { WriteSeparator(CharCat.Delimiter); WriteRaw('['); _lastCat = CharCat.Delimiter; } else if (obj is PdfDictionary) { NewLine(); WriteSeparator(CharCat.Delimiter); WriteRaw("<<\n"); _lastCat = CharCat.NewLine; } } if (_layout == PdfWriterLayout.Verbose) IncreaseIndent(); } /// /// Ends a direct or indirect dictionary or array. /// public void WriteEndObject() { int count = _stack.Count; Debug.Assert(count > 0, "PdfWriter stack underflow."); StackItem stackItem = _stack[count - 1]; _stack.RemoveAt(count - 1); PdfObject value = stackItem.Object; bool indirect = value.IsIndirect; if (_layout == PdfWriterLayout.Verbose) DecreaseIndent(); if (value is PdfArray) { if (indirect) { WriteRaw("\n]\n"); _lastCat = CharCat.NewLine; } else { WriteRaw("]"); _lastCat = CharCat.Delimiter; } } else if (value is PdfDictionary) { if (indirect) { if (!stackItem.HasStream) WriteRaw(_lastCat == CharCat.NewLine ? ">>\n" : " >>\n"); } else { Debug.Assert(!stackItem.HasStream, "Direct object with stream??"); WriteSeparator(CharCat.NewLine); WriteRaw(">>\n"); _lastCat = CharCat.NewLine; } } if (indirect) { NewLine(); WriteRaw("endobj\n"); if (_layout == PdfWriterLayout.Verbose) WriteRaw("%--------------------------------------------------------------------------------------------------\n"); } } /// /// Writes the stream of the specified dictionary. /// public void WriteStream(PdfDictionary value, bool omitStream) { StackItem stackItem = _stack[_stack.Count - 1]; Debug.Assert(stackItem.Object is PdfDictionary); Debug.Assert(stackItem.Object.IsIndirect); stackItem.HasStream = true; WriteRaw(_lastCat == CharCat.NewLine ? ">>\nstream\n" : " >>\nstream\n"); if (omitStream) { WriteRaw(" «...stream content omitted...»\n"); // useful for debugging only } else { byte[] bytes = value.Stream.Value; if (bytes.Length != 0) { if (_securityHandler != null) { bytes = (byte[])bytes.Clone(); bytes = _securityHandler.EncryptBytes(bytes); } Write(bytes); if (_lastCat != CharCat.NewLine) WriteRaw('\n'); } } WriteRaw("endstream\n"); } public void WriteRaw(string rawString) { if (String.IsNullOrEmpty(rawString)) return; byte[] bytes = PdfEncoders.RawEncoding.GetBytes(rawString); _stream.Write(bytes, 0, bytes.Length); _lastCat = GetCategory((char)bytes[bytes.Length - 1]); } public void WriteRaw(char ch) { Debug.Assert(ch < 256, "Raw character greater than 255 detected."); _stream.WriteByte((byte)ch); _lastCat = GetCategory(ch); } public void Write(byte[] bytes) { if (bytes == null || bytes.Length == 0) return; _stream.Write(bytes, 0, bytes.Length); _lastCat = GetCategory((char)bytes[bytes.Length - 1]); } void WriteObjectAddress(PdfObject value) { if (_layout == PdfWriterLayout.Verbose) WriteRaw(String.Format("{0} {1} obj % {2}\n", value.ObjectID.ObjectNumber, value.ObjectID.GenerationNumber, value.GetType().FullName)); else WriteRaw(String.Format("{0} {1} obj\n", value.ObjectID.ObjectNumber, value.ObjectID.GenerationNumber)); } public void WriteFileHeader(PdfDocument document) { StringBuilder header = new StringBuilder("%PDF-"); int version = document._version; header.Append((version / 10).ToString(CultureInfo.InvariantCulture) + "." + (version % 10).ToString(CultureInfo.InvariantCulture) + "\n%\xD3\xF4\xCC\xE1\n"); WriteRaw(header.ToString()); if (_layout == PdfWriterLayout.Verbose) { WriteRaw(String.Format("% PDFsharp Version {0} (verbose mode)\n", VersionInfo.Version)); // Keep some space for later fix-up. _commentPosition = (int)_stream.Position + 2; WriteRaw("% \n"); WriteRaw("% \n"); WriteRaw("% \n"); WriteRaw("% \n"); WriteRaw("% \n"); WriteRaw("%--------------------------------------------------------------------------------------------------\n"); } } public void WriteEof(PdfDocument document, int startxref) { WriteRaw("startxref\n"); WriteRaw(startxref.ToString(CultureInfo.InvariantCulture)); WriteRaw("\n%%EOF\n"); int fileSize = (int)_stream.Position; if (_layout == PdfWriterLayout.Verbose) { TimeSpan duration = DateTime.Now - document._creation; _stream.Position = _commentPosition; // Without InvariantCulture parameter the following line fails if the current culture is e.g. // a Far East culture, because the date string contains non-ASCII characters. // So never never never never use ToString without a culture info. WriteRaw("Creation date: " + document._creation.ToString("G", CultureInfo.InvariantCulture)); _stream.Position = _commentPosition + 50; WriteRaw("Creation time: " + duration.TotalSeconds.ToString("0.000", CultureInfo.InvariantCulture) + " seconds"); _stream.Position = _commentPosition + 100; WriteRaw("File size: " + fileSize.ToString(CultureInfo.InvariantCulture) + " bytes"); _stream.Position = _commentPosition + 150; WriteRaw("Pages: " + document.Pages.Count.ToString(CultureInfo.InvariantCulture)); _stream.Position = _commentPosition + 200; WriteRaw("Objects: " + document._irefTable.ObjectTable.Count.ToString(CultureInfo.InvariantCulture)); } } /// /// Gets or sets the indentation for a new indentation level. /// internal int Indent { get { return _indent; } set { _indent = value; } } int _indent = 2; int _writeIndent; /// /// Increases indent level. /// void IncreaseIndent() { _writeIndent += _indent; } /// /// Decreases indent level. /// void DecreaseIndent() { _writeIndent -= _indent; } ///// ///// Returns an indent string of blanks. ///// //static string Ind(int _indent) //{ // return new String(' ', _indent); //} /// /// Gets an indent string of current indent. /// string IndentBlanks { get { return new string(' ', _writeIndent); } } void WriteIndent() { WriteRaw(IndentBlanks); } void WriteSeparator(CharCat cat, char ch) { switch (_lastCat) { case CharCat.NewLine: if (_layout == PdfWriterLayout.Verbose) WriteIndent(); break; case CharCat.Delimiter: break; case CharCat.Character: if (_layout == PdfWriterLayout.Verbose) { //if (cat == CharCat.Character || ch == '/') _stream.WriteByte((byte)' '); } else { if (cat == CharCat.Character) _stream.WriteByte((byte)' '); } break; } } void WriteSeparator(CharCat cat) { WriteSeparator(cat, '\0'); } public void NewLine() { if (_lastCat != CharCat.NewLine) WriteRaw('\n'); } CharCat GetCategory(char ch) { if (Lexer.IsDelimiter(ch)) return CharCat.Delimiter; if (ch == Chars.LF) return CharCat.NewLine; return CharCat.Character; } enum CharCat { NewLine, Character, Delimiter, } CharCat _lastCat; /// /// Gets the underlying stream. /// internal Stream Stream { get { return _stream; } } Stream _stream; internal PdfStandardSecurityHandler SecurityHandler { get { return _securityHandler; } set { _securityHandler = value; } } PdfStandardSecurityHandler _securityHandler; class StackItem { public StackItem(PdfObject value) { Object = value; } public readonly PdfObject Object; public bool HasStream; } readonly List _stack = new List(); int _commentPosition; } }