// --------------------------------------------------------------------
// Creating PDF output
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (C) 1993-2004 Otfried Cheong
Ipe 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.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeimage.h"
#include "ipetext.h"
#include "ipevisitor.h"
#include "ipepainter.h"
#include "ipegroup.h"
#include "iperef.h"
#include "ipeutils.h"
#include "ipepdfwriter.h"
#include "ipefontpool.h"
typedef std::vector::const_iterator BmIter;
// --------------------------------------------------------------------
class IpePdfPainter : public IpePainter {
public:
IpePdfPainter(const IpeStyleSheet *style, IpeStream &stream);
virtual ~IpePdfPainter() { }
virtual void Rect(const IpeRect &re);
protected:
virtual void DoPush();
virtual void DoPop();
virtual void DoNewPath();
virtual void DoMoveTo(const IpeVector &v);
virtual void DoLineTo(const IpeVector &v);
virtual void DoCurveTo(const IpeVector &v1, const IpeVector &v2,
const IpeVector &v3);
virtual void DoClosePath();
virtual void DoDrawPath();
virtual void DoDrawBitmap(IpeBitmap bitmap);
virtual void DoDrawText(const IpeText *text);
private:
void DrawColor(IpeAttribute color, const char *gray, const char *rgb);
void DrawAttributes();
private:
IpeStream &iStream;
// iActiveState records the attribute settings that have been
// recorded in the PDF output, to avoid repeating them
// over and over again.
StateSeq iActiveState;
};
IpePdfPainter::IpePdfPainter(const IpeStyleSheet *style, IpeStream &stream)
: IpePainter(style), iStream(stream)
{
State state;
state.iStroke = IpeAttribute::Black();
state.iFill = IpeAttribute::Black();
state.iDashStyle = IpeAttribute::Solid();
state.iLineCap = IpeAttribute(IpeAttribute::ELineCap, false, 0);
state.iLineJoin = IpeAttribute(IpeAttribute::ELineJoin, false, 0);
iActiveState.push_back(state);
}
void IpePdfPainter::Rect(const IpeRect &re)
{
iStream << Matrix() * re.Min() << " "
<< Matrix().Linear() * (re.Max() - re.Min()) << " re\n";
}
void IpePdfPainter::DoNewPath()
{
DrawAttributes();
}
void IpePdfPainter::DoMoveTo(const IpeVector &v)
{
iStream << Matrix() * v << " m\n";
}
void IpePdfPainter::DoLineTo(const IpeVector &v)
{
iStream << Matrix() * v << " l\n";
}
void IpePdfPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2,
const IpeVector &v3)
{
iStream << Matrix() * v1 << " "
<< Matrix() * v2 << " "
<< Matrix() * v3 << " c\n";
}
void IpePdfPainter::DoClosePath()
{
iStream << "h ";
}
void IpePdfPainter::DoPush()
{
State state = iActiveState.back();
iActiveState.push_back(state);
iStream << "q ";
}
void IpePdfPainter::DoPop()
{
iActiveState.pop_back();
iStream << "Q\n";
}
void IpePdfPainter::DrawColor(IpeAttribute color, const char *gray,
const char *rgb)
{
if (color.IsNullOrVoid())
return;
assert(color.IsAbsolute());
IpeColor col = StyleSheet()->Repository()->ToColor(color);
if (col.IsGray())
iStream << col.iRed << " " << gray << "\n";
else
iStream << col << " " << rgb << "\n";
}
void IpePdfPainter::DrawAttributes()
{
State &s = iState.back();
State &sa = iActiveState.back();
const IpeRepository *rep = StyleSheet()->Repository();
if (s.iDashStyle && s.iDashStyle != sa.iDashStyle) {
sa.iDashStyle = s.iDashStyle;
if (s.iDashStyle.IsSolid())
iStream << "[] 0 d\n";
else
iStream << rep->String(s.iDashStyle) << " d\n";
}
if (s.iLineWidth && s.iLineWidth != sa.iLineWidth) {
sa.iLineWidth = s.iLineWidth;
iStream << rep->String(s.iLineWidth) << " w\n";
}
if (s.iLineCap && s.iLineCap != sa.iLineCap) {
sa.iLineCap = s.iLineCap;
iStream << int(s.iLineCap.Index()) << " J\n";
}
if (s.iLineJoin && s.iLineJoin != sa.iLineJoin) {
sa.iLineJoin = s.iLineJoin;
iStream << int(s.iLineJoin.Index()) << " j\n";
}
if (!s.iStroke.IsNullOrVoid() && s.iStroke != sa.iStroke) {
sa.iStroke = s.iStroke;
DrawColor(s.iStroke, "G", "RG");
}
if (!s.iFill.IsNullOrVoid() && s.iFill != sa.iFill) {
sa.iFill = s.iFill;
DrawColor(s.iFill, "g", "rg");
/*
if (s.iFill.IsNullOrVoid())
return;
assert(s.iFill.IsAbsolute());
IpeColor col = StyleSheet()->Repository()->ToColor(s.iFill);
iStream << col.iRed << " " << col.iGreen << " "
<< col.iBlue << " /P1 scn\n";
*/
}
}
void IpePdfPainter::DoDrawPath()
{
bool noStroke = Stroke().IsNullOrVoid();
bool noFill = Fill().IsNullOrVoid();
bool eofill = !WindRule() || !WindRule().Index();
if (noStroke && noFill)
iStream << "n\n"; // no op path
else if (noStroke)
iStream << (eofill ? "f*\n" : "f\n"); // fill only
else if (noFill)
iStream << "S\n"; // stroke only
else
iStream << (eofill ? "B*\n" : "B\n"); // fill and then stroke
}
void IpePdfPainter::DoDrawBitmap(IpeBitmap bitmap)
{
if (bitmap.ObjNum() < 0)
return;
iStream << Matrix() << " cm /Image"
<< bitmap.ObjNum() << " Do\n";
}
void IpePdfPainter::DoDrawText(const IpeText *text)
{
const IpeText::XForm *xf = text->GetXForm();
if (xf) {
iStream << Matrix() << " cm " ;
iStream << IpeMatrix(xf->iStretch.iX, 0, 0, xf->iStretch.iY, 0, 0)
<< " cm ";
iStream.PutRaw(xf->iStream.data(), xf->iStream.size());
iStream << "\n";
}
}
// --------------------------------------------------------------------
/*! \class IpePdfWriter
\brief Create PDF file.
This class is responsible for the creation of a PDF file from the
Ipe data. You have to create an IpePdfWriter first, providing a file
that has been opened for (binary) writing and is empty. Then call
EmbedFonts() to embed the IpeFontPool, EmbedBitmaps() to embed the
bitmaps, and CreatePages() to embed the pages.
\c CreateXmlStream embeds a stream with the XML representation of
the Ipe document. Finally, call \c CreateTrailer to complete the PDF
document, and close the file.
Some reserved PDF object numbers:
- 0: Must be left empty (a PDF restriction).
- 1: Ipe XML stream.
- 2: Parent of all pages objects.
*/
//! Create PDF writer operating on this (open and empty) file.
/*! If \a noShading is \c true, no background shading will be applied
to pages (if such a shading is defined in the document's style
sheet. */
IpePdfWriter::IpePdfWriter(IpeTellStream &stream, const IpeDocument *doc,
bool noShading, int compression)
: iStream(stream), iDoc(doc)
{
iCompressLevel = compression;
iObjNum = 3; // 0 - 2 are reserved
iXmlStreamNum = -1; // no XML stream yet
iResourceNum = -1;
iShadingNum = -1;
iStream << "%PDF-1.3\n";
IpeShading s = iDoc->StyleSheet()->FindShading();
if (!noShading && s.iType) {
iShadingNum = StartObject();
iStream << "<<\n"
<< " /ShadingType " << int(s.iType) << "\n"
<< " /ColorSpace /DeviceRGB\n";
switch (s.iType) {
case IpeShading::EAxial:
iStream << " /Coords [" << s.iV[0] << " " << s.iV[1] << "]\n";
break;
case IpeShading::ERadial:
iStream << " /Coords [" << s.iV[0] << " " << s.iRadius[0]
<< " " << s.iV[1] << " " << s.iRadius[1] << "]\n";
default:
break;
}
iStream << " /Function << /FunctionType 2 /Domain [ 0 1 ] /N 1\n"
<< " /C1 [" << s.iColor[0] << "]\n"
<< " /C0 [" << s.iColor[1]<< "] >>\n"
<< " /Extend [" << (s.iExtend[0] ? "true " : "false ")
<< (s.iExtend[1] ? "true " : "false ") << "]\n"
<< ">> endobj\n";
}
/*
const int patSize = 8;
const int imgSize = 1;
StartObject(); // object 1 -> image for the pattern
iStream << "<<\n";
iStream << "/Type /XObject\n";
iStream << "/Subtype /Image\n";
iStream << "/ImageMask true\n";
iStream << "/Width " << patSize << "\n";
iStream << "/Height " << patSize << "\n";
iStream << "/BitsPerComponent 1\n";
iStream << "/Length " << (patSize * patSize) / 8 << "\n>> stream\n";
for (int y = 0; y < patSize; ++y) {
char byte = (y & 1) ? char(0xaa) : 0x55;
for (int x = 0; x < patSize / 8; ++x)
iStream.PutChar(byte);
}
iStream << "\nendstream endobj\n";
StartObject(); // object 2 -> pattern
iStream << "<<\n";
iStream << "/Type /Pattern\n";
iStream << "/PatternType 1\n";
iStream << "/PaintType 2\n";
iStream << "/TilingType 1\n";
//iStream << "/BBox [0 0 " << imgSize << " " << imgSize << "]\n";
//iStream << "/XStep " << imgSize << "\n";
//iStream << "/YStep " << imgSize << "\n";
iStream << "/BBox [0 0 1 1] /XStep 1.0 /YStep 1.0\n";
iStream << "/Resources << /ProcSet [ /PDF ]\n";
iStream << " /XObject << /Image 1 0 R >> >>\n";
iStream << "/Matrix [" << imgSize << " 0 0 " << imgSize << " 0 0]\n";
iStream << "/Length 9\n>> stream\n";
iStream << "/Image Do\nendstream endobj\n";
*/
}
//! Destructor.
IpePdfWriter::~IpePdfWriter()
{
// nothing
}
/*! Write the beginning of the next object: "no 0 obj " and save
information about file position. Default argument uses next unused
object number. Returns number of new object. */
int IpePdfWriter::StartObject(int objnum)
{
if (objnum < 0)
objnum = iObjNum++;
iXref[objnum] = iStream.Tell();
iStream << objnum << " 0 obj ";
return objnum;
}
/*! Write all fonts to the PDF file, and fill in their object numbers.
Embeds no fonts if \c pool is 0, but must be called nevertheless. */
void IpePdfWriter::EmbedFonts(const IpeFontPool *pool)
{
// no fonts embedded?
if (!pool)
return;
// get fonts that require a cmap
std::vector cmapFonts;
iDoc->StyleSheet()->AllCMaps(cmapFonts);
std::map fontNumber;
for (IpeFontPool::const_iterator font = pool->begin();
font != pool->end(); ++font) {
int fontDescriptor = -1;
if (!font->iFontDescriptor.empty()) {
int streamId = StartObject();
iStream << "<<\n" << font->iStreamDict;
CreateStream(font->iStreamData.data(), font->iStreamData.size(), false);
fontDescriptor = StartObject();
iStream << "<<\n" << font->iFontDescriptor
<< streamId << " 0 R\n" << ">> endobj\n";
}
int cmap = -1;
int j = font->iName.size() - 2;
if (j > 0 && std::find(cmapFonts.begin(), cmapFonts.end(),
font->iName.substr(0, j)) != cmapFonts.end()) {
IpeString fpage = font->iName.substr(j);
// int page = std::strtol(fpage.CString(), 0, 16);
IpeString s;
IpeStringStream ss(s);
ss << "/CIDInit /ProcSet findresource begin\n"
<< "12 dict begin\n"
<< "begincmap\n"
<< "/CIDSystemInfo\n"
<< "<< /Registry (Adobe)\n"
<< "/Ordering (UCS)\n"
<< "/Supplement 0\n"
<< ">> def\n"
<< "/CMapName /Adobe.Identity.UCS def\n"
<< "/CMapType 2 def\n"
<< "1 begincodespacerange\n"
<< "<00> \n"
<< "endcodespacerange\n"
<< "1 beginbfrange\n"
<< "<00> <" << fpage << "00>\n"
<< "endbfrange\n"
<< "endcmap\n"
<< "CMapName currentdict /CMap defineresource pop\n"
<< "end\n"
<< "end\n";
cmap = StartObject();
iStream << "<<\n";
CreateStream(s.data(), s.size(), false);
}
int objectNumber = StartObject();
fontNumber[font->iLatexNumber] = objectNumber;
iStream << "<<\n" << font->iFontDict;
if (fontDescriptor >= 0)
iStream << "/FontDescriptor " << fontDescriptor << " 0 R\n";
if (cmap >= 0)
iStream << "/ToUnicode " << cmap << " 0 R\n";
iStream << ">> endobj\n";
}
iResourceNum = StartObject();
iStream << "<< ";
for (IpeFontPool::const_iterator font = pool->begin();
font != pool->end(); ++font) {
iStream << "/F" << font->iLatexNumber << " "
<< fontNumber[font->iLatexNumber] << " 0 R ";
}
iStream << ">> endobj\n";
}
//! Write a stream.
/*! Write a stream, either plain or compressed, depending on compress
level. Object must have been created with dictionary start having
been written.
If \a preCompressed is true, the data is already deflated.
*/
void IpePdfWriter::CreateStream(const char *data, int size, bool preCompressed)
{
if (preCompressed) {
iStream << "/Length " << size << " /Filter /FlateDecode >>\nstream\n";
iStream.PutRaw(data, size);
iStream << "\nendstream endobj\n";
return;
}
if (iCompressLevel > 0) {
int deflatedSize;
IpeBuffer deflated = IpeDeflateStream::Deflate(data, size, deflatedSize,
iCompressLevel);
iStream << "/Length " << deflatedSize
<< " /Filter /FlateDecode >>\nstream\n";
iStream.PutRaw(deflated.data(), deflatedSize);
iStream << "\nendstream endobj\n";
} else {
iStream << "/Length " << size << " >>\nstream\n";
iStream.PutRaw(data, size);
iStream << "endstream endobj\n";
}
}
// --------------------------------------------------------------------
void IpePdfWriter::EmbedBitmap(IpeBitmap bitmap)
{
int objnum = StartObject();
iStream << "<<\n";
iStream << "/Type /XObject\n";
iStream << "/Subtype /Image\n";
iStream << "/Width " << bitmap.Width() << "\n";
iStream << "/Height " << bitmap.Height() << "\n";
switch (bitmap.ColorSpace()) {
case IpeBitmap::EDeviceGray:
iStream << "/ColorSpace /DeviceGray\n";
break;
case IpeBitmap::EDeviceRGB:
iStream << "/ColorSpace /DeviceRGB\n";
break;
case IpeBitmap::EDeviceCMYK:
iStream << "/ColorSpace /DeviceCMYK\n";
// apparently JPEG CMYK images need this decode array??
// iStream << "/Decode [1 0 1 0 1 0 1 0]\n";
break;
}
switch (bitmap.Filter()) {
case IpeBitmap::EFlateDecode:
iStream << "/Filter /FlateDecode\n";
break;
case IpeBitmap::EDCTDecode:
iStream << "/Filter /DCTDecode\n";
break;
default:
// no filter
break;
}
iStream << "/BitsPerComponent " << bitmap.BitsPerComponent() << "\n";
iStream << "/Length " << bitmap.Size() << "\n>> stream\n";
iStream.PutRaw(bitmap.Data(), bitmap.Size());
iStream << "\nendstream endobj\n";
bitmap.SetObjNum(objnum);
}
// --------------------------------------------------------------------
IpeRect IpePdfWriter::PaintView(IpeStream &stream, const IpePage *page,
int view)
{
// XXX pattern
// stream << "/Cs1 cs\n";
if (iShadingNum >= 0)
stream << "/Sh sh\n";
IpePdfPainter painter(iDoc->StyleSheet(), stream);
IpeBBoxPainter bboxPainter(iDoc->StyleSheet());
std::vector layers;
page->MakeLayerTable(layers, view, false);
for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) {
if (layers[it->Layer()]) {
it->Object()->Draw(painter);
it->Object()->Draw(bboxPainter);
}
}
return bboxPainter.BBox();
}
//! Create contents and page stream for this page view.
void IpePdfWriter::CreatePageView(int pno, int view)
{
const IpePage *page = (*iDoc)[pno];
// Find bitmaps to embed
IpeBitmapFinder bm;
bm.ScanPage(page);
// Embed them
for (BmIter it = bm.iBitmaps.begin(); it != bm.iBitmaps.end(); ++it) {
BmIter it1 = std::find(iBitmaps.begin(), iBitmaps.end(), *it);
if (it1 == iBitmaps.end()) {
// look again, more carefully
for (it1 = iBitmaps.begin();
it1 != iBitmaps.end() && !it1->Equal(*it); ++it1)
;
if (it1 == iBitmaps.end())
EmbedBitmap(*it); // not yet embedded
else
it->SetObjNum(it1->ObjNum()); // identical IpeBitmap is embedded
iBitmaps.push_back(*it);
}
}
// Create page stream
IpeString pagedata;
IpeStringStream sstream(pagedata);
IpeRect bbox;
if (iCompressLevel > 0) {
IpeDeflateStream dfStream(sstream, iCompressLevel);
bbox = PaintView(dfStream, page, view);
dfStream.Close();
} else
bbox = PaintView(sstream, page, view);
int contentsobj = StartObject();
iStream << "<<\n";
CreateStream(pagedata.data(), pagedata.size(), (iCompressLevel > 0));
int pageobj = StartObject();
iStream << "<<\n";
iStream << "/Type /Page\n";
iStream << "/Contents " << contentsobj << " 0 R\n";
// iStream << "/Rotate 0\n";
iStream << "/Resources <<\n /ProcSet [ /PDF ";
if (iResourceNum >= 0)
iStream << "/Text";
if (!bm.iBitmaps.empty())
iStream << "/ImageB /ImageC";
iStream << " ]\n";
if (iResourceNum >= 0)
iStream << " /Font " << iResourceNum << " 0 R\n";
if (iShadingNum >= 0)
iStream << " /Shading << /Sh " << iShadingNum << " 0 R >>\n";
if (!bm.iBitmaps.empty()) {
iStream << " /XObject << ";
for (BmIter it = bm.iBitmaps.begin(); it != bm.iBitmaps.end(); ++it) {
// mention each PDF object only once
BmIter it1;
for (it1 = bm.iBitmaps.begin();
it1 != it && it1->ObjNum() != it->ObjNum(); it1++)
;
if (it1 == it)
iStream << "/Image" << it->ObjNum() << " " << it->ObjNum() << " 0 R ";
}
iStream << ">>\n";
}
// XXX pattern
/*
iStream << "/Pattern << /P1 2 0 R >>\n";
iStream << "/ColorSpace << /Cs1 [/Pattern /DeviceRGB] >>\n";
*/
iStream << " >>\n";
page->View(view).PageDictionary(iStream);
iStream << "/MediaBox [" << iDoc->Properties().iMedia << "]\n";
if (iDoc->Properties().iCropBox && !bbox.IsEmpty())
iStream << "/CropBox [" << bbox << "]\n";
if (!bbox.IsEmpty())
iStream << "/ArtBox [" << bbox << "]\n";
iStream << "/Parent 2 0 R\n";
iStream << ">> endobj\n";
iPageObjectNumbers.push_back(pageobj);
}
#if 0
//! Create PDF pages presenting this Ipe page.
void IpePdfWriter::CreatePage(int page)
{
for (int view = 0; view < (*iDoc)[page]->CountViews(); ++view)
CreatePageView(page, view);
}
#endif
//! Create all PDF pages.
void IpePdfWriter::CreatePages(bool lastView)
{
for (uint page = 0; page < iDoc->size(); ++page) {
int nViews = (*iDoc)[page]->CountViews();
if (lastView)
CreatePageView(page, lastView - 1);
else
for (int view = 0; view < nViews; ++view)
CreatePageView(page, view);
}
}
//! Create a stream containing the XML data.
void IpePdfWriter::CreateXmlStream(IpeString xmldata, bool preCompressed)
{
iXmlStreamNum = StartObject(1);
iStream << "<<\n/Type /Ipe\n";
CreateStream(xmldata.data(), xmldata.size(), preCompressed);
}
//! Write a PDF string object to the PDF stream.
void IpePdfWriter::WriteString(IpeString text)
{
iStream << "(";
for (int i = 0; i < text.size(); ++i) {
char ch = text[i];
switch (ch) {
case '(':
case ')':
case '\\':
iStream << "\\";
// fall through
default:
iStream << ch;
break;
}
}
iStream << ")";
}
// --------------------------------------------------------------------
struct Section {
int iPage;
int iObjNum;
std::vector iSubPages;
};
//! Create the bookmarks (PDF outline).
void IpePdfWriter::CreateBookmarks()
{
// first collect all information
std::vector sections;
for (uint pg = 0; pg < iDoc->size(); ++pg) {
IpeString s = (*iDoc)[pg]->Section(0);
IpeString ss = (*iDoc)[pg]->Section(1);
if (!s.empty()) {
Section sec;
sec.iPage = pg;
sections.push_back(sec);
}
if (!sections.empty() && !ss.empty())
sections.back().iSubPages.push_back(pg);
}
iBookmarks = -1;
if (sections.empty())
return;
// reserve outline object
iBookmarks = iObjNum++;
// assign object numbers
for (uint s = 0; s < sections.size(); ++s) {
sections[s].iObjNum = iObjNum++;
iObjNum += sections[s].iSubPages.size(); // leave space for subsections
}
// embed root
StartObject(iBookmarks);
iStream << "<<\n/First " << sections[0].iObjNum << " 0 R\n"
<< "/Count " << int(sections.size()) << "\n"
<< "/Last " << sections.back().iObjNum << " 0 R\n>> endobj\n";
for (uint s = 0; s < sections.size(); ++s) {
int count = sections[s].iSubPages.size();
int obj = sections[s].iObjNum;
// embed section
StartObject(obj);
iStream << "<<\n/Title ";
WriteString((*iDoc)[sections[s].iPage]->Section(0));
iStream << "\n/Parent " << iBookmarks << " 0 R\n"
<< "/Dest [ " << iPageObjectNumbers[sections[s].iPage]
<< " 0 R /XYZ null null null ]\n";
if (s > 0)
iStream << "/Prev " << sections[s-1].iObjNum << " 0 R\n";
if (s < sections.size() - 1)
iStream << "/Next " << sections[s+1].iObjNum << " 0 R\n";
if (count > 0)
iStream << "/Count " << -count << "\n"
<< "/First " << (obj + 1) << " 0 R\n"
<< "/Last " << (obj + count) << " 0 R\n";
iStream << ">> endobj\n";
// using ids obj + 1 .. obj + count for the subsections
for (int ss = 0; ss < count; ++ss) {
int pageNo = sections[s].iSubPages[ss];
StartObject(obj + ss + 1);
iStream << "<<\n/Title ";
WriteString((*iDoc)[pageNo]->Section(1));
iStream << "\n/Parent " << obj << " 0 R\n"
<< "/Dest [ " << iPageObjectNumbers[pageNo]
<< " 0 R /XYZ null null null ]\n";
if (ss > 0)
iStream << "/Prev " << (obj + ss) << " 0 R\n";
if (ss < count - 1)
iStream << "/Next " << (obj + ss + 2) << " 0 R\n";
iStream << ">> endobj\n";
}
}
}
// --------------------------------------------------------------------
//! Create the root objects and trailer of the PDF file.
void IpePdfWriter::CreateTrailer(IpeString creator)
{
const IpeDocument::SProperties &props = iDoc->Properties();
// Create /Pages
StartObject(2);
iStream << "<<\n" << "/Type /Pages\n";
iStream << "/Count " << int(iPageObjectNumbers.size()) << "\n";
iStream << "/Kids [ ";
for (std::vector::const_iterator it = iPageObjectNumbers.begin();
it != iPageObjectNumbers.end(); ++it)
iStream << (*it) << " 0 R ";
iStream << "]\n>> endobj\n";
// Create /Catalog
int catalogobj = StartObject();
iStream << "<<\n/Type /Catalog\n/Pages 2 0 R\n";
if (props.iFullScreen)
iStream << "/PageMode /FullScreen\n";
if (iBookmarks >= 0) {
if (!props.iFullScreen)
iStream << "/PageMode /UseOutlines\n";
iStream << "/Outlines " << iBookmarks << " 0 R\n";
}
if (iDoc->TotalViews() > 1) {
iStream << "/PageLabels << /Nums [ ";
int count = 0;
for (int page = 0; page < int(iDoc->size()); ++page) {
int nviews = (*iDoc)[page]->CountViews();
if (nviews > 1) {
iStream << count << " <>";
} else { // one view only!
iStream << count << " <
>";
}
count += nviews;
}
iStream << "] >>\n";
}
iStream << ">> endobj\n";
// Create /Info
int infoobj = StartObject();
iStream << "<<\n";
iStream << "/Creator (" << creator << ")\n";
iStream << "/Producer (" << creator << ")\n";
if (!props.iTitle.empty()) {
iStream << "/Title ";
WriteString(props.iTitle);
iStream << "\n";
}
if (!props.iAuthor.empty()) {
iStream << "/Author ";
WriteString(props.iAuthor);
iStream << "\n";
}
if (!props.iSubject.empty()) {
iStream << "/Subject ";
WriteString(props.iSubject);
iStream << "\n";
}
if (!props.iKeywords.empty()) {
iStream << "/Keywords ";
WriteString(props.iKeywords);
iStream << "\n";
}
iStream << "/CreationDate (" << props.iCreated << ")\n";
iStream << "/ModDate (" << props.iModified << ")\n";
iStream << ">> endobj\n";
// Create Xref
int xrefpos = iStream.Tell();
iStream << "xref\n0 " << iObjNum << "\n";
for (int obj = 0; obj < iObjNum; ++obj) {
std::map::const_iterator it = iXref.find(obj);
char s[12];
if (it == iXref.end()) {
std::sprintf(s, "%010d", obj);
iStream << s << " 00000 f \n"; // note the final space!
} else {
std::sprintf(s, "%010ld", iXref[obj]);
iStream << s << " 00000 n \n"; // note the final space!
}
}
iStream << "trailer\n<<\n";
iStream << "/Size " << iObjNum << "\n";
iStream << "/Root " << catalogobj << " 0 R\n";
iStream << "/Info " << infoobj << " 0 R\n";
iStream << ">>\nstartxref\n" << int(xrefpos) << "\n%%EOF\n";
}
// --------------------------------------------------------------------