/*
Copyright 2006,2007,2008 Stefano Chizzolini. http://clown.stefanochizzolini.it
Contributors:
* Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it)
* Haakan Aakerberg (bugfix contributor):
- [FIX:0.0.4:5]
This file should be part of the source code distribution of "PDF Clown library"
(the Program): see the accompanying README files for more info.
This Program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later version.
This Program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY, either expressed or implied; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
You should have received a copy of the GNU General Public License along with this
Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
Redistribution and use, with or without modification, are permitted provided that such
redistributions retain the above copyright notice, license and disclaimer, along with
this list of conditions.
*/
using it.stefanochizzolini.clown.bytes;
using it.stefanochizzolini.clown.files;
using it.stefanochizzolini.clown.objects;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace it.stefanochizzolini.clown.tokens{
/**
<summary>PDF file writer.</summary>
*/
public class Writer
{
#region static
#region fields
private static readonly byte[] HeaderBinaryHint = new byte[]{(byte)'%',(byte)0x80,(byte)0x80,(byte)0x80,(byte)0x80,(byte)'\r'}; // NOTE: Arbitrary binary characters (code >= 128) for ensuring proper behavior of file transfer applications.
#endregion
#endregion
#region dynamic
#region fields
private files.File file;
private IOutputStream stream;
#endregion
#region constructors
internal Writer(
IOutputStream stream,
files.File file
)
{
this.stream = stream;
this.file = file;
}
#endregion
#region interface
#region public
public IOutputStream Stream
{get{return stream;}}
/**
<summary>Serializes the PDF file compactly [PDF:1.6:3.4].</summary>
*/
public void WriteStandard(
)
{
StringBuilder xrefBuilder = new StringBuilder();
int xrefSize = file.IndirectObjects.Count;
// Header [PDF:1.6:3.4.1].
{
stream.Write("%PDF-1.6\r");
stream.Write(HeaderBinaryHint);
}
// Body [PDF:1.6:3.4.2].
{
/*
NOTE: compact xref table comprises just one section composed by just one subsection.
NOTE: As xref-table free entries MUST be arrayed as a linked list,
it's needed to cache intermingled in-use entries in order to properly render
the object number of the next free entry inside the previous one.
*/
StringBuilder xrefInUseBlockBuilder = new StringBuilder();
IndirectObjects indirectObjects = file.IndirectObjects;
PdfReference freeReference = indirectObjects[0].Reference; // Initialized to the first free entry.
for(
int index = 1;
index < xrefSize;
index++
)
{
PdfIndirectObject indirectObject = indirectObjects[index];
// Is the object entry in use?
if(indirectObject.IsInUse()) // In-use entry.
{
// Indirect object.
// Append to the xref table its xref!
xrefInUseBlockBuilder.Append(
indirectObject.Reference.CrossReference(
stream.Length
)
);
// Serialize its content!
indirectObject.WriteTo(stream);
}
else // Free entry.
{
// Flush current xref-table cache!
xrefBuilder.Append(
freeReference.CrossReference(index)
+ xrefInUseBlockBuilder.ToString()
);
// Initialize next xref-table subsection!
xrefInUseBlockBuilder.Length = 0;
freeReference = indirectObject.Reference;
}
}
// Flush current xref-table cache!
xrefBuilder.Append(
freeReference.CrossReference(0)
+ xrefInUseBlockBuilder.ToString()
);
}
// XRef table (unique section) [PDF:1.6:3.4.3]...
long startxref = stream.Length;
{
// ...header.
stream.Write(
"xref\r"
+ "0 " + xrefSize.ToString() + "\r"
);
// ...body.
stream.Write(xrefBuilder.ToString());
}
// Trailer [PDF:1.6:3.4.4]...
{
// ...header.
stream.Write("trailer\r");
// ...body.
// Update the counter!
PdfDictionary trailer = file.Trailer;
trailer[PdfName.Size] = new PdfInteger(xrefSize);
trailer.Remove(PdfName.Prev); // [FIX:0.0.4:5] It (wrongly) kept the 'Prev' entry of multiple-section xref tables.
// Serialize the contents!
trailer.WriteTo(stream);
// ...tail.
stream.Write(
"\r"
+ "startxref\r"
+ startxref.ToString() + "\r"
+ "%%EOF"
);
}
}
/**
<summary>Serializes the PDF file as incremental update [PDF:1.6:3.4.5].</summary>
*/
public void WriteIncremental(
)
{
StringBuilder xrefBuilder = new StringBuilder();
int xrefSize = file.IndirectObjects.Count;
Parser parser = file.Reader.Parser;
// Original content.
stream.Write(parser.Stream);
// Body update.
{
/*
NOTE: incremental xref table comprises multiple sections each one composed by multiple
subsections.
*/
// Insert modified indirect objects.
StringBuilder xrefSubBuilder = new StringBuilder(); // xref-table subsection builder.
int xrefSubCount = 0; // xref-table subsection counter.
int prevKey = 0; // Previous-entry object number.
foreach(
KeyValuePair<int,PdfIndirectObject> indirectObjectEntry
in file.IndirectObjects.ModifiedObjects
)
{
// Is the object in the current subsection?
/*
NOTE: to belong to the current subsection, the object entry MUST be contiguous with the
previous (1 condition) or the iteration has to have been just started (2 condition).
*/
if(indirectObjectEntry.Key - prevKey == 1
|| prevKey == 0) // Current subsection continues.
{
xrefSubCount++;
}
else // Current subsection terminates.
{
// Flush current xref-table subsection!
xrefBuilder.Append(
(prevKey - xrefSubCount + 1) + " " + xrefSubCount + "\r"
+ xrefSubBuilder.ToString()
);
// Initialize next xref-table subsection!
xrefSubBuilder.Length = 0;
xrefSubCount = 1;
}
prevKey = indirectObjectEntry.Key;
// Modified indirect object.
if(indirectObjectEntry.Value.IsInUse()) // In-use entry.
{
// Append to the current xref-table subsection its xref!
xrefSubBuilder.Append(
indirectObjectEntry.Value.Reference.CrossReference(
stream.Length
)
);
// Serialize its content!
indirectObjectEntry.Value.WriteTo(stream);
}
else // Free entry.
{
// Append to the current xref-table subsection its xref!
/*
NOTE: We purposely neglect the linked list of free entries
(see IndirectObjects.remove(int)), so that this entry links directly back to
object number 0, having a generation number of 65535 (not reusable) [PDF:1.6:3.4.3].
*/
xrefSubBuilder.Append(
indirectObjectEntry.Value.Reference.CrossReference(0)
);
}
}
// Flush current xref-table subsection!
xrefBuilder.Append(
(prevKey - xrefSubCount + 1) + " " + xrefSubCount + "\r"
+ xrefSubBuilder.ToString()
);
}
// XRef-table last section...
long startxref = stream.Length;
{
// ...header.
stream.Write("xref\r");
// ...body.
stream.Write(xrefBuilder.ToString());
}
// Updated trailer...
{
// ...header.
stream.Write("trailer\r");
// ...body.
// Update the entries!
PdfDictionary trailer = file.Trailer;
trailer[PdfName.Size] = new PdfInteger(xrefSize);
trailer[PdfName.Prev] = new PdfInteger((int)parser.RetrieveXRefOffset());
// Serialize the contents!
trailer.WriteTo(stream);
// ...tail.
stream.Write(
"\r"
+ "startxref\r"
+ startxref.ToString() + "\r"
+ "%%EOF"
);
}
}
#endregion
#endregion
#endregion
}
}
|